python database-design rdf freebase triplestore

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.



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 .