neo4j-contrib / neomodel

An Object Graph Mapper (OGM) for the Neo4j graph database.
https://neomodel.readthedocs.io
MIT License
963 stars 232 forks source link

Upgrading to version 3.3.0 causes a KeyError on initialising connection to neo4j DB #378

Closed jonadaly closed 5 years ago

jonadaly commented 6 years ago

We have a neo4j instance, populated with the help of neomodel v3.2.9. The v3.3.0 of neomodel breaks when attempting to initialise the connection to the database:

Traceback (most recent call last):
  File "/usr/local/lib/python3.6/site-packages/neomodel/util.py", line 150, in _object_resolution
    resolved_object = self._NODE_CLASS_REGISTRY[frozenset(a_result_attribute[1].labels)].inflate(
KeyError: frozenset({'Person'})

This appears to be as a result of the self._NODE_CLASS_REGISTRY changes included in v3.3.0, which appear to be breaking. Is this a known issue? Given that this is a minor version bump, we would not expect breaking changes to be included.

aanastasiou commented 5 years ago

Hello @jdalydt

Thanks for letting us know about this. No, it is not a known issue so far and I am sorry if it has caused a headache to you guys.

Can you please confirm if import neomodel comes before any other neomodel imports you might be doing on the file that raises the error?

This is related to the way neomodel creates a mapping between labels and native classes to be able to properly instantiate a class given a generic Node as retrieved from the database. To make sure that every call references the same neomodel "instance", import model has to be placed first.

All the best

P.S.: Can I please ask @Winawer, @mstefaniak10 and @manoadamro to confirm if they also receive a similar error and let us know if the fix that is suggested in this post fixed it?

jonadaly commented 5 years ago

Adding import neomodel at the top of the file producing the error has no effect.

The full stack trace is:

File "./lib/python3.6/site-packages/neomodel/match.py", line 564, in get_or_none
    return self.get(**kwargs)
  File "./lib/python3.6/site-packages/neomodel/match.py", line 548, in get
    result = self._get(limit=2, **kwargs)
  File "./lib/python3.6/site-packages/neomodel/match.py", line 539, in _get
    return self.query_cls(self).build_ast()._execute()
  File "./lib/python3.6/site-packages/neomodel/match.py", line 445, in _execute
    results, _ = db.cypher_query(query, self._query_params, resolve_objects=True)
  File "./lib/python3.6/site-packages/neomodel/util.py", line 32, in wrapper
    return func(self, *args, **kwargs)
  File "./lib/python3.6/site-packages/neomodel/util.py", line 202, in cypher_query
    results = self._object_resolution(results)
  File "./lib/python3.6/site-packages/neomodel/util.py", line 162, in _object_resolution
    raise ModelDefinitionMismatch(a_result_attribute[1], self._NODE_CLASS_REGISTRY)
neomodel.exceptions.ModelDefinitionMismatch: Node with labels Person does not resolve to any of the known objects
manoadamro commented 5 years ago

@aanastasiou I can also confirm that this fix had no effect.

aanastasiou commented 5 years ago

@manoadamro & @jdalydt thank you very much for such a quick response.

At this point, I do not have enough information to give you something more useful. The module passes its tests and it is also used actively in a project I am working on which has not produced any such errors. So, there is not much to work with at my end I am afraid.

Can I please ask you to create a small test case demonstrating how this fails?

In the meantime, the exception is thrown because neomodel has retrieved a Node with a subset of labels that does not match the subset of labels inferred from the (model) class hierarchy (if that is of any help).

Would like to hear more about this so we can fix it however.

All the best

manoadamro commented 5 years ago

@aanastasiou That's not going to be easy. The problem is, the database was populated using v3.2.9 and is being accessed using v3.3.0. I'm not sure that can be done with a unit test

aanastasiou commented 5 years ago

@manoadamro I understand. When I said "...small test case..." I meant something like this. That repo outlines how we got to this change really.

I have not tried accessing the database across versions as you describe, but there should not be a problem as long as the labels that are inferred from the class hierarchy match the labels of the nodes in the database.

This is why, I cannot really tell you, what might be going wrong. I will give it a try to setup "cross-version access" myself, but in the meantime, if you can isolate the problem in a small test script that would be helpful too.

manoadamro commented 5 years ago

I have started a repo, will let you know how it goes 👍

aanastasiou commented 5 years ago

Thanks for letting me know, BTW, have you run neomodel_install_labels on your database? (here is an example). Does running :schema on the Neo4J Browser returns indices and constraints matching your class hierarchy as expected? If not, then could you give that a try first please?

manoadamro commented 5 years ago

yeah we have this: neomodel_install_labels the_repo.app.py --db bolt://neo4j:neo4j@localhost:7687 as a part of our start up script

aanastasiou commented 5 years ago

@manoadamro Quick question, related to my earlier comment: Do you have anywhere in your code, something like:

from mymodels import myentity

And within that file not having imported neomodel?

If yes, can you please try this:

import neomodel
from mymodels import myentity

And let me know if that improved the situation?

manoadamro commented 5 years ago

@aanastasiou currently trying to get my head around another error at the moment.

the write_transaction decorator is failing every second time with: neo4j.exceptions.SecurityError: Failed to establish secure connection to 'EOF occurred in violation of protocol (_ssl.c:749)'

when write_transaction is removed, it works every single time.

🤷‍♂️

aanastasiou commented 5 years ago

@manoadamro Thanks for letting me know. Wish I could be more helpful but without knowing a bit more about your setup, it is difficult to comment on the exception snippets :/

manoadamro commented 5 years ago

@aanastasiou I have managed to fix one occurrence of the error with your suggested fix (not all though)

this causes unused imports and forces bad code style :(

aanastasiou commented 5 years ago

@manoadamro That is great, thank you for letting me know. I am not advocating bad code style with my suggestion but since that worked, I now have something to work with on my end and a better idea of how to go about fixing the problem.

If you come up with any specific fixes in the meantime, please let me know or go ahead with a PR.

All the best

aanastasiou commented 5 years ago

@manoadamro

Background:

  1. neomodel 3.3.0 solves a specific issue with it that allows it to instantiate classes properly in a hierarchy. Prior to this, if a class had an association with an abstract class, then upon instantiation it would instantiate a node to that abstract class, even if subsequent specialisations of that class pointed to specialised versions of the abstract class. You can read more about this here.

  2. This was solved by adding a mapping (a dictionary) in neomodel.util.Database() (tuple(node labels)=>node class) that is populated by the NodeMeta class upon a given StructuredNode definition.

The issue:

  1. The consequence of this is that if a class is not defined, it is unknown to the mapping and consequently it will raise ModelDefinitionMismatch

  2. Now, the question is, how do you get into this condition? So far, I have not been able to reproduce the problem EXACTLY as you mention. But here is the closest I got to:

    • Suppose that we have a project with modules neomodel, A, B, C, D where A,B,C,D are purely various specialisations of StructuredNode (i.e. an application's models).
      • A depends on B
      • C depends on A
      • D depends on A
  3. From the point of view of Node inheritance, it is impossible for a node class to be imported and not end up in the neomodel mapping.

    • The reason why, here, is trivial. Any import, imports the whole module and depending on its form will choose to import a particular symbol to the calling namespace. So doing from D import AClassDefinedInD, imports D (which will import A which imports B) and subsequently AClassDefinedInD to the local namespace.
    • Therefore upon doing this, the mapping is populated with the full lineage of the class and it can instantiate it properly.
  4. BUT, Nodes can maintain relationships with other nodes. In this case, from the point of view of referential integrity we need to know:

    • The data types of the relationship's end points, the direction of the relationship and the multiplicity (one-to-one, one-to-many, etc)
    • The properties of the relationship.
  5. And here is where the issue is because neomodel (currently) does not follow the relationships of a class to check if it might attempt to load a class that has not yet been defined. Therefore, if AClassDefinedInD contains some some_aclass_attribute = neomodel.Relationship("C.ModelWhatever", "REL_TO", model=AClassToWhatever), the C module will not have been imported when importing D which will cause a runtime error if the property is traversed.

    • Notice here, if you did not have to traverse the property, no error will be raised because neomodel is not going to try to instantiate the object on the other side of the relationship which is the one whose definition is not yet in the mapping.
    • This was partly why it was a bit difficult to reproduce the error with trivial attempts.

Temporary Fix:

Now, is the solution for neomodel to actually try and traverse a relationship and automate the loading? (maybe that's too slow (?)). Shall we make the type parameter of the Relationship an actual data type rather than a string? (Maybe that's a bit too constraining (?)).

I have not thought about this yet, maybe the fix ends up being a combination of the two.

As a temporary solution however, I would suggest that you examine your Relationship definitions within a particular module and make sure that you import the classes that might be referenced within that module. Yes, this means that you might end up importing classes that are not used within the module, but this is a temporary fix so that you continue using 3.3.0 which is better at handling inheritance (and also includes a number of other very useful contributions).

Can I please ask you to confirm if this fixes the issue for you?

manoadamro commented 5 years ago

@aanastasiou Thanks, I really appreciate your efforts here, I will try what you suggested here and get back to you. 👍

robinedwards commented 5 years ago

Hey @aanastasiou thanks for looking into this. From my recollection the full module path gets stored in the relationship definition. we just need to check if that exists in sys.modules prior to loading and if its missing import it.

aanastasiou commented 5 years ago

Hi @robinedwards, I have not looked much into resolving it yet but thanks for pointing this out, it really helps.

All the best

aanastasiou commented 5 years ago

@manoadamro Quick note as I am going through this to automate the loading of a potential class:

The relationship's end-point model can well be a class.

Which then does not lead to any unused imports. We can make this clearer in the documentation.

theterg commented 5 years ago

I am also running into this issue in a different context which may be of use. I'm attempting to use neomodel from a django environment via django-neomodel. In this environment I have even less control over the ordering of import statements or the threading context in which neomodel is imported. I'm not very well versed on django internals, but it looks as though the global db object is re-instantiated many times during the lifecycle of a django process.

During django startup I see the db object being recreated and my models successfully added to _NODE_CLASS_REGISTRY. However during a webrequest to a View object, I see the db object have been re-instantiated immediately before servicing the request, and my model never gets added back to the _NODE_CLASS_REGISTRY.

It's worth mentioning that neomodel works perfectly fine when invoked from a normal django shell python manage.py shell. I may try to dig a little deepr, but thought i'd give you a heads-up. Maybe a global db object is difficult to manage in this context?

EDIT: I can confirm that reverting to 3.2.9 solves the problem.

aanastasiou commented 5 years ago

Hey, thanks a lot for contributing your experience, this is very useful. Please see below:

I'm not very well versed on django internals, but it looks as though the global db object is re-instantiated many times during the lifecycle of a django process.

That should not be a problem because neomodel.util.Database inherits from threading.local. So, anything that happens within the same "script" will still use the same neomodel.db object.

during a webrequest to a View object, I see the db object have been re-instantiated immediately before servicing the request, and my model never gets added back to the _NODE_CLASS_REGISTRY.

This is a problem. The short term answer to that is to make sure that all the models that the view is likely to use have been imported to the view (please see earlier discussion in this thread). We are working on a better solution for that.

Maybe a global db object is difficult to manage in this context?

That might be the case but I do not feel that we have enough data to decide categorically on that yet. For example, it is not necessary for the whole model to be loaded to the registry for the resolution to work. Only the part that will be required is necessary. And this does not require a global context.

I can confirm that reverting to 3.2.9 solves the problem.

I have not worked very much with neomodel AND Django, but there was a reason for the 3.3.0 change and while challenging, I think that it is worth working towards a better inheritance "compliance".

All the best

theterg commented 5 years ago

Thank you for the quick reply and apologies if I came across as critical of your efforts. It is true that I have a small fear and skepticism towards a global db instance being hidden inside of a module at import time. But it's far outweighed by the simplicity and ease of use of the neomodel interface. It was incredibly easy to start using, and the model definitions so far seem super concise and lightweight. I'm grateful for this!

aanastasiou commented 5 years ago

@theterg Hi, no need for apologies, don't worry about it.

One thing we have not done about this issue is to create a script that replicates the problematic behaviour. At the moment, I am working along the lines of this and this is why I said that I do not have experience in using neomodel with Django.

Can I please ask you the following:

  1. Can you please see previous recommendation regarding importing all of the classes that potentially might be needed by your View and let us know if that solves the problem? Please note that it is not so much the Node classes themselves but also the classes of Node endpoints in Relationships. The ones that are usually passed as strings. (or in other words, cls_name here). So, if you have any relationships that currently define that model as string, please make sure that you import the class they refer to. Once you do that, the registry will be updated with the labels of the class and that should be enough to stop the runtime exception from being raised. We are currently looking into doing this automatically upon loading a relationship.

  2. If 1 does not work for you, can you please create a minimal Django project that demonstrates the failure when neomodel is used in conjunction with Django? This should give us more information for the particular Django use case.

All the best

aanastasiou commented 5 years ago

Update: The script that is mentioned in this stack overflow post, works in my development environment without raising the exception. (at least the GET part)

Neo4j-community edition 3.4.9, neomodel 3.3.0, Flask 1.0.2, Python 3.5.2. The only modification that I did was to add:

if __name__ == "__main__":
    create_app().run()  

...at the end of the script and run it with python main.py.

Update 2: No it doesn't :/

The script fails when the application really tries to access the database and THAT is how the problem reveals itself because the neomodel.db during the Flask() instantiation is totally different than the neomodel.db that is created to serve the GET request.

I am not very familiar with the internals of Flask but this could be resolved by somehow preserving the neomodel.db that is created in create_app() (from the example above).

Please let me know if any of you think otherwise but I think that this should be mentioned explicitly in the documentation. Essentially, prior to 3.3.0 neomodel.db did not have state other than whether it was connected to the database or not. But to resolve the objects properly, the "state" (as in, what is in _NODE_CLASS_REGISTRY) needs to be preserved between calls. I think that this is normal and acceptable considering the kind of functionality that is delivered but it should be mentioned explicitly in the documentation.

mzgajner commented 5 years ago

Chiming in from the other issue - I've had similar problems in Flask, notably the db object being reinstantiated on each request and consequently models missing from _NODE_CLASS_REGISTRY.

I'm generally a JavaScript dev, so pardon if I'm kicking in the dark, but in some of the official neo4j app examples, I've noticed they explicitly keep references to the database connection on the g object in Flask - could this be achieved manually in neomodel or would it ruin the abstractions?

aanastasiou commented 5 years ago

@mzgajner Please see the update to my earlier post. I am not this familiar with Flask to suggest if saving neomodel.db in the g attribute (?) would solve the issue but the key thing to remember here is that state needs to be preserved between calls to neomodel.db. Can I please ask you to try and save the neomodel.db (in your own setting) and let us know if that solved the issue? Again, this would be temporary until we provide a definitive solution to this.

It is very important to collect all of these failure modes and scenarios so that we address them appropriately in an upcoming release. And from this point of view, this Flask issue was very helpful.

All the best

mzgajner commented 5 years ago

Will try this evening and report back

mostafa commented 5 years ago

@aanastasiou @mzgajner The exact reason is that db._NODE_CLASS_REGISTRY is always empty inside the request body, so the solution would be to save the db instance inside the g object, which makes it visible to all requests, but may solve the issue. I wonder if it helps or not, because the db object is never used independently. Also the model.inherited_labels() always returns the exact class name. But since there is no mapping inside the _NODE_CLASS_REGISTRY, it gets a mismatch exception, I think!

aanastasiou commented 5 years ago

@mostafa Thank you very much for contributing to this. Yes, this is what happens but this is not because of an extra initialisation. It is not the case that a neomodel.db was created at some point within the program and then later on the Database() constructor was called again and wiped out everything.

What is hapenning is that Flask's routing of GET to the function that will serve it skips the definition of Item entirely because from the whole script, it is only that specific function that is executed, anything else is "lost".

If you change get_all_items to:

    @app.route('/', methods=['GET'])
    def get_all_items():
        app_db = neomodel.db.set_connection('bolt://someone:whatever@wherever:7687')
        from the_model import Item
        return jsonify({'items': [item.name for item in Item.nodes]})

Where the_model.py contains:

import neomodel

class Item(neomodel.StructuredNode):
    __primarykey__ = "name"
    name = neomodel.StringProperty(unique_index=True)

The exception is not raised, but in this case, everything gets re-initialised in every request.

mostafa commented 5 years ago

This kind of re-initialization has such a big performance penalty!

P.S. I've tested the g object and it seems that it doesn't work either!

aanastasiou commented 5 years ago

@mostafa I agree. I can see that for myself too. If you notice further above, I am trying to give temporary solutions to problems that users face until we can come up with a better solution. I don't think that this comes across very efficiently, so I am going to have to try harder :)

Thanks for letting us know that the g option appears to be a deadend.

aanastasiou commented 5 years ago

@mostafa, @mzgajner: Just a note, if you just saved the neomodel.db object to g, that is not going to do anything by itself. You also need to substitute the request's neomodel.db with the saved one prior to serving the request.

The reason for this is this: Ultimately, everything (every operation towards the database, irrespectively of which entity initiated it), goes via neomodel.Database.cypher_query. Consequently, if you save some neomodel.db at some point in time, you have to restore it, prior to serving the request, otherwise, you run into exactly the same issue we are having here: The neomodel.db object that is used to access the database is not the one that was created during the initialisation of app.

Also, please note this.

( @manoadamro Please note, this might apply to your case as well. )

mjmare commented 5 years ago

Let me chime in here. I have the exact same problem in a Pyramid app. On app init everything is well, labels etc are installed. db._NODE_CLASS_REGISTRY is populated etc. However, in a view, db._NODE_CLASS_REGISTRY is empty and obviously I get a neomodel.exceptions.ModelDefinitionMismatch exception. Using

import neomodel
from blah import MyModel 

does not solve the problem. neomodel.config.DATABASE_URL is still correct in the view.

Downgrading to neomodel 3.2.9 "fixes" the problem.

aanastasiou commented 5 years ago

@mjmare, @jdalydt Thank you for letting us know. We have a plan for a solution that will improve the way neomodel loads the data model it offers mappings for. This will catch some corner cases that cause problems in single thread applications.

However, WSGI applications are a totally different matter and it is likely that a solution would have to be worked out for each.

If it is of any use, the experience from Flask is as follows:

  1. During the setting up of the application, a neomodel object is instantiated appropriately and fully with absolutely no problems.

  2. The WSGI server however will spawn a new process for every request. The neomodel object that is instantiated within THAT process has no "knowledge" of the instantiation that has already taken place and this is why the exception is raised.

This second issue can be dealt with by preserving the neomodel.db object that was instantiated during the creation of the application and restoring it prior to handling each subsequent request. That is, find a way to preserve application wide variables. This is best handled by each web framework separately because it might even have security repercussions.

These are also difficulties that we face and considerations that we make prior to suggesting the next step.

Downgrading to 3.2.9 is a solution if your data model does not use generalisation to offer alternatives. If it does, you will discover that what is instantiated from the database is the abstract rather than the concrete class you would expect. (And of course, 3.3.0 also brought with it a few more properties, Q queries and other changes).

If you guys can pitch in ideas, scripts that demonstrate relevant problems, specific solutions that could work best with a particular webapp framework, that would be very helpful in dealing with this problem.

All the best

mjmare commented 5 years ago

Can you indicate exactly what should be done (saved/restored/...) at what moment? Currently it is not clear to me what happens at creation time since it all happens automagically in the background. That's what happens with global state I guess. Maybe provide some pseudo code?

aanastasiou commented 5 years ago

@mjmare Would this be any help at this point? (Start at main_naive.py). This is specifically about Flask but I suppose that a similar sequence of events leads to an exception in other WSGI frameworks as well.

mjmare commented 5 years ago

OMG that's ugly. What I would like to know: why is this working? What would be the minimum required to make it work? In order to abstract it away into some reusable component that would be essential knowledge.

aanastasiou commented 5 years ago

@mjmare I appreciate your interest but I am not sure I understand what you are trying to do, please see below:

why is this working?

Why is what "working"? This is how webapps are working (that's one thing) and at the same time, neomodel needs to preserve state in order to work properly (that is another thing).

What would be the minimum required to make it work?

As stated in previous posts in this discussion: Save the "state" of the neomodel.db object, either by preserving neomodel.db as a whole, or the _NODE_CLASS_REGISTRY which is used to resolve labels to classes properly.

In order to abstract it away into some reusable component that would be essential knowledge.

This is the bit I do not understand. As far as I can tell, this save / restore the state of the neomodel.db object might have to be handled differently for each webapp framework because of their differences in structure. Whether that is a Flask plugin, or a pylons plugin or a Django plugin or whatever...The "essential knowledge" is the preservation of neomodel.db "state" :)

mjmare commented 5 years ago

@aanastasiou I wanted to know exactly what should be undertaken to fix the problem so I could see how it could be implented in Pyramid. That's clearer now.

What I don't understand why the problem exists anyway. Global variables should not be a problem in Pyramid: https://stackoverflow.com/questions/10906477/in-pyramid-is-it-safe-to-have-a-python-global-variable-that-stores-the-db-conne

Also: I checked the id of neomodel.db (which is more or less the memory address). The id is the same at startup (ie just after model creation) and in the view. So I'm not convinced by the WSGI argument. I do not how to diagnose the actual cause, though. Sorry.

aanastasiou commented 5 years ago

@mjmare Well, that is kind of useful for troubleshooting this error in Pyramid specifically but at the moment, I am a bit caught up with certain things that have to be done in the main project I am working on.

Are you absolutely sure that the object you are looking at is the one that is used to query the database? The fact that a copy of neomodel.db is saved at some point within the application does not mean that it is the one that is used during a request. If Pyramid has something like on_request or before_request or any other function that is guaranteed to execute before every request then you might want to try something like neomodel.db = saved_neomodel_db.

As to the "actual cause", if you do get a ModelMismatch exception, then this is because the labels of the node you are trying to isntantiate are not known to neomodel yet. This can be caused by two things:

  1. The classes you will be instantiating to have not been imported
  2. Between applying the labels to the database and querying it, you have changed the definition of the models.

The most common reason is the first one however. Give it a try and let me know if that worked for you.

mjmare commented 5 years ago

@aanastasiou Just to be clear: I have not yet attempted to "fix" the problem in Pyramid. I just put some breakpoints at program startup (after install labels etc) and in the view. With my limited knowledge I'd say the objects are identical, given that their id() (~memory address) is the same.

aanastasiou commented 5 years ago

@mjmare Thank you for letting me know. I am dealing with a different project at the moment and will come back to implement some changes that have been planned for neomodel soon. In the meantime, if you think that you can provide some insight from Pyramid's point of view, that would definitely be welcome.

All the best

mjmare commented 5 years ago

Hi @aanastasiou I did some more spelunking. In pyramid I tried several approaches to save and restore the neomodel.db. They failed. Well, technically they worked but the db is still empty ie an empty Database() object in the view. Other test objects were OK. I also found out, by setting breakpoints, that the models' definition code is only run once.

Then I noticed in neomodel.util that Database is defined as:

class Database(thread.local):

Doesn't that mean that it is to be expected/intended for the class to be newly created for each request? IOW db._CLASS_REGISTRY will always be empty except in the thread that runs the program startup code. It is not a singleton!

I changed the code to

class Database():

and everything works again! Don't know whether this messes up something else, but for now I'm happy.

aanastasiou commented 5 years ago

@mjmare Thank you very much, that is helpful. What this would mess up is instances where multiple threads in the same process attempt to connect to the database which at the moment is handled by re-using the same transaction. I would consider this as a further enhancement where this behaviour could start an entirely separate connection to the Neo4J database but I am not sure it solves the original problem (I am also away from any means of running a couple of tests about this). Will have a look and get back to you about it. As far as the "singleton" qualifier is concerned, that is true within the context of a single process, it would be good to clarify this further in the documentation.

Doesn't that mean that it is to be expected/intended for the class to be newly created for each request?

In the context of WSGI applications, yes. But this is a byproduct of WSGI.

mjmare commented 5 years ago

neomodel.db contains connection info and the model registry and therefore is IMHO a singleton (global to the application). This is consistent with the old neomodel behaviour. The database connection should (considered to be) recreated for each request (for this reason the neo4j driver maintains a connection pool). I'm not familiar enough with the technical implications of WSGI to say anything about any sideeffects. The user should not be bothered by implementation details, don't you think?

MardanovTimur commented 5 years ago

@aanastasiou this situation is bad with Node_registry. 3.3.0 - need to be recover

phoebebright commented 5 years ago

Also having this problem in django when upgrading to 3.3.0 and import neomodel does not solve the problem.

My models are all in one place and have relationships between each other. Am happy to test any fixes but for me 3.2.9 is ok for now.

aanastasiou commented 5 years ago

@phoebebright, I understand. I am glad to hear you would be happy to help. @mjmare has done a bit of poking around with Pyramid (please see earlier comments here), if you can do something similar with Django, that would be great.

@mjmare, the Database object was always a descendant of the thread local object. This is done for very good reasons, given the design of Neomodel.

If an application does not need Generalisation to offer "alternatives" within a data model, then 3.2.9 will work out of the box. Otherwise, 3.3.0 is required. Once you are with 3.3.0, things work as long as the code that uses Neomodel instantiates and uses a data model within the same thread.

Otherwise, a re-initialisation is required. This, on its own is not a problem, but within the context of a web application it inserts extra delays.

The current "solution" is to ensure that a data model's essential classes are loaded prior to accessing the database and this is what we have scheduled for implementation.

All the best

phoebebright commented 5 years ago

Put a print statement at the top of the the neo models file and found it was loading twice due to inconsistent import statements. Fixed this problem so now it is only loading once but the same problem with 3.3.0 persists.

robertlagrant commented 5 years ago

neomodel.db contains connection info and the model registry and therefore is IMHO a singleton (global to the application). This is consistent with the old neomodel behaviour. The database connection should (considered to be) recreated for each request (for this reason the neo4j driver maintains a connection pool). I'm not familiar enough with the technical implications of WSGI to say anything about any sideeffects. The user should not be bothered by implementation details, don't you think?

We're seeing neomodel not reconnecting to a new leader in a neo4j causal cluster; could this be related to that?