From b366ff4742e7b922ccf9dc129b1bf59a19413287 Mon Sep 17 00:00:00 2001 From: Albert Engstfeld Date: Fri, 13 Feb 2026 22:43:17 +0100 Subject: [PATCH 1/3] Update how examples are created and used --- unitpackage/collection.py | 17 +--- unitpackage/database/echemdb_entry.py | 20 ++--- unitpackage/descriptor.py | 6 +- unitpackage/entry.py | 116 +++++++++++++------------- unitpackage/metadata.py | 18 ++-- 5 files changed, 86 insertions(+), 91 deletions(-) diff --git a/unitpackage/collection.py b/unitpackage/collection.py index d7e641f..13c100b 100644 --- a/unitpackage/collection.py +++ b/unitpackage/collection.py @@ -47,6 +47,7 @@ # along with unitpackage. If not, see . # ******************************************************************** import logging +import os.path from frictionless import Package @@ -121,21 +122,11 @@ def create_example(cls): Entry('no_bibliography')] """ - - entries = ( - cls.Entry.create_examples("alves_2011_electrochemistry_6010") - + cls.Entry.create_examples("engstfeld_2018_polycrystalline_17743") - + cls.Entry.create_examples("no_bibliography") + example_dir = os.path.join( + os.path.dirname(__file__), "..", "examples", "local" ) - package = Package() - - for entry in entries: - package.add_resource(entry.resource) - - return cls( - package=package, - ) + return cls.from_local(example_dir) def filter(self, predicate): r""" diff --git a/unitpackage/database/echemdb_entry.py b/unitpackage/database/echemdb_entry.py index 62b0d92..5a28236 100644 --- a/unitpackage/database/echemdb_entry.py +++ b/unitpackage/database/echemdb_entry.py @@ -95,7 +95,7 @@ def __repr__(self): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry Echemdb('alves_2011_electrochemistry_6010_f1a_solid') @@ -109,14 +109,14 @@ def bibliography(self): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry.bibliography # doctest: +NORMALIZE_WHITESPACE Entry('article', fields=[ ('title', ... ... - >>> entry_no_bib = EchemdbEntry.create_examples(name="no_bibliography")[0] + >>> entry_no_bib = EchemdbEntry.create_example(name="no_bibliography") >>> entry_no_bib.bibliography '' @@ -144,7 +144,7 @@ def citation(self, backend="text"): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry.citation(backend='text') 'O. B. Alves et al. Electrochemistry at Ru(0001) in a flowing CO-saturated electrolyte—reactive and inert adlayer phases. Physical Chemistry Chemical Physics, 13(13):6010–6021, 2011.' >>> print(entry.citation(backend='md')) @@ -210,7 +210,7 @@ def get_electrode(self, name): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry.get_electrode('WE') # doctest: +NORMALIZE_WHITESPACE {'name': 'WE', 'function': 'workingElectrode', 'type': 'single crystal', 'crystallographicOrientation': '0001', 'material': 'Ru', @@ -245,7 +245,7 @@ def rescale(self, units): These units must be defined in the metadata of the resource, within the key ``figureDescription.fields``:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> rescaled_entry = entry.rescale(units='original') >>> rescaled_entry.fields # doctest: +NORMALIZE_WHITESPACE [{'name': 't', 'type': 'number', 'unit': 's'}, @@ -269,7 +269,7 @@ def _normalize_field_name(self, field_name): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry._normalize_field_name('j') 'j' >>> entry._normalize_field_name('x') @@ -290,7 +290,7 @@ def thumbnail(self, width=96, height=72, dpi=72, **kwds): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> thumb = entry.thumbnail() >>> thumb.startswith(b'\x89PNG') True @@ -340,7 +340,7 @@ def plot(self, x_label="E", y_label="j", name=None): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry.plot() Figure(...) @@ -404,7 +404,7 @@ def rescale_reference(self, new_reference=None, field_name=None, ph=None): EXAMPLES:: - >>> entry = EchemdbEntry.create_examples()[0] + >>> entry = EchemdbEntry.create_example() >>> entry.resource.schema.get_field('E') # doctest: +NORMALIZE_WHITESPACE {'name': 'E', 'type': 'number', 'unit': 'V', 'reference': 'RHE'} diff --git a/unitpackage/descriptor.py b/unitpackage/descriptor.py index 7dc1092..9906952 100644 --- a/unitpackage/descriptor.py +++ b/unitpackage/descriptor.py @@ -170,7 +170,7 @@ class QuantityDescriptor(GenericDescriptor): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> temperature = entry.echemdb.system.electrolyte.temperature >>> temperature 298.15 K @@ -187,7 +187,7 @@ def quantity(self): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> temperature = entry.echemdb.system.electrolyte.temperature >>> temperature.quantity @@ -204,7 +204,7 @@ def __repr__(self): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> temperature = entry.echemdb.system.electrolyte.temperature >>> temperature 298.15 K diff --git a/unitpackage/entry.py b/unitpackage/entry.py index 625c7c1..102fefa 100644 --- a/unitpackage/entry.py +++ b/unitpackage/entry.py @@ -15,7 +15,7 @@ Metadata included in an entry is accessible as an attribute:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.echemdb.source # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE {'citationKey': 'alves_2011_electrochemistry_6010', 'url': 'https://doi.org/10.1039/C0CP01001D', @@ -25,7 +25,7 @@ The data of the entry can be called as a pandas dataframe:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -141,7 +141,7 @@ def metadata(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata['echemdb']['source']['citationKey'] 'alves_2011_electrochemistry_6010' @@ -150,14 +150,14 @@ def metadata(self): Load metadata from a dict:: - >>> new_entry = Entry.create_examples()[0] + >>> new_entry = Entry.create_example() >>> new_entry.metadata.from_dict({'echemdb': {'test': 'data'}}) >>> new_entry.metadata['echemdb']['test'] 'data' The descriptor is cached but still sees metadata updates:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> descriptor1 = entry.metadata >>> entry.metadata.from_dict({'custom': {'key': 'value'}}) >>> descriptor2 = entry.metadata @@ -183,7 +183,7 @@ def load_metadata(self, filename, file_format=None, key=None): >>> import os >>> import tempfile >>> import yaml - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: ... yaml.dump({'source': {'citationKey': 'chain_test'}}, f) ... temp_path = f.name @@ -196,7 +196,7 @@ def load_metadata(self, filename, file_format=None, key=None): >>> import os >>> import json >>> import tempfile - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: ... json.dump({'custom': {'data': 'value'}}, f) ... temp_path = f.name @@ -237,7 +237,7 @@ def identifier(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.identifier 'alves_2011_electrochemistry_6010_f1a_solid' @@ -252,9 +252,9 @@ def __dir__(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> dir(entry) # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE - [... 'create_examples', 'default_metadata_key', 'df', 'echemdb', 'field_unit', + [... 'create_example', 'default_metadata_key', 'df', 'echemdb', 'field_unit', 'fields', 'from_csv', 'from_df', 'from_local', 'identifier', 'load_metadata', 'metadata', 'plot', 'remove_column', 'remove_columns', 'rename_field', 'rename_fields', 'rescale', 'resource', 'save', 'update_fields', 'yaml'] @@ -268,7 +268,7 @@ def __getattr__(self, name): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.echemdb.source # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE {'citationKey': 'alves_2011_electrochemistry_6010', 'url': 'https://doi.org/10.1039/C0CP01001D', @@ -290,7 +290,7 @@ def __getitem__(self, name): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry["echemdb"]["source"] # doctest: +NORMALIZE_WHITESPACE {'citationKey': 'alves_2011_electrochemistry_6010', 'url': 'https://doi.org/10.1039/C0CP01001D', @@ -318,7 +318,7 @@ def _descriptor(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry._descriptor # doctest: +ELLIPSIS {'echemdb': ...} @@ -339,7 +339,7 @@ def _metadata(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry._metadata # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE {...'echemdb': {...'source': {'citationKey': 'alves_2011_electrochemistry_6010',...}...} @@ -358,7 +358,7 @@ def _default_metadata(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry._default_metadata # doctest: +ELLIPSIS +NORMALIZE_WHITESPACE {...'echemdb': {...'source': {'citationKey': 'alves_2011_electrochemistry_6010',...}...} @@ -377,7 +377,7 @@ def fields(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.fields [{'name': 't', 'type': 'number', 'unit': 's'}, {'name': 'E', 'type': 'number', 'unit': 'V', 'reference': 'RHE'}, @@ -392,7 +392,7 @@ def field_unit(self, field_name): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.field_unit('E') 'V' @@ -414,7 +414,7 @@ def rescale(self, units): The units without any rescaling:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.fields [{'name': 't', 'type': 'number', 'unit': 's'}, {'name': 'E', 'type': 'number', 'unit': 'V', 'reference': 'RHE'}, @@ -476,7 +476,7 @@ def add_offset(self, field_name=None, offset=None, unit=""): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df.head() # doctest: +NORMALIZE_WHITESPACE t E j 0 0.00 -0.103158 -0.998277 @@ -588,7 +588,7 @@ def _create_new_df_resource(self, df, schema=None, field_updates=None): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> df = entry.df.copy() >>> new_resource = entry._create_new_df_resource(df) >>> new_resource.name == entry.resource.name @@ -632,7 +632,7 @@ def _df_resource(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> resource = entry._df_resource >>> resource.format 'pandas' @@ -667,7 +667,7 @@ def df(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -703,7 +703,7 @@ def add_columns(self, df, new_fields): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -762,7 +762,7 @@ def remove_column(self, field_name): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -805,7 +805,7 @@ def remove_columns(self, *field_names): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -853,7 +853,7 @@ def __repr__(self): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry Entry('alves_2011_electrochemistry_6010_f1a_solid') @@ -861,48 +861,52 @@ def __repr__(self): return f"Entry({repr(self.identifier)})" @classmethod - def create_examples(cls, name=""): + def _create_examples(cls): r""" - Return some example entries for use in automated tests. + Return an example collection for use in automated tests. The examples are created from Data Packages in the unitpackage's examples directory. These are only available from the development environment. EXAMPLES:: - >>> Entry.create_examples() - [Entry('alves_2011_electrochemistry_6010_f1a_solid'), Entry('engstfeld_2018_polycrystalline_17743_f4b_1'), Entry('no_bibliography')] - - An entry without associated BIB file. - - >>> Entry.create_examples(name="no_bibliography") - [Entry('no_bibliography')] + >>> Entry._create_examples() # doctest: +NORMALIZE_WHITESPACE + [Entry('alves_2011_electrochemistry_6010_f1a_solid'), + Entry('engstfeld_2018_polycrystalline_17743_f4b_1'), + Entry('no_bibliography')] """ example_dir = os.path.join( - os.path.dirname(__file__), "..", "examples", "local", name + os.path.dirname(__file__), "..", "examples", "local" ) - if not os.path.exists(example_dir): - raise ValueError( - f"No subdirectory in examples/ for {name}, i.e., could not find {example_dir}." - ) + from unitpackage.collection import Collection - from unitpackage.local import collect_datapackages + return Collection.from_local(example_dir) - packages = collect_datapackages(example_dir) + @classmethod + def create_example(cls, name=None): + r""" + Return an example entry for use in automated tests. - if len(packages) == 0: - from glob import glob + The examples are created from Data Packages in the unitpackage's examples directory. + These are only available from the development environment. - raise ValueError( - f"No literature data found for {name}. The directory for this data {example_dir} exists. But we could not find any datapackages in there. " - f"There is probably some outdated data in {example_dir}. The contents of that directory are: { glob(os.path.join(example_dir,'**')) }" - ) + EXAMPLES:: + + >>> Entry.create_example() + Entry('alves_2011_electrochemistry_6010_f1a_solid') + + >>> Entry.create_example(name="no_bibliography") + Entry('no_bibliography') + + """ + if name is None: + name = "alves_2011_electrochemistry_6010_f1a_solid" - from unitpackage.local import collect_resources + collection = cls._create_examples() - return [cls(resource=resource) for resource in collect_resources(packages)] + return cls(resource=collection[name].resource) def plot(self, x_label=None, y_label=None, name=None): r""" @@ -912,7 +916,7 @@ def plot(self, x_label=None, y_label=None, name=None): EXAMPLES:: - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.plot() Figure(...) @@ -976,7 +980,7 @@ def update_fields(self, fields): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.fields # doctest: +NORMALIZE_WHITESPACE [{'name': 't', 'type': 'number', 'unit': 's'}, {'name': 'E', 'type': 'number', 'unit': 'V', 'reference': 'RHE'}, @@ -1145,7 +1149,7 @@ def rename_field(self, field_name, new_name, keep_original_name_as=None): The original dataframe:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -1203,7 +1207,7 @@ def rename_fields(self, field_names, keep_original_name_as=None): The original dataframe:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.df t E j 0 0.000000 -0.103158 -0.998277 @@ -1360,7 +1364,7 @@ def save(self, *, outdir, basename=None): The output files are named ``identifier.csv`` and ``identifier.json`` using the identifier of the original resource:: >>> import os - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.save(outdir='./test/generated') >>> basename = entry.identifier >>> os.path.exists(f'test/generated/{basename}.json') and os.path.exists(f'test/generated/{basename}.csv') @@ -1376,7 +1380,7 @@ def save(self, *, outdir, basename=None): A valid basename:: >>> import os - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> basename = 'save_basename' >>> entry.save(basename=basename, outdir='./test/generated') >>> os.path.exists(f'test/generated/{basename}.json') and os.path.exists(f'test/generated/{basename}.csv') diff --git a/unitpackage/metadata.py b/unitpackage/metadata.py index 1bd0e70..554dc4c 100644 --- a/unitpackage/metadata.py +++ b/unitpackage/metadata.py @@ -10,7 +10,7 @@ Access metadata with dict-style or attribute-style syntax:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata['echemdb']['source']['citationKey'] 'alves_2011_electrochemistry_6010' @@ -55,7 +55,7 @@ class MetadataDescriptor: EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata # doctest: +ELLIPSIS {'echemdb': {'experimental': ... @@ -88,7 +88,7 @@ def __getitem__(self, key): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata['echemdb']['source']['citationKey'] 'alves_2011_electrochemistry_6010' @@ -102,7 +102,7 @@ def __setitem__(self, key, value): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata['custom_key'] = {'data': 'value'} >>> entry.metadata['custom_key'] {'data': 'value'} @@ -117,7 +117,7 @@ def __getattr__(self, name): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata.echemdb.source.citationKey 'alves_2011_electrochemistry_6010' @@ -131,7 +131,7 @@ def from_dict(self, data): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata.from_dict({'echemdb': {'source': {'citationKey': 'test'}}}) >>> entry.metadata['echemdb']['source']['citationKey'] 'test' @@ -146,7 +146,7 @@ def _add_metadata(self, key, data): EXAMPLES:: >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> entry.metadata._add_metadata('custom_key', {'data': 'value'}) >>> entry.metadata['custom_key'] {'data': 'value'} @@ -177,7 +177,7 @@ def from_yaml(self, filename, key=None): >>> import tempfile >>> import yaml >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> with tempfile.NamedTemporaryFile(mode='w', suffix='.yaml', delete=False) as f: ... yaml.dump({'source': {'citationKey': 'yaml_test'}}, f) ... temp_path = f.name @@ -207,7 +207,7 @@ def from_json(self, filename, key=None): >>> import json >>> import tempfile >>> from unitpackage.entry import Entry - >>> entry = Entry.create_examples()[0] + >>> entry = Entry.create_example() >>> with tempfile.NamedTemporaryFile(mode='w', suffix='.json', delete=False) as f: ... json.dump({'source': {'citationKey': 'json_test'}}, f) ... temp_path = f.name From 4f1eaabd0e3b0940720bdccad487031317d1af8f Mon Sep 17 00:00:00 2001 From: Albert Engstfeld Date: Fri, 13 Feb 2026 22:49:20 +0100 Subject: [PATCH 2/3] fix lint --- unitpackage/collection.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/unitpackage/collection.py b/unitpackage/collection.py index 13c100b..aa7f201 100644 --- a/unitpackage/collection.py +++ b/unitpackage/collection.py @@ -122,9 +122,7 @@ def create_example(cls): Entry('no_bibliography')] """ - example_dir = os.path.join( - os.path.dirname(__file__), "..", "examples", "local" - ) + example_dir = os.path.join(os.path.dirname(__file__), "..", "examples", "local") return cls.from_local(example_dir) From a263a28a2d2e02d811bbc0eb93c6f883b8fe0ca2 Mon Sep 17 00:00:00 2001 From: Albert Engstfeld Date: Fri, 13 Feb 2026 22:53:17 +0100 Subject: [PATCH 3/3] Add news --- doc/news/refactor-create-examples.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 doc/news/refactor-create-examples.rst diff --git a/doc/news/refactor-create-examples.rst b/doc/news/refactor-create-examples.rst new file mode 100644 index 0000000..de64cd6 --- /dev/null +++ b/doc/news/refactor-create-examples.rst @@ -0,0 +1,11 @@ +**Added:** + +* Added ``Entry.create_example(name=None)`` which returns a single example entry. Defaults to ``'alves_2011_electrochemistry_6010_f1a_solid'`` when no name is provided. + +**Changed:** + +* Changed ``Collection.create_example`` to use ``Collection.from_local`` internally. + +**Removed:** + +* Removed ``Entry.create_examples``. Use ``Entry.create_example`` instead.