-
Notifications
You must be signed in to change notification settings - Fork 29
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
ENH: Containers via entrypoints #126
Conversation
ENH: Allow containers via entrypoint happi.containers.
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.
Exciting to see this happening... a few thoughts below
happi/containers.py
Outdated
# Avoid happi internal classes due to imports | ||
and not var.__module__.startswith('happi.')]) | ||
|
||
locals().update({c.__name__: c for c in registry}) |
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.
Indentation right here (seems like this should happen at the end after the registry is built)?
I think you're trying to get happi.containers.<classname>
working: globals()
seems more correct but may do the same here. We're also risking name collisions in the namespace as this strips off the __module__
prefix, hmm...
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.
Indentation solved with 5c39ffb. Sorry that I forgot to push before opening the PR.
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.
About the globals()... I'm not sure.
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.
ok, now I understand... that is correct, we inject the class name into the happi.containers
namespace it would fail if we have 2 containers with the same name but different full qualified name.
happi/containers.py
Outdated
|
||
_entries = entrypoints.get_group_all('happi.containers') | ||
|
||
registry = set() |
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 wonder if the registry should be keyed as follows: registry['(module).(classname)'] = obj
?
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.
What about the name
from the entry_points? This way we can make it uniform to always be the package that originated it and we guarantee unique names per package?
E.g.:
pcdsdevices.Acromag
as the container and key at registry.
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.
Hmm, maybe... but what about multi-level imports?
Would (e.g.) pcdsdevices.motors.Acromag
become pcdsdevices.Acromag
?
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.
The second one will be an internal key for Happi at the registry
and it will point to the proper class pcdsdevices.happi.containers.Acromag
and queried. I decided to make it shorter as a convenience for users when selecting a class at the CLI but we can also make the namespace shorter at the pcdsdevices
side and improve the display of the possibilities at the CLI.
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.
In our example, the name you'd put in a happi entry to get the Acromag
instantiated would be pcdsdevices.Acromag
, right?
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.
That depends but I can see the confusion. Would it be okay if I make the namespace be pcdsdevices.happi.Acromag
and I switch to use the full class at the registry like you suggested before?
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 trying to get at the heart of the problem and don't really know the "right" answer just yet to say yes or no...
full_module.classname
is unambiguous. Entry_points name
gives more flexibility, but adds indirection to get to the class itself.
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.
If we populate the registry with both entry_point_name.classname
and module.classname
, I can imagine that'd get confusing as well. I think it has to be one or the other.
The entrypoint name would allow for us to move around classes and refactor codebases without updating our device databases. Maybe that's preferable?
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 of now we have:
In [1]: import happi
Questionnaire backend unavailable: No module named 'psdm_qs_cli'
In [2]: happi.containers.registry
Out[2]:
{'pcdsdevices.Acromag': pcdsdevices.happi.containers.Acromag,
'pcdsdevices.AreaDetector': pcdsdevices.happi.containers.AreaDetector,
'pcdsdevices.Attenuator': pcdsdevices.happi.containers.Attenuator,
'pcdsdevices.BeamControl': pcdsdevices.happi.containers.BeamControl,
'pcdsdevices.Diagnostic': pcdsdevices.happi.containers.Diagnostic,
'pcdsdevices.GateValve': pcdsdevices.happi.containers.GateValve,
'pcdsdevices.IPM': pcdsdevices.happi.containers.IPM,
'pcdsdevices.LCLSItem': pcdsdevices.happi.containers.LCLSItem,
'pcdsdevices.LODCM': pcdsdevices.happi.containers.LODCM,
'pcdsdevices.Motor': pcdsdevices.happi.containers.Motor,
'pcdsdevices.MovableStand': pcdsdevices.happi.containers.MovableStand,
'pcdsdevices.OffsetMirror': pcdsdevices.happi.containers.OffsetMirror,
'pcdsdevices.PIM': pcdsdevices.happi.containers.PIM,
'pcdsdevices.PulsePicker': pcdsdevices.happi.containers.PulsePicker,
'pcdsdevices.Slits': pcdsdevices.happi.containers.Slits,
'pcdsdevices.Stopper': pcdsdevices.happi.containers.Stopper,
'pcdsdevices.Trigger': pcdsdevices.happi.containers.Trigger,
'pcdsdevices.Vacuum': pcdsdevices.happi.containers.Vacuum}
So technically we are already doing that, right? Since you have the container class as the value of the dict you can get the module.classname
?
I agree, the entrypoint will allow for flexibility on this sense
FIX: Switch client to use registry instead of search at happi.containers. FIX: Address issues found during tests of the cli.
I need to fix the tests and I am finding some more issues... it will take a while, folks! 😞 |
happi/containers.py
Outdated
for _, var in inspect.getmembers(obj, inspect.isclass) | ||
if issubclass(var, HappiItem) | ||
# Avoid happi internal classes due to imports | ||
and not var.__module__.startswith('happi.')}) |
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.
and not var.__module__.startswith('happi.')}) | |
and not var.__module__.startswith('happi.')} and var not in registry.values()) |
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.
Also this check of "can we add it to the registry" is a repeated condition that could be made a function: i.e., it's duplicated on line 20 and in this dict comprehension
happi/containers.py
Outdated
|
||
_entries = entrypoints.get_group_all('happi.containers') | ||
|
||
registry = {} |
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.
Let's move the registry
generation code inside a function, otherwise things like name
, entry
, obj
, etc may leak such that from happi.containers import entry
would work.
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.
Will address that.
happi/containers.py
Outdated
continue | ||
if inspect.isclass(obj) and issubclass(obj, HappiItem): | ||
registry[f"{name}.{obj.__name__}"] = obj | ||
elif inspect.ismodule(obj): |
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.
To re-cap we have 2 options now:
- Specify a class
- Specify a (sub-)module to find classes in
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.
Correct! I ended up adding support for both.
Sorry for the delay... I reworked the registry after talking with Ken. Most likely the changes here will break the existing Registry keys are now in the following format:
This change will allow us to move/rename packages and still be able to find the entries with no changes to the database. What is not done:
|
Codecov Report
@@ Coverage Diff @@
## master #126 +/- ##
==========================================
- Coverage 75.57% 72.58% -2.99%
==========================================
Files 15 15
Lines 1134 1120 -14
==========================================
- Hits 857 813 -44
- Misses 277 307 +30
Continue to review full report at Codecov.
|
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 think this looks really good!
f"and class: {klass}") | ||
if klass in self._reverse_registry: | ||
dup_key = self._reverse_registry.get(klass) | ||
raise RuntimeError(f"Duplicated entry found. Keys: {key} " |
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.
Nice and helpful 👍
(Nudging @ZLLentz) |
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.
LGTM. Was confused about including pcdsdevices
in the tests, but it makes sense.
@ZLLentz I would gladly nuke that test out but it tests the Questionnaire backend and that directly depends on pcdsdevices to test the type of devices. Sorry about the confusion. |
@hhslepicka that backend should eventually migrate to a different repo... Surely there's a questionnaire-centered repo somewhere? But it's not an issue so there is no reason to prioritize it |
Description
As of now in order to add a new container we need to modify Happi source code and add it at the
happi.containers
module so it is found by the client.Via discussion at #106 and many talks with @ZLLentz and @klauer we decided to experiment with the usage of entry_points.
The LCLS specific containers and item were moved to
pcdsdevices
under the following PR: pcdshub/pcdsdevices#402Motivation and Context
Merging this will contribute to close #106
How Has This Been Tested?
Tested locally
Where Has This Been Documented?
Still need to be done. Will work on that after code comments
Screenshots (if appropriate):