¿Hay un equivalente a cometer en el marco de los bulbos para neo4j

Estoy construyendo una aplicación Python con uso intensivo de datos basada en neo4j y, por razones de rendimiento, necesito crear / recuperar varios nodos y relaciones durante cada transacción. ¿Hay un equivalente a la statement de SQLAlchemy session.commit() en bulbos?

Editar:

para aquellos interesados, se ha desarrollado una interfaz para los Bulbos que implementa esa función de forma nativa y, por lo demás, funciona de manera muy parecida a SQLAlchemy: https://github.com/chefjerome/graphalchemy

La forma más eficaz de ejecutar una transacción de varias partes es encapsular la transacción en un script de Gremlin y ejecutarla como una sola solicitud.

Aquí hay un ejemplo de cómo hacerlo: es de una aplicación de ejemplo que elaboré el año pasado para el Desafío Heroku de Neo4j.

El proyecto se llama Lightbulb: https://github.com/espeed/lightbulb

El README describe lo que hace …

¿Qué es Lightbulb?

Lightbulb es un motor de blog respaldado por Neo4j y alimentado por Git para Heroku escrito en Python.

Puedes escribir entradas de blog en Emacs (o tu editor de texto favorito) y usar Git para el control de versiones, sin renunciar a las características de una aplicación dinámica.

Escribe entradas de blog en ReStructuredText y dales el estilo utilizando el sistema de plantillas de tu sitio web.

Cuando empuja a Heroku, los metadatos de entrada se guardarán automáticamente en Neo4j, y el fragmento HTML generado a partir del archivo fuente ReStructuredText se servirá del disco.

Sin embargo, Neo4j dejó de ofrecer Gremlin en su prueba de Heroku Add / gratuita, por lo que Lightbulb no funcionará para los nuevos usuarios de Neo4j / Heroku.

En el próximo año, antes de que salga el libro de TinkerPop, TinkerPop lanzará un Addok de Rexster Heroku con soporte total de Gremlin para que la gente pueda ejecutar sus proyectos en Heroku mientras trabajan en el libro.

Pero por ahora, no necesita preocuparse por ejecutar la aplicación, ya que todo el código relevante está contenido en estos dos archivos: el archivo modelo de la aplicación Lightbulb y su archivo de script Gremlin:

https://github.com/espeed/lightbulb/blob/master/lightbulb/model.py https://github.com/espeed/lightbulb/blob/master/lightbulb/gremlin.groovy

model.py proporciona un ejemplo para crear modelos de Bulbos personalizados y una clase de Graph Bulbos personalizados.

gremlin.groovy contiene un script de Gremlin personalizado que ejecuta el modelo de Entry personalizado; este script de Gremlin encapsula toda la transacción de varias partes para que pueda ejecutarse como una sola solicitud.

Observe en el archivo model.py anterior, personalizo EntryProxy anulando los métodos create() y update() y en su lugar defino un método singular save() para manejar las creaciones y actualizaciones.

Para enganchar el EntryProxy personalizado en el modelo Entry , simplemente get_proxy_class método get_proxy_class del modelo Entry para que devuelva la clase EntryProxy lugar de la clase predeterminada NodeProxy .

Todo lo demás en el modelo de Entry está diseñado para construir los datos para el save_blog_entry Gremlin save_blog_entry (definido en el archivo gremlin.groovy anterior).

Observe en gremlin.groovy que el método save_blog_entry() es largo y contiene varios cierres. Podría definir cada cierre como un método independiente y ejecutarlos con múltiples llamadas de Python, pero luego tendría la sobrecarga de realizar múltiples solicitudes de servidor y, dado que las solicitudes son independientes, no habría manera de envolverlas todas en una transacción.

Al usar un solo script de Gremlin, combina todo en una sola solicitud transaccional. Esto es mucho más rápido, y es transaccional.

Puede ver cómo se ejecuta el script completo en la línea final del método Gremlin:

return transaction(save_blog_entry);

Aquí simplemente estoy envolviendo un cierre de transacción alrededor de todos los comandos en el cierre interno de save_blog_entry . Hacer un cierre de transacción mantiene el código aislado y es mucho más limpio que incrustar la lógica de transacción en los otros cierres.

Luego, si observa el código en el cierre interno de save_blog_entry , solo está llamando a los otros cierres que save_blog_entry anteriormente, usando los parámetros que pasé de Python cuando llamé al script en el modelo de Entry :

 def _save(self, _data, kwds): script = self._client.scripts.get('save_blog_entry') params = self._get_params(_data, kwds) result = self._client.gremlin(script, params).one() 

