-
Notifications
You must be signed in to change notification settings - Fork 234
Extract Model classes out of ORM entity classes
#7093
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #7093 +/- ##
===========================================
- Coverage 77.61% 24.35% -53.25%
===========================================
Files 566 566
Lines 43546 43720 +174
===========================================
- Hits 33794 10645 -23149
- Misses 9752 33075 +23323 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
ada7f96 to
0cc3e45
Compare
|
@danielhollas I'm trying to make this one work, but it seems to require a lot of typing tweaks. Anyhow, it would be easier to understand for a reviewer if and when #6990 is merged. For now, if possible, can you make sense of why the UpdateI force-pushed the PR onto a single commit. Check out the commit to see changes in this PR. |
d7c200e to
44b70b5
Compare
src/aiida/common/typing.py
Outdated
| if sys.version_info >= (3, 10): | ||
| from typing import TypeAlias | ||
| else: | ||
| from typing_extensions import TypeAlias |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@danielhollas you do this in #7036 in age_entities.py. Moving it here for consistency.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've opened #7096 as a better solution for this. I don't think it makes sense to put these common types in our own module.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well, it makes sense if the try...except dance (as you call it) is required, to avoid excessive dancing 😅 I think in #7096 your claim is that the dance itself is not required.
src/aiida/orm/authinfos.py
Outdated
| self._backend.authinfos.delete(pk) | ||
|
|
||
|
|
||
| class AuthInfoModel(entities.Entity.Model): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As mentioned in the PR description, I provided the Model attribute (see below) to allow retaining Entity.Model access. However, this required me to define it as a TypeAlias (which is recommended by PEP). But it is unclear to me if this is correct, or rather if this helps with typing. More on this in entities.py.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Which PEP do you mean?
EDIT: In this specific files, if I remove the TypeAlias annotation it still type-checks. So I don't think it's needed here?
(also it seems very weird for it to be TypeAlias, it's a (generic) class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, not PEP, but mypy - see https://mypy.readthedocs.io/en/stable/common_issues.html#variables-vs-type-aliases. However, this is not quite correct. The recommendation is that the class variable Model should be explicitly typed as a TypeAlias, as compared to global variables. In any case, I don't care for this solution in general. I don't think TypeAlias is the correct approach, but I'm uncertain about all of this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure if it is good to inherit from a class property reference. However, I encounter strange behavior from pydantic when I expose the model classes directly. I'll paste the error here shortly.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right, so after switching from Entity.Model to EntityModel, etc. in the inheritance chain Entity -> Node -> Data -> AbstractCode -> InstalledCode, I get the following mypy error
(aiida-dev) (refactor-models) aiida-core > mypy src/aiida/orm/nodes/data/code/
src/aiida/orm/nodes/data/code/installed.py:38: error: INTERNAL ERROR -- Please try using mypy master on GitHub:
https://mypy.readthedocs.io/en/stable/common_issues.html#using-a-development-mypy-build
Please report a bug at https://github.com/python/mypy/issues
version: 1.18.2
Traceback (most recent call last):
File "mypy/semanal.py", line 7350, in accept
File "mypy/nodes.py", line 1440, in accept
File "mypy/semanal.py", line 1775, in visit_class_def
File "mypy/semanal.py", line 1991, in analyze_class
File "mypy/semanal.py", line 2038, in analyze_class_body_common
File "mypy/semanal.py", line 2123, in apply_class_plugin_hooks
File "/home/edanb/miniforge3/envs/aiida-dev/lib/python3.10/site-packages/pydantic/mypy.py", line 184, in _pydantic_model_class_maker_callback
return transformer.transform()
File "/home/edanb/miniforge3/envs/aiida-dev/lib/python3.10/site-packages/pydantic/mypy.py", line 518, in transform
fields, class_vars = self.collect_fields_and_class_vars(config, is_root_model)
File "/home/edanb/miniforge3/envs/aiida-dev/lib/python3.10/site-packages/pydantic/mypy.py", line 682, in collect_fields_and_class_vars
self._api.fail(
File "mypy/semanal.py", line 7325, in fail
AttributeError: attribute 'column' of 'Context' undefined
src/aiida/orm/nodes/data/code/installed.py:38: : note: use --pdb to drop into pdb
I have not yet attempted further debugging.
Notice that this is an apparent issue of the pydantic mypy plugin, which you recently introduced in #7064. Indeed, disabling the plugin removes the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oof, we're getting into some dark magic territory. Maybe if you managed to get a minimal reproducer you could open a bug report against pydantic? Internal error seems like something that's not your fault.
(I don't really care about the plugin, I hoped it would be helpful, but we can remove it if it would conflict with this PR)
Btw: this discussion seems relevant
python/typing#1424
Edit: also this?
python/mypy#11538 (comment)
src/aiida/orm/entities.py
Outdated
| class Entity(abc.ABC, Generic[BackendEntityType, EntityCollectionType], metaclass=EntityFieldMeta): | ||
| """An AiiDA entity""" | ||
|
|
||
| Model: TypeAlias = EntityModel |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As promised, more on EntityModelType...
Here I initially tried to add an EntityModelType to the Generic list on Entity and wire it throughout Entity, for example, Model: type[EntityModelType] = EntityModel. But this did not yield the desired outcome. Again, it may just be due to a misunderstanding of typing on my part. @danielhollas great if we can discuss this.
90d9c1b to
4a87230
Compare
| except IndexError: | ||
| raise ValueError( | ||
| 'To understand if it is a metal or insulator, ' 'need more bands than n_band=number_electrons' | ||
| 'To understand if it is a metal or insulator, need more bands than n_band=number_electrons' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unrelated formatting (a few more later)
|
|
||
| @classmethod | ||
| def from_model(cls, model: Model) -> Self: # type: ignore[override] | ||
| def from_model(cls, model: NodeModel) -> Self: # type: ignore[override] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unclear why mypy complains. NodeModel is a subclass of EntityModel. But I imagine here I'm misunderstanding typing/subtyping, etc. @danielhollas what do you think?
043db37 to
b0e1a22
Compare
I'll have a look once I am back but no promises as this seem to touch on the generics / variance topics which are still very murky to me.
You can push the branch from #6990 to the origin repo, and then make it a target for this PR.
The job fails at the setup step, not during the tests. Have you looked into it? The error seems to suggest something is broken here, but I don't have time to dig deeper (still on holidays 😅) |
It seems to fail at |
8a13ea3 to
f1068b5
Compare
f1068b5 to
3144d5a
Compare
cd0f29f to
42230ac
Compare
42230ac to
1a2c038
Compare
This PR extracts the nested ORM entity models out to the module level for consistency. It builds on the work of #6255 and #6990. Similar to what was done in #5183 for collections.
The of this PR is as follows (referencing the example below):
Modelclass attribute as a type alias for the module-level model2 ensures that much of the existing model mechanics are retained while avoiding the nested model.
Before
After
Why do this?
I found it difficult type hinting for the nested model system. I had hoped that providing the model at the module level will resolve this matter, for example, by allowing the definition of
EntityModelType = TypeVar('EntityModelType', bound=EntityModel), instead of binding it toEntity.Model, which seemed to not work withmypy.It ended up being a bit of a typing hell. However, I am uncertain if it is due to the design or a misunderstanding of typing on my part. Help welcomed @danielhollas 🙏