python - ¿Cómo almacenar datos como lo hace Freebase?
database-design rdf (6)
Una buena noticia para los usuarios de volcado de base de datos gratuita es que ahora Freebase ofrece el volcado de RDF: http://wiki.freebase.com/wiki/Data_dumps . Está en formato de tortuga, por lo que es muy conveniente utilizar cualquier base de datos de gráficos diseñada para RDF.
Mi sugerencia es también 4store: http://4store.org/ . Es simple y fácil de usar. Podría usar http request para hacer la operación SPARQL.
Una cosa difícil en mi proyecto es que el "." utilizado en el volcado de Freebase (para representar el acortamiento de la URL) no es reconocible para 4store. Así que agregué un paréntesis "<>" o todas las columnas contenidas "." y trato de acortar la URL yo mismo.
Admito que esto es básicamente una pregunta duplicada de Usar datos de base de datos en el servidor local. pero necesito respuestas más detalladas de las que ya se me dieron allí
Me enamoré de Freebase. Lo que quiero ahora es crear básicamente un clon de Freebase muy simple para almacenar contenido que puede no pertenecer a Freebase, pero que puede describirse utilizando el esquema de Freebase. Esencialmente, lo que quiero es una manera simple y elegante de almacenar datos, como lo hace Freebase, y ser capaz de usar fácilmente esos datos en una aplicación web de Python (CherryPy).
El Capítulo 2 de la guía de referencia de MQL dice:
La base de datos que subyace a Metaweb es fundamentalmente diferente de las bases de datos relacionales con las que puede estar familiarizado. Las bases de datos relacionales almacenan datos en forma de tablas, pero la base de datos Metaweb almacena datos como un gráfico de nodos y relaciones entre esos nodos .
¿Qué supongo que significa que debería estar utilizando un triplestore o una base de datos de gráficos como Neo4j? ¿Alguien aquí tiene alguna experiencia con el uso de uno de esos de un entorno de Python?
(Lo que en realidad he intentado hasta ahora es crear un esquema de base de datos relacional que pueda almacenar fácilmente temas de Freebase, pero tengo problemas para configurar las asignaciones en SQLAlchemy).
Cosas que estoy buscando
ACTUALIZACIÓN [28/12/2011]:
Encontré un artículo en el blog de Freebase que describe la tienda de tuplas propietaria / base de datos que los propios Freebase usan (gráficamente): http://blog.freebase.com/2008/04/09/a-brief-tour-of-graphd/
SPARQL es el lenguaje de consulta para consultar RDF, permite escribir consultas similares a SQL. La mayoría de las bases de datos RDF implementan interfaces SPARQL. Además, Freebase te permite exportar datos en RDF para que puedas utilizar esos datos directamente en una base de datos RDF y consultarlos con SPARQL.
Echaré un vistazo a este tutorial para tener una mejor idea de SPARQL.
Si va a manejar un gran conjunto de datos, como la base de datos gratuita, usaría 4store junto con cualquiera de los clientes de Python . 4store expone SPARQL a través de HTTP, puede realizar solicitudes HTTP para afirmar, eliminar y consultar datos. También maneja los conjuntos de resultados en JSON, y esto es realmente útil con Python. He utilizado esta infraestructura en varios proyectos, no con CherryPy sino con Django, pero supongo que esta diferencia en realidad no importa.
Mis 2 centavos ...
Utilizo un poco de código Java para convertir el volcado de datos de Freebase en RDF: https://github.com/castagna/freebase2rdf
Utilizo la tienda TDB de Apache Jena para cargar los datos RDF y Fuseki para servir los datos a través del protocolo SPARQL a través de HTTP.
Ver también:
Esto es lo que funcionó para mí. Le permite cargar todo un volcado de Freebase en una instalación estándar de MySQL en menos de 100 GB de disco. La clave es comprender el diseño de datos en un volcado y luego transformarlo (optimizándolo para espacio y velocidad).
Nociones de Freebase que debe comprender antes de intentar usar esto (todo tomado de la documentación):
- Tema: cualquier cosa de tipo ''/ common / topic'', preste atención a los diferentes tipos de ids que puede encontrar en Freebase: ''id'', ''mid'', ''guid'', ''webid'', etc.
- Dominio
- Tipo - relación "es una"
- Propiedades - relación "tiene una"
- Esquema
- Espacio de nombre
- Clave - legible para humanos en el espacio de nombres ''/ en''
Algunas otras características importantes de Freebase :
- el editor de consultas es tu amigo
- comprender las nociones de ''fuente'', ''propiedad'', ''destino'' y ''valor'' que se describen aquí
- todo tiene un medio, incluso cosas como ''/'', ''/ m'', ''/ en'', ''/ lang'', ''/ m / 0bnqs_5'', etc .; Prueba usando el editor de consultas:
[{''id'':''/'',''mid'':null}]
- usted no sabe qué es una entidad (es decir, una fila) en el volcado de datos, tiene que llegar a sus tipos para hacer eso (por ejemplo, cómo sé que
''/m/0cwtm''
es un ser humano); - cada entidad tiene al menos un tipo (pero generalmente muchas más)
- cada entidad tiene al menos una identificación / clave (pero generalmente muchas más)
- la ontología (es decir, los metadatos) está integrada en el mismo volcado y en el mismo formato que los datos (no es el caso con otras distribuciones como DBPedia, etc.)
- la columna de ''destino'' en el volcado es la más confusa, puede contener un medio o una clave (vea cómo las transformaciones a continuación tratan con esto)
- los dominios, tipos, propiedades son niveles de espacio de nombres al mismo tiempo (quien se le ocurrió esto es un genio en mi humilde opinión);
- entender qué es un tema y qué no es un tema (absolutamente crucial), por ejemplo, esta entidad
''/m/03lmb2f''
de tipo''/film/performance''
NO es un tema (elijo pensar en esto como en qué nodos en blanco RDF son aunque esto puede no ser filosóficamente preciso), mientras que''/m/04y78wb''
del tipo''/film/director''
(entre otros) es;
Transforma
(vea el código de Python en la parte inferior)
TRANSFORM 1 (desde shell, enlaces divididos desde espacios de nombres que ignoran el texto notable_para y no / lang / en):
python parse.py freebase.tsv #end up with freebase_links.tsv and freebase_ns.tsv
TRANSFORM 2 (desde la consola de Python, divide freebase_ns.tsv en freebase_ns_types.tsv, freebase_ns_props.tsv más otras 15 que ignoramos por el momento)
import e
e.split_external_keys( ''freebase_ns.tsv'' )
TRANSFORM 3 (desde la consola de Python, convertir propiedad y destino a medios)
import e
ns = e.get_namespaced_data( ''freebase_ns_types.tsv'' )
e.replace_property_and_destination_with_mid( ''freebase_links.tsv'', ns ) #produces freebase_links_pdmids.tsv
e.replace_property_with_mid( ''freebase_ns_props.tsv'', ns ) #produces freebase_ns_props_pmids.tsv
TRANSFORM 4 (desde la consola MySQL, cargue freebase_links_mids.tsv, freebase_ns_props_mids.tsv y freebase_ns_types.tsv en DB):
CREATE TABLE links(
source VARCHAR(20),
property VARCHAR(20),
destination VARCHAR(20),
value VARCHAR(1)
) ENGINE=MyISAM CHARACTER SET utf8;
CREATE TABLE ns(
source VARCHAR(20),
property VARCHAR(20),
destination VARCHAR(40),
value VARCHAR(255)
) ENGINE=MyISAM CHARACTER SET utf8;
CREATE TABLE types(
source VARCHAR(20),
property VARCHAR(40),
destination VARCHAR(40),
value VARCHAR(40)
) ENGINE=MyISAM CHARACTER SET utf8;
LOAD DATA LOCAL INFILE "/data/freebase_links_pdmids.tsv" INTO TABLE links FIELDS TERMINATED BY ''/t'' LINES TERMINATED BY ''/n'';
LOAD DATA LOCAL INFILE "/data/freebase_ns_props_pmids.tsv" INTO TABLE ns FIELDS TERMINATED BY ''/t'' LINES TERMINATED BY ''/n'';
LOAD DATA LOCAL INFILE "/data/freebase_ns_base_plus_types.tsv" INTO TABLE types FIELDS TERMINATED BY ''/t'' LINES TERMINATED BY ''/n'';
CREATE INDEX links_source ON links (source) USING BTREE;
CREATE INDEX ns_source ON ns (source) USING BTREE;
CREATE INDEX ns_value ON ns (value) USING BTREE;
CREATE INDEX types_source ON types (source) USING BTREE;
CREATE INDEX types_destination_value ON types (destination, value) USING BTREE;
Código
Guarde esto como e.py:
import sys
#returns a dict to be used by mid(...), replace_property_and_destination_with_mid(...) bellow
def get_namespaced_data( file_name ):
f = open( file_name )
result = {}
for line in f:
elements = line[:-1].split(''/t'')
if len( elements ) < 4:
print ''Skip...''
continue
result[(elements[2], elements[3])] = elements[0]
return result
#runs out of memory
def load_links( file_name ):
f = open( file_name )
result = {}
for line in f:
if len( result ) % 1000000 == 0:
print len(result)
elements = line[:-1].split(''/t'')
src, prop, dest = elements[0], elements[1], elements[2]
if result.get( src, False ):
if result[ src ].get( prop, False ):
result[ src ][ prop ].append( dest )
else:
result[ src ][ prop ] = [dest]
else:
result[ src ] = dict([( prop, [dest] )])
return result
#same as load_links but for the namespaced data
def load_ns( file_name ):
f = open( file_name )
result = {}
for line in f:
if len( result ) % 1000000 == 0:
print len(result)
elements = line[:-1].split(''/t'')
src, prop, value = elements[0], elements[1], elements[3]
if result.get( src, False ):
if result[ src ].get( prop, False ):
result[ src ][ prop ].append( value )
else:
result[ src ][ prop ] = [value]
else:
result[ src ] = dict([( prop, [value] )])
return result
def links_in_set( file_name ):
f = open( file_name )
result = set()
for line in f:
elements = line[:-1].split(''/t'')
result.add( elements[0] )
return result
def mid( key, ns ):
if key == '''':
return False
elif key == ''/'':
key = ''/boot/root_namespace''
parts = key.split(''/'')
if len(parts) == 1: #cover the case of something which doesn''t start with ''/''
print key
return False
if parts[1] == ''m'': #already a mid
return key
namespace = ''/''.join(parts[:-1])
key = parts[-1]
return ns.get( (namespace, key), False )
def replace_property_and_destination_with_mid( file_name, ns ):
fn = file_name.split(''.'')[0]
f = open( file_name )
f_out_mids = open(fn+''_pdmids''+''.tsv'', ''w'')
def convert_to_mid_if_possible( value ):
m = mid( value, ns )
if m: return m
else: return None
counter = 0
for line in f:
elements = line[:-1].split(''/t'')
md = convert_to_mid_if_possible(elements[1])
dest = convert_to_mid_if_possible(elements[2])
if md and dest:
elements[1] = md
elements[2] = dest
f_out_mids.write( ''/t''.join(elements)+''/n'' )
else:
counter += 1
print ''Skipped: '' + str( counter )
def replace_property_with_mid( file_name, ns ):
fn = file_name.split(''.'')[0]
f = open( file_name )
f_out_mids = open(fn+''_pmids''+''.tsv'', ''w'')
def convert_to_mid_if_possible( value ):
m = mid( value, ns )
if m: return m
else: return None
for line in f:
elements = line[:-1].split(''/t'')
md = convert_to_mid_if_possible(elements[1])
if md:
elements[1]=md
f_out_mids.write( ''/t''.join(elements)+''/n'' )
else:
#print ''Skipping '' + elements[1]
pass
#cPickle
#ns=e.get_namespaced_data(''freebase_2.tsv'')
#import cPickle
#cPickle.dump( ns, open(''ttt.dump'',''wb''), protocol=2 )
#ns=cPickle.load( open(''ttt.dump'',''rb'') )
#fn=''/m/0''
#n=fn.split(''/'')[2]
#dir = n[:-1]
def is_mid( value ):
parts = value.split(''/'')
if len(parts) == 1: #it doesn''t start with ''/''
return False
if parts[1] == ''m'':
return True
return False
def check_if_property_or_destination_are_mid( file_name ):
f = open( file_name )
for line in f:
elements = line[:-1].split(''/t'')
#if is_mid( elements[1] ) or is_mid( elements[2] ):
if is_mid( elements[1] ):
print line
#
def split_external_keys( file_name ):
fn = file_name.split(''.'')[0]
f = open( file_name )
f_out_extkeys = open(fn+''_extkeys'' + ''.tsv'', ''w'')
f_out_intkeys = open(fn+''_intkeys'' + ''.tsv'', ''w'')
f_out_props = open(fn+''_props'' + ''.tsv'', ''w'')
f_out_types = open(fn+''_types'' + ''.tsv'', ''w'')
f_out_m = open(fn+''_m'' + ''.tsv'', ''w'')
f_out_src = open(fn+''_src'' + ''.tsv'', ''w'')
f_out_usr = open(fn+''_usr'' + ''.tsv'', ''w'')
f_out_base = open(fn+''_base'' + ''.tsv'', ''w'')
f_out_blg = open(fn+''_blg'' + ''.tsv'', ''w'')
f_out_bus = open(fn+''_bus'' + ''.tsv'', ''w'')
f_out_soft = open(fn+''_soft'' + ''.tsv'', ''w'')
f_out_uri = open(fn+''_uri'' + ''.tsv'', ''w'')
f_out_quot = open(fn+''_quot'' + ''.tsv'', ''w'')
f_out_frb = open(fn+''_frb'' + ''.tsv'', ''w'')
f_out_tag = open(fn+''_tag'' + ''.tsv'', ''w'')
f_out_guid = open(fn+''_guid'' + ''.tsv'', ''w'')
f_out_dtwrld = open(fn+''_dtwrld'' + ''.tsv'', ''w'')
for line in f:
elements = line[:-1].split(''/t'')
parts_2 = elements[2].split(''/'')
if len(parts_2) == 1: #the blank destination elements - '''', plus the root domain ones
if elements[1] == ''/type/object/key'':
f_out_types.write( line )
else:
f_out_props.write( line )
elif elements[2] == ''/lang/en'':
f_out_props.write( line )
elif (parts_2[1] == ''wikipedia'' or parts_2[1] == ''authority'') and len( parts_2 ) > 2:
f_out_extkeys.write( line )
elif parts_2[1] == ''m'':
f_out_m.write( line )
elif parts_2[1] == ''en'':
f_out_intkeys.write( line )
elif parts_2[1] == ''source'' and len( parts_2 ) > 2:
f_out_src.write( line )
elif parts_2[1] == ''user'':
f_out_usr.write( line )
elif parts_2[1] == ''base'' and len( parts_2 ) > 2:
if elements[1] == ''/type/object/key'':
f_out_types.write( line )
else:
f_out_base.write( line )
elif parts_2[1] == ''biology'' and len( parts_2 ) > 2:
f_out_blg.write( line )
elif parts_2[1] == ''business'' and len( parts_2 ) > 2:
f_out_bus.write( line )
elif parts_2[1] == ''soft'' and len( parts_2 ) > 2:
f_out_soft.write( line )
elif parts_2[1] == ''uri'':
f_out_uri.write( line )
elif parts_2[1] == ''quotationsbook'' and len( parts_2 ) > 2:
f_out_quot.write( line )
elif parts_2[1] == ''freebase'' and len( parts_2 ) > 2:
f_out_frb.write( line )
elif parts_2[1] == ''tag'' and len( parts_2 ) > 2:
f_out_tag.write( line )
elif parts_2[1] == ''guid'' and len( parts_2 ) > 2:
f_out_guid.write( line )
elif parts_2[1] == ''dataworld'' and len( parts_2 ) > 2:
f_out_dtwrld.write( line )
else:
f_out_types.write( line )
Guarde esto como parse.py:
import sys
def parse_freebase_quadruple_tsv_file( file_name ):
fn = file_name.split(''.'')[0]
f = open( file_name )
f_out_links = open(fn+''_links''+''.tsv'', ''w'')
f_out_ns = open(fn+''_ns'' +''.tsv'', ''w'')
for line in f:
elements = line[:-1].split(''/t'')
if len( elements ) < 4:
print ''Skip...''
continue
#print ''Processing '' + str( elements )
#cases described here http://wiki.freebase.com/wiki/Data_dumps
if elements[1].endswith(''/notable_for''): #ignore notable_for, it has JSON in it
continue
elif elements[2] and not elements[3]: #case 1, linked
f_out_links.write( line )
elif not (elements[2].startswith(''/lang/'') and elements[2] != ''/lang/en''): #ignore languages other than English
f_out_ns.write( line )
if len(sys.argv[1:]) == 0:
print ''Pass a list of .tsv filenames''
for file_name in sys.argv[1:]:
parse_freebase_quadruple_tsv_file( file_name )
Notas:
- Dependiendo de la máquina, la creación del índice puede tomar entre unas pocas y más de 12 horas (sin embargo, tenga en cuenta la cantidad de datos con los que está tratando).
- Para poder recorrer los datos en ambas direcciones, necesita un índice de links.destination, que me pareció costoso en el tiempo y nunca finalizó.
- Muchas otras optimizaciones son posibles aquí. Por ejemplo, la tabla ''tipos'' es lo suficientemente pequeña como para cargarse en la memoria en un dict de Python (ver
e.get_namespaced_data( ''freebase_ns_types.tsv'' )
)
Y el descargo de responsabilidad estándar aquí. Han pasado unos meses desde que hice esto. Creo que es correcto en su mayoría, pero me disculpo si mis notas se perdieron algo. Lamentablemente, el proyecto para el que lo necesitaba se vino abajo, pero espero que esto ayude a otra persona. Si algo no está claro, deje un comentario aquí.
Y este es el código extra para mi otra respuesta. La carne está en edb.py. Ejecute desde la consola de Python y siga los ejemplos. O use el controlador web2py y ejecútelo en su navegador.
Guarde esto como edb.py:
import MySQLdb
import sys
connection = MySQLdb.connect (host = "localhost",
user = "root",
passwd = "x",
db = "y")
cursor = connection.cursor()
query_counter = 0
print_queries = False
limit = 1000
def fetch_one( query ):
global query_counter, print_queries
query = query + '' LIMIT '' + str(limit)
if print_queries:
print query
cursor = connection.cursor()
cursor.execute( query )
query_counter += 1
result = cursor.fetchone()
if result:
return result[0]
else:
return None
def fetch_all( query ):
global query_counter, print_queries
query = query + '' LIMIT '' + str(limit)
if print_queries:
print query
cursor = connection.cursor()
cursor.execute( query )
query_counter += 1
return cursor.fetchall()
def _flatten( list_of_lists ):
import itertools
return list(itertools.chain(*list_of_lists))
#Example: e._search_by_name(''steve martin'')
def _search_by_name( name, operator = ''='' ):
typed, ranked = {}, []
if name:
name = name.strip()
if not name:
return ( typed, ranked )
filler = '''' if operator == ''='' else ''%''
ranks = {}
#to filter meaningful stuff for every mid returned order by the number of types they have
#search for value text if prop. is
#select * from ns where value = ''the king'' and (property = ''/m/01gr'' or property = ''/m/06b'');
name_mid = _mid( ''/type/object/name'' )
alias_mid = _mid( ''/common/topic/alias'' )
query = "select ns.source from ns where ns.value %s ''%s%s'' and ns.property in (''%s'', ''%s'')" % ( operator, name, filler, name_mid, alias_mid )
for i in fetch_all( query ):
typed[ i[0] ] = _types( i[0] )
import operator
ranked = [ ( len( typed[i] ), i ) for i in typed ]
ranked = [ e[1] for e in sorted( ranked, key=operator.itemgetter(0), reverse = True ) ]
return (typed, ranked)
#Example: e._children('''') <---will get the top level domains
# e._children(''/film'') <---get all types from the domain
# e._children(''/film/film'') <---get all properties for the type
def _children( parent, expand = False, raw = False ):
query = "select t.source, t.value from types t where t.destination = ''%s''" % (parent)
res = fetch_all( query )
if raw:
return [ row[0] for row in res ]
if expand: prefix = parent
else: prefix = ''''
return [ prefix + ''/'' + row[1] for row in fetch_all(query) ]
#Example: e._parent(''/film/film/songs'')
def _parent( child ): # ''/people/marriage/to'' -> ''/people/marriage''
#if not isinstance( child, str ): return None # what kind of safety mechanisms do we need here?
return ''/''.join(child.split(''/'')[:-1])
#Example: e._domains()
def _domains():
return _children('''')
#Example: e._top_level_types()
def _top_level_types():
return _children(''/type'')
#TODO get all primitive types
#Example: e._mid(''/type/object'')
# e._mid(''/authority/imdb/name/nm0000188'')
def _mid( key ):
if key == '''':
return None
elif key == ''/'':
key = ''/boot/root_namespace''
parts = key.split(''/'')
if parts[1] == ''m'': #already a mid
return key
namespace = ''/''.join(parts[:-1])
key = parts[-1]
return fetch_one( "select source from types t where t.destination = ''%s'' and t.value = ''%s''" % (namespace, key) )
#Example: e._key(''/type'')
def _key( mid ):
if isinstance( mid, str):
res = _keys( mid )
if not res:
return None
rt = [ r for r in res if r.startswith( ''/type'' ) ]
if rt:
return rt[0]
else:
return res[0]
elif isinstance( mid, list ) or isinstance( mid, tuple ):
res = [ _key( e ) for e in mid ]
return [ r for r in res if r is not None ]
else:
return None
def _keys( mid ):
# check for ''/type/object/key'' as well?
query = "select t.destination, t.value from types t where t.source = ''%s''" % mid
return [ row[0]+''/''+row[1] for row in fetch_all( query ) ]
#Example: e._types(''/m/0p_47'')
def _types( mid ):
tm = _mid( ''/type/object/type'' )
query = "select l.destination from links l where l.source = ''%s'' and l.property = ''%s''" % (mid, tm)
return [ row[0] for row in fetch_all( query ) ]
#Example: e._props_n(''/m/0p_47'') <---Named immediate properties (like name, etc.)
def _props_n( mid ): #the same property can be set more than once per topic!
query = "select ns.property from ns where ns.source = ''%s''" % (mid)
return list( set( [ row[0] for row in fetch_all( query ) ] ) )
#Example: e._props_l(''/m/0p_47'') <---All remote properties, some are named, some are anonymous
def _props_l( mid ): #the same property can be set more than once per topic!
tm = _mid( ''/type/object/type'' ) #exclude types, they have tons of instance links
res = fetch_all( "select l.property, l.destination from links l where l.source = ''%s'' and property <> ''%s''" % (mid, tm) )
output = {}
for r in res:
dests = output.get( r[0], False )
if dests:
dests.append( r[1] )
else:
output[ r[0] ] = [ r[1] ]
return output
#Example: e._props_ln(''/m/0p_47'') <---All remote named properties
def _props_ln( mid ): #named properties
result = []
ps = _props_l( mid )
common_topic = _mid( ''/common/topic'' )
for p in ps:
ts = _types( ps[p][0] )
if common_topic in ts: #it''s a common topic
result.append( p )
return result
#Example: e._props_la(''/m/0p_47'') <---All remote anonymous properties, these actually belong to the children!
#instead of has type /common/topic we used to check if it has name
def _props_la( mid, raw = True ): #anonymous properties (blank nodes in RDF?)
result = []
ps = _props_l( mid )
common_topic = _mid( ''/common/topic'' )
for p in ps:
ts = _types( ps[p][0] )
if common_topic not in ts: #it is not a common topic
t = _key( _types( ps[p][0] ) )
if t and ''/type/type'' not in t: #FIXME: hack not to go into types, could be done better
result.append( _children( t[0], expand=True, raw=raw ) ) #get the first, is this correct?
return _flatten( result ) #it is a list of lists
#FIXME: try to get ''/film/actor/film'' -> ''/type/property/expected_type'' -> ''/film/performance'' -> properties/children
#instead of trying is something has name
#Example: e._get_n(''/m/0p_47'', e._props_n(''/m/0p_47'')[0])[''/lang/en''] <---These come with a namespace
def _get_n( mid, prop ): #the same property can be set more than once per topic!
p = _mid( prop )
query = "select ns.value from ns where ns.source = ''%s'' and ns.property = ''%s''" % (mid, p)
return [ r[0] for r in fetch_all( query ) ]
#Example: e._get_l(''/m/0p_47'', e._props_l(''/m/0p_47'')[0]) <---returns a list of mids coresponding to that prop.
# e._name(e._get_l(''/m/0p_47'', ''/film/writer/film''))
def _get_l( mid, prop ): #the same property can be set more than once per topic!
p = _mid( prop )
query = "select l.destination from links l where l.source = ''%s'' and l.property = ''%s''" % (mid, p)
return [ row[0] for row in fetch_all( query ) ]
#Example: e._name(e._get_ln(''/m/0p_47'', e._props_ln(''/m/0p_47'')[0]))
def _get_ln( mid, p ): #just alias for _get_l, keeping for consistency
return _get_l( mid, p )
#Example: e._name(e._get_la(''/m/0p_47'', ''/film/performance/film''))
def _get_la( mid, prop ):
result = []
ps = _props_l( mid )
for p in ps:
es = _get_l( mid, p ) #get the destinations
if not es: continue
ts = set( _types( es[0] ) )
if _mid(_parent(_key(_mid(prop)))) in ts: #should be able to do this more efficiently!!!
for e in es:
result.append( _get_l( e, prop ) )
return _flatten( result ) #return after the first result
#How do we determine properties with multiple values vs those with singular (i.e. place of birth)?
#is this in the ontology?
#Ans: yes, /type/property/unique
#Example: e._all_names_ln(''/m/0p_47'') <---gets all of object''s remote named properties
def _all_names_ln( mid ):
result = {}
for p in _props_ln( mid ):
result[ _key(p) ] = _name( _get_ln( mid, p ) )
return result
#Example: e._all_names_la(''/m/0p_47'') <---gets all of object''s remote anonymous properties
def _all_names_la( mid ): #TODO: prevent loops, run e.all_names_la(''/m/0p_47'')
result = {}
for p in _props_la( mid ):
result[ _key( p ) ] = _name ( _get_la( mid, p ) )
return result
#FIXME: _all_names_la is going into destinations which are types and have a ton of instance links...
#Example: e._name(''/m/0p_47'') <---the name of a topic
#
def _name( mid ):
if isinstance( mid, str ):
nm = _mid( ''/type/object/name'' )
return _get_n( mid, nm )
elif isinstance( mid, list ) or isinstance( mid, tuple ) or isinstance( mid, set ):
return [ _name( e ) for e in mid ]
else:
return None
#for internal use only
def _get_linked( mid ):
tm = _mid( ''/type/object/type'' ) #exclude types, they have tons of instance links
query = "select destination from links where source = ''%s'' and property <> ''%s'' " % ( mid, tm )
return set( [ r[0] for r in fetch_all( query ) ] )
#for internal use only
def _get_connections_internal( entity1, target, path, all_paths, depth, max_depth):
import copy
if depth > max_depth:
return
if True:
print
print str(entity1) + '', '' + str(target)
print str( path )
print str( all_paths )
print depth
path.append( entity1 )
linked1 = _get_linked( entity1 )
if target in linked1 or entity1 == target:
path.append( target )
all_paths.append( path )
#print str( path )
return
for l1 in linked1:
if l1 in path:
continue
_get_connections_internal( l1,
target,
copy.copy( path ),
all_paths,
depth+1,
max_depth )
#Example: e._name(e._get_connections(''/m/0p_47'', ''/m/0cwtm'')) <---find path in the graph between the two entities
def _get_connections( entity1, target ):
result = []
_get_connections_internal( entity1, target, [], result, 0, 2 )
return result
#for internal use only
def _get_connections_internal2( entity1, entity2, path1, path2, all_paths, depth, max_depth, level ):
import copy
if depth > max_depth:
return
if level < 0: level = 0
path1.append( entity1 )
path2.append( entity2 )
if entity1 == entity2 and level == 0:
all_paths.append( ( path1, path2 ) ) #no need to append entity1 or entity2 to the paths
return
linked1 = _get_linked( entity1 )
if entity2 in linked1 and entity2 not in path1 and level == 0:
path1.append( entity2 )
all_paths.append( ( path1, path2 ) )
return
linked2 = _get_linked( entity2 )
if entity1 in linked2 and entity1 not in path2 and level == 0:
path2.append( entity1 )
all_paths.append( ( path1, path2 ) )
return
inters = linked1.intersection( linked2 )
inters = inters.difference( set( path1 ) )
inters = inters.difference( set( path2 ) )
if inters and level == 0:
for e in inters: #these are many paths, have to clone
p1 = copy.copy( path1 )
p1.append( e )
p2 = copy.copy( path2 )
p2.append( e )
all_paths.append( ( p1,p2 ) )
return
for l1 in linked1:
if l1 in path1 or l1 in path2:
continue
for l2 in linked2:
if l2 in path1 or l2 in path2:
continue
_get_connections_internal2( l1, l2,
copy.copy( path1 ), copy.copy( path2 ),
all_paths,
depth+1,
max_depth,
level - 1 )
#Example: e._name(e._get_connections2(''/m/0p_47'', ''/m/0cwtm'')) <---returns two meeting paths starting from both entities
# e._name(e._get_connections(''/m/0p_47'', ''/m/0cwtm'', level=1)) <---search deeper
# e._name(e._get_connections(''/m/0p_47'', ''/m/0cwtm'', level=2)) <---even deeper
def _get_connections2( entity1, entity2, level = 0 ):
result = []
_get_connections_internal2( entity1, entity2, [], [], result, 0, 15, level )
return result
Y aquí hay un ejemplo de controlador web2py (solo copie edb.py en el directorio de modelos web2py):
# -*- coding: utf-8 -*-
def mid_to_url( mid ):
return mid.split(''/'')[2]
def index():
form = FORM( TABLE( TR( INPUT(_name=''term'', _value=request.vars.term ) ),
TR(INPUT(_type=''submit'', _value=''Search'') ) ),
_method=''get'')
typed, ranked = _search_by_name( request.vars.term )
rows = []
for r in ranked:
keys = []
for t in typed[r]:
k = _key( t )
if k:
keys.append( k )
rows.append( TR( TD( A(_name( r ),
_href = URL(''result'', args = [mid_to_url(r)]))),
TD( XML( ''<br/>''.join( keys ) ) ) ) )
result = TABLE( *rows )
return {
''form'': form,
''result'' : result
}
def result():
path, data = '''', ''''
if not request.args:
return { ''path'':path, ''data'':data}
path_rows = []
for ra in range(len(request.args)):
if ra%2:
arrow_url = URL( ''static'', ''images/blue_arr.png'' )
display_name = _key(''/m/''+request.args[ra]) #it''s a property
else:
arrow_url = URL( ''static'', ''images/red_arr.png'' )
display_name = _name(''/m/''+request.args[ra]) #it''s a topic
path_rows.append( TD( A( display_name, _href=URL( args = request.args[0:ra+1] ) ) ) )
path_rows.append( TD( IMG( _src = arrow_url ) ) )
path = TABLE( *path_rows )
elems = [ ''/m/''+a for a in request.args ]
if _mid( ''/type/property'' ) in _types( elems[-1] ): #we are rendering a property
objects = _get_ln( elems[-2], elems[-1] )
if not objects: #there should be a better way to see if this is anonymous
objects = _get_la( elems[-2], elems[-1] )
data = TABLE( *[ TR( TD( A(_name(o), _href = URL( args = request.args+[mid_to_url(o)])))) for o in objects ] )
else: #we are rendering a topic
direct_props = TABLE(*[TR(TD(_key(p)), TD('', ''.join(_get_n( elems[-1], p)))) for p in _props_n( elems[-1] )])
linked_named_props = TABLE(*[TR(TD(A(_key(p),
_href = URL(args = request.args+[mid_to_url(p)])))) for p in _props_ln( elems[-1] ) ] )
linked_anon_props = TABLE(*[TR(TD(A(_key(p),
_href = URL(args = request.args+[mid_to_url(p)])))) for p in _props_la( elems[-1] ) ] )
data = TABLE( TR( TH( ''Linked named data:''), TH( ''Linked anonymous data:'' ), TH( ''Direct data:'' ) ),
TR( TD( linked_named_props ), TD( linked_anon_props ), TD( direct_props ) ) )
return { ''path'': path, ''data'':data }
Eche un vistazo a https://cayley.io . Creo que está escrito por el mismo autor y usa los mismos principios que graphd
, el backend de Freebase, antes de que Google lo matara.
En cuanto a los datos, es probable que desee ejecutar algo como esto para limpiar los volcados de DB de Freebase o usar datahub .