Los parámetros que paso están integrados en el método personalizado _get_parms() del modelo:

 def _get_params(self, _data, kwds): params = dict() # Get the property data, regardless of how it was entered data = build_data(_data, kwds) # Author author = data.pop('author') params['author_id'] = cache.get("username:%s" % author) # Topic Tags tags = (tag.strip() for tag in data.pop('tags').split(',')) topic_bundles = [] for topic_name in tags: #slug = slugify(topic_name) bundle = Topic(self._client).get_bundle(name=topic_name) topic_bundles.append(bundle) params['topic_bundles'] = topic_bundles # Entry # clean off any extra kwds that aren't defined as an Entry Property desired_keys = self.get_property_keys() data = extract(desired_keys, data) params['entry_bundle'] = self.get_bundle(data) return params 

_get_params() es lo que _get_params() está haciendo …

buld_data(_data, kwds) es una función definida en bulbs.element : https://github.com/espeed/bulbs/blob/master/bulbs/element.py#L959

Simplemente fusiona los argumentos en caso de que el usuario ingrese algunos como argumentos posicionales y otros como argumentos de palabras clave.

El primer parámetro que paso en _get_params() es el author , que es el nombre de usuario del autor, pero no paso el nombre de usuario a la secuencia de comandos Gremlin, paso el author_id . El author_id está guardado en la caché, así que uso el nombre de usuario para buscar el author_id y establecerlo como parámetro, que luego save_blog_entry script Gremlin save_blog_entry .

Luego creo los objetos del Model Topic para cada etiqueta de blog que se estableció, y llamo a get_bundle() en cada uno y los topic_bundles como una lista de topic_bundles en params.

El método get_bundle() se define en bulbs.model: https://github.com/espeed/bulbs/blob/master/bulbs/model.py#L363

Simplemente devuelve una tupla que contiene los data , el index_name índice y las keys índice para la instancia del modelo:

 def get_bundle(self, _data=None, **kwds): """ Returns a tuple containing the property data, index name, and index keys. :param _data: Data that was passed in via a dict. :type _data: dict :param kwds: Data that was passed in via name/value pairs. :type kwds: dict :rtype: tuple """ self._set_property_defaults() self._set_keyword_attributes(_data, kwds) data = self._get_property_data() index_name = self.get_index_name(self._client.config) keys = self.get_index_keys() return data, index_name, keys 

get_bundle() método get_bundle() a Bulbs para proporcionar una forma agradable y ordenada de agrupar params juntos para que su script Gremlin no se llene con una tonelada de argumentos en su firma.

Finalmente, para Entry , simplemente creo un entry_bundle y lo entry_bundle como entry_bundle .

Tenga en cuenta que _get_params() devuelve un dict de tres parámetros: author_id , topic_bundle y entry_bundle .

Este params dict se pasa directamente a la secuencia de comandos Gremlin:

 def _save(self, _data, kwds): script = self._client.scripts.get('save_blog_entry') params = self._get_params(_data, kwds) result = self._client.gremlin(script, params).one() self._initialize(result) 

Y la secuencia de comandos de Gremlin tiene los mismos nombres arg que los pasados ​​por params :

 def save_blog_entry(entry_bundle, author_id, topic_bundles) { // Gremlin code omitted for brevity } 

Los parámetros se utilizan simplemente en el script de Gremlin según sea necesario, sin que ocurra nada especial.

Así que ahora que he creado mi modelo personalizado y el script de Gremlin, construyo un objeto Graph personalizado que encapsula todos los proxies y los modelos respectivos:

 class Graph(Neo4jGraph): def __init__(self, config=None): super(Graph, self).__init__(config) # Node Proxies self.people = self.build_proxy(Person) self.entries = self.build_proxy(Entry) self.topics = self.build_proxy(Topic) # Relationship Proxies self.tagged = self.build_proxy(Tagged) self.author = self.build_proxy(Author) # Add our custom Gremlin-Groovy scripts scripts_file = get_file_path(__file__, "gremlin.groovy") self.scripts.update(scripts_file) 

Ahora puede importar Graph directamente desde model.py de su aplicación y crear una instancia del objeto Graph como de costumbre.

 >> from lightbulb.model import Graph >> g = Graph() >> data = dict(username='espeed',tags=['gremlin','bulbs'],docid='42',title="Test") >> g.entries.save(data) # execute transaction via Gremlin script 

¿Eso ayuda?