-
Notifications
You must be signed in to change notification settings - Fork 8
Context-dependent Descriptors + Electrolyte Rendering #81
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
Merged
Merged
Changes from all commits
Commits
Show all changes
25 commits
Select commit
Hold shift + click to select a range
a200587
A demo of adapting descriptors depending on the context
saraedum 54f9075
Split templates into full pages and smaller snippets
saraedum 3b5018c
Speed up doctesting
saraedum 01b50c1
Allow recursive lookups in descriptors that are lists
saraedum 180d8b8
Rewrite M as astropy compatible unit mol / l
saraedum e4c939e
Document new descriptor features
saraedum de69d4f
Remove unused imports
saraedum 71cb2d8
Standardize macro/filter provisioning
saraedum decc111
Implement render filter
saraedum 7c9e9cc
Implement render macro
saraedum 9306238
Fix warning in code
saraedum bd312c7
Implement electrolyte rendering
saraedum a093535
Merge remote-tracking branch 'origin/main' into unit-value
saraedum fd94485
Replace units not understood by astropy
saraedum e3b2d2d
Merge branch 'main' into unit-value
saraedum 7281a0a
Assert that digitizer creates output
saraedum 92c17b5
Add debug information when entry.create_example() fails
saraedum c10aac6
Add tmate for debugging
saraedum 7547df2
Revert "Add tmate for debugging"
saraedum da98bab
Add lock file to avoid race condition when testing in parallel
saraedum e96fbe9
Fix typo
saraedum ac3ddaf
Merge branch 'main' into unit-value
saraedum 73470c0
Fix sphinx syntax
saraedum 81c858a
Enable tmate for debugging temporarily
saraedum fb965ae
Keep site-packages writable when installing echemdb
saraedum File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,249 @@ | ||
| r""" | ||
| Wrappers for a Data Package's metadata stored in the descriptor property. | ||
|
|
||
| These wrappers are automatically applied to all metadata of each :class:`Entry` | ||
| in our :class:`Database`. | ||
|
|
||
| Metadata in data packages is stored as a JSON object. In Python, such a nested | ||
| JSON object gets turned into a hierarchy of dictionaries and lists. Such raw | ||
| data structures can be a bit tedious to work with, so the descriptors in this | ||
| module provide some convenience wrappers for it, e.g., better tab-completion | ||
| when working in an interactive session. | ||
|
|
||
| EXAMPLES: | ||
|
|
||
| To add convenience methods to a data package's descriptor, run it through the | ||
| :func:`Descriptor` factory function:: | ||
|
|
||
| >>> descriptor = {'some': {'nested': 'metadata'}} | ||
| >>> descriptor = Descriptor(descriptor) | ||
|
|
||
| Such a descriptor has some added convenience methods that make it more | ||
| convenient to work with. | ||
|
|
||
| For example, it can be easily dumped to YAML:: | ||
|
|
||
| >>> print(descriptor.yaml) | ||
| some: | ||
| nested: metadata | ||
|
|
||
| It can be explored with attributes that are more tab-completion friendly:: | ||
|
|
||
| >>> descriptor.some.nested | ||
| 'metadata' | ||
|
|
||
| Extra methods are added if the descriptor satisfies a certain interface:: | ||
|
|
||
| >>> descriptor = {'some': {'nested': {'value': 13.37, 'unit': 'parsec'}}} | ||
| >>> descriptor = Descriptor(descriptor) | ||
| >>> descriptor.some.nested.quantity.to('km') | ||
| <Quantity 4.12555093e+14 km> | ||
|
|
||
| """ | ||
| # ******************************************************************** | ||
| # This file is part of echemdb. | ||
| # | ||
| # Copyright (C) 2021 Albert Engstfeld | ||
| # Copyright (C) 2021 Johannes Hermann | ||
| # Copyright (C) 2021 Julian Rüth | ||
| # Copyright (C) 2021 Nicolas Hörmann | ||
| # | ||
| # echemdb is free software: you can redistribute it and/or modify | ||
| # it under the terms of the GNU General Public License as published by | ||
| # the Free Software Foundation, either version 3 of the License, or | ||
| # (at your option) any later version. | ||
| # | ||
| # echemdb is distributed in the hope that it will be useful, | ||
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| # GNU General Public License for more details. | ||
| # | ||
| # You should have received a copy of the GNU General Public License | ||
| # along with echemdb. If not, see <https://www.gnu.org/licenses/>. | ||
| # ******************************************************************** | ||
|
|
||
|
|
||
| class GenericDescriptor: | ||
| r""" | ||
| Wrapper for a data package's descriptor to make searching in metadata easier. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> GenericDescriptor({'a': 0}) | ||
| {'a': 0} | ||
|
|
||
| """ | ||
|
|
||
| def __init__(self, descriptor): | ||
| self._descriptor = descriptor | ||
|
|
||
| def __dir__(self): | ||
| r""" | ||
| Return the attributes of this descriptor. | ||
|
|
||
| Implemented to allow tab-completion in a package's descriptor. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> descriptor = GenericDescriptor({'a': 0}) | ||
| >>> 'a' in dir(descriptor) | ||
| True | ||
|
|
||
| """ | ||
| return list(key.replace(' ', '_') for key in self._descriptor.keys()) + object.__dir__(self) | ||
|
|
||
| def __getattr__(self, name): | ||
| r""" | ||
| Return the attribute `name` of the descriptor. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> descriptor = GenericDescriptor({'a': 0}) | ||
| >>> descriptor.a | ||
| 0 | ||
|
|
||
| """ | ||
| name = name.replace('_', ' ') | ||
| if name in self._descriptor: | ||
| return Descriptor(self._descriptor[name]) | ||
|
|
||
| raise AttributeError(f"Descriptor has no entry {name}. Did you mean one of {[key.replace(' ', '_') for key in self._descriptor.keys()]}?") | ||
|
|
||
| def __getitem__(self, name): | ||
| r""" | ||
| Return the attribute `name` of the descriptor. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> descriptor = GenericDescriptor({'a': 0}) | ||
| >>> descriptor["a"] | ||
| 0 | ||
|
|
||
| """ | ||
| if name in self._descriptor: | ||
| return Descriptor(self._descriptor[name]) | ||
|
|
||
| raise KeyError(f"Descriptor has no entry {name}. Did you mean one of {list(self._descriptor.keys())}?") | ||
|
|
||
| def __repr__(self): | ||
| r""" | ||
| Return a printable representation of this descriptor. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> GenericDescriptor({}) | ||
| {} | ||
|
|
||
| """ | ||
| return repr(self._descriptor) | ||
|
|
||
| @property | ||
| def yaml(self): | ||
| r'''Return a printable representation of this descriptor in yaml format. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> descriptor = GenericDescriptor({'a': 0}) | ||
| >>> descriptor.yaml | ||
| 'a: 0\n' | ||
|
|
||
| ''' | ||
| import yaml | ||
| return yaml.dump(self._descriptor) | ||
|
|
||
|
|
||
| class QuantityDescriptor(GenericDescriptor): | ||
| r""" | ||
| Extends a descriptor with convenience methods when it is encoding a | ||
| quantity, i.e., unit and value. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> from echemdb.data.cv.entry import Entry | ||
| >>> entry = Entry.create_examples()[0] | ||
| >>> temperature = entry.electrochemical_system.electrolyte.temperature | ||
| >>> temperature | ||
| 298.15 K | ||
|
|
||
| """ | ||
| markdown_template = "components/quantity.md" | ||
|
|
||
| @property | ||
| def quantity(self): | ||
| r""" | ||
| This quantity as an astropy quantity. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> from echemdb.data.cv.entry import Entry | ||
| >>> entry = Entry.create_examples()[0] | ||
| >>> temperature = entry.electrochemical_system.electrolyte.temperature | ||
| >>> temperature.quantity | ||
| <Quantity 298.15 K> | ||
|
|
||
| """ | ||
| from astropy import units | ||
|
|
||
| return float(self.value) * units.Unit(self.unit) | ||
|
|
||
| def __repr__(self): | ||
| r""" | ||
| Return a printable representation of this quantity. | ||
|
|
||
| EXAMPLES:: | ||
|
|
||
| >>> from echemdb.data.cv.entry import Entry | ||
| >>> entry = Entry.create_examples()[0] | ||
| >>> temperature = entry.electrochemical_system.electrolyte.temperature | ||
| >>> temperature | ||
| 298.15 K | ||
|
|
||
| """ | ||
| return str(self.quantity) | ||
|
|
||
|
|
||
| def Descriptor(descriptor): | ||
| r""" | ||
| Return `descriptor` augmented with additional convenience methods. | ||
|
|
||
| EXAMPLES: | ||
|
|
||
| Primitive types are returned unchanged:: | ||
|
|
||
| >>> Descriptor("string") | ||
| 'string' | ||
|
|
||
| Dictionaries are augmented with attribute access:: | ||
|
|
||
| >>> descriptor = Descriptor({"an attribute": 13.37}) | ||
| >>> descriptor.an_attribute | ||
| 13.37 | ||
|
|
||
| Lists are recursively augmented:: | ||
|
|
||
| >>> descriptor = Descriptor([{"an attribute": 13.37}, {}]) | ||
| >>> descriptor[0].an_attribute | ||
| 13.37 | ||
|
|
||
| Dictionaries encoding a unit and value are augmented with astropy | ||
| convenience methods:: | ||
|
|
||
| >>> descriptor = Descriptor({"value": 1, "unit": "liter"}) | ||
| >>> descriptor.quantity.to("m^3") | ||
| <Quantity 0.001 m3> | ||
|
|
||
| """ | ||
|
|
||
| if isinstance(descriptor, GenericDescriptor): | ||
| return descriptor | ||
|
|
||
| if isinstance(descriptor, dict): | ||
| if set(descriptor.keys()) == {"unit", "value"}: | ||
| return QuantityDescriptor(descriptor) | ||
|
|
||
| return GenericDescriptor(descriptor) | ||
|
|
||
| if isinstance(descriptor, list): | ||
| return [Descriptor(item) for item in descriptor] | ||
|
|
||
| return descriptor |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.