From c2b46ccc637945c33d6742b06a2f3984ebd5bf6b Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:07:24 +0100 Subject: [PATCH 1/6] Create Python reference documentation --- bindings/python/dlite-collection-python.i | 10 +- bindings/python/dlite-entity-python.i | 2 +- bindings/python/dlite-misc-python.i | 6 +- bindings/python/triplestore/README.md | 148 -------- bindings/python/triplestore/__init__.py | 13 - bindings/python/triplestore/test_units.py | 31 -- bindings/python/triplestore/units.py | 364 -------------------- bindings/python/triplestore/units_README.md | 61 ---- doc/CMakeLists.txt | 13 +- doc/Doxyfile.in | 8 +- doc/conf.py.in | 28 +- doc/getting_started/build/patch_activate.md | 2 + requirements_doc.txt | 46 ++- src/getuuid.h | 2 +- 14 files changed, 73 insertions(+), 661 deletions(-) delete mode 100644 bindings/python/triplestore/README.md delete mode 100644 bindings/python/triplestore/__init__.py delete mode 100644 bindings/python/triplestore/test_units.py delete mode 100644 bindings/python/triplestore/units.py delete mode 100644 bindings/python/triplestore/units_README.md diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index de53c2309..62839d0e7 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -64,10 +64,10 @@ class Collection(Instance): Relations are (s, p, o, d=None)-triples with an optional fourth field `d`, specifying the datatype of the object. The datatype may have the following values: - - None: object is an IRI. - - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. - - Otherwise: object is a literal with datatype `d`. + - None: object is an IRI. + - Starts with '@': object is a language-tagged plain literal. + The language identifier follows the '@'-sign. + - Otherwise: object is a literal with datatype `d`. """ def __new__(cls, id=None): """Creates an empty collection.""" @@ -85,7 +85,7 @@ class Collection(Instance): Arguments: src: Storage instance | url | driver - location: str + location: File path to load from when `src` is a driver. options: str Options passed to the storage plugin when `src` is a driver. diff --git a/bindings/python/dlite-entity-python.i b/bindings/python/dlite-entity-python.i index 7a572c2ef..c65c9ede2 100644 --- a/bindings/python/dlite-entity-python.i +++ b/bindings/python/dlite-entity-python.i @@ -459,7 +459,7 @@ def get_instance( protocol+driver://location?options#id protocol://location?driver=;options#id - where `protocol`, `driver`, `location`, `options` and `id are + where `protocol`, `driver`, `location`, `options` and `id` are documented in the load() method. If `metaid` is provided, the instance is tried mapped to this diff --git a/bindings/python/dlite-misc-python.i b/bindings/python/dlite-misc-python.i index 36b2e2508..544989b0c 100644 --- a/bindings/python/dlite-misc-python.i +++ b/bindings/python/dlite-misc-python.i @@ -28,9 +28,9 @@ class errctl(): shown/hidden. filename: Filename to redirect errors to. The following values are handled specially: - - "None" or empty: No output is written. - - "": Write errors to stderr (default). - - "": Write errors to stdout. + - "None" or empty: No output is written. + - "": Write errors to stderr (default). + - "": Write errors to stdout. """ def __init__(self, hide=(), show=(), filename=""): diff --git a/bindings/python/triplestore/README.md b/bindings/python/triplestore/README.md deleted file mode 100644 index 53d3baf25..000000000 --- a/bindings/python/triplestore/README.md +++ /dev/null @@ -1,148 +0,0 @@ -Triplestore -=========== -> A Python package encapsulating different triplestores using the strategy -> design pattern. - -This package has by itself no dependencies outside the standard library, -but the triplestore backends may have. - -The main class is Triplestore, who's `__init__()` method takes the name of the -backend to encapsulate as first argument. Its interface is strongly inspired -by rdflib.Graph, but simplified when possible to make it easy to use. Some -important differences: -- all IRIs are represented by Python strings -- blank nodes are strings starting with "_:" -- literals are constructed with `Literal()` - -```python -from triplestore import Triplestore -ts = Triplestore(backend="rdflib") -``` - -The module already provides a set of pre-defined namespaces that simplifies -writing IRIs. For example: - -```python -from triplestore import RDFS, OWL -RDFS.subClassOf -# -> 'http://www.w3.org/2000/01/rdf-schema#subClassOf' -``` - -New namespaces can be created using the Namespace class, but are usually -added with the `bind()` method: - -```python -ONTO = ts.bind("onto", "http://example.com/onto#") -ONTO.MyConcept -# -> 'http://example.com/onto#MyConcept' -``` - -Namespace also support access by label and IRI checking. Both of these features -requires loading an ontology. The following example shows how to create an EMMO -namespace with IRI checking. The keyword argument `label_annotations=True` enables -access by `skos:prefLabel`, `rdfs:label` or `skos:altLabel`. The `check=True` -enables checking for existing IRIs. The `triplestore_url=...` is a resolvable URL -that can be read by the 'rdflib' backend. It is needed, because the 'rdflib' -backend is currently not able to load EMMO from the "http://emmo.info/emmo#" -namespace. - -```python -EMMO = ts.bind( - "emmo", "http://emmo.info/emmo#", - label_annotations=True, - check=True, - triplestore_url="https://emmo-repo.github.io/versions/1.0.0-beta4/emmo-inferred.ttl", -) -EMMO.Atom -# -> 'http://emmo.info/emmo#EMMO_eb77076b_a104_42ac_a065_798b2d2809ad' -EMMO.invalid_name -# -> NoSuchIRIError: http://emmo.info/emmo#invalid_name -``` - -New triples can be added either with the `parse()` method (for -backends that support it) or the `add()` and `add_triples()` methods: - -```python -# en(msg) is a convenient function for adding english literals. -# It is equivalent to ``triplestore.Literal(msg, lang="en")``. -from triplestore import en -ts.parse("onto.ttl", format="turtle") -ts.add_triples([ - (ONTO.MyConcept, RDFS.subClassOf, OWL.Thing), - (ONTO.MyConcept, RDFS.label, en("My briliant ontological concept.")), -]) -``` - -For backends that support it the triplestore can be serialised using -`serialize()`: - -```python -ts.serialize("onto2.ttl") -``` - -A set of convenient functions exists for simple queries, including -`triples()`, `subjects()`, `predicates()`, `objects()`, `subject_predicates()`, -`subject_objects()`, `predicate_objects()` and `value()`. Except for `value()`, -they return the result as generators. For example: - -```python -ts.objects(subject=ONTO.MyConcept, predicate=RDFS.subClassOf) -# -> -list(ts.objects(subject=ONTO.MyConcept, predicate=RDFS.subClassOf)) -# -> ['http://www.w3.org/2002/07/owl#Thing'] -``` - -The `query()` and `update()` methods can be used to query and update the -triplestore using SPARQL. - -Finally Triplestore has two specialised methods `add_mapsTo()` and -`add_function()` that simplify working with mappings. `add_mapsTo()` is -convinient for defining new mappings: - -```python -from triplestore import Namespace -META = Namespace("http://onto-ns.com/meta/0.1/MyEntity#") -ts.add_mapsTo(ONTO.MyConcept, META.my_property) -``` - -It can also be used with DLite and SOFT7 data models. Here we repeat -the above with DLite: - -```python -import dlite -meta = dlite.get_instance("http://onto-ns.com/meta/0.1/MyEntity") -ts.add_mapsTo(ONTO.MyConcept, meta, "my_property") -``` - -The `add_function()` describes a function and adds mappings for its -arguments and return value(s). Currently it only supports the [Function -Ontology (FnO)](https://fno.io/). - -```python -def mean(x, y): - """Returns the mean value of `x` and `y`.""" - return (x + y)/2 - -ts.add_function( - mean, - expects=(ONTO.RightArmLength, ONTO.LeftArmLength), - returns=ONTO.AverageArmLength, -) -``` - - -Further development -------------------- -* Update the `query()` method to return the SPARQL result in a backend- - independent way. -* Add additional backends. Candidates include: - - list of tuples - - owlready2/EMMOntoPy - - OntoRec/OntoFlowKB - - Stardog - - DLite triplestore (based on Redland librdf) - - Redland librdf - - Apache Jena Fuseki - - Allegrograph - - Wikidata -* Add ontological validation of physical dimension to Triplestore.mapsTo(). diff --git a/bindings/python/triplestore/__init__.py b/bindings/python/triplestore/__init__.py deleted file mode 100644 index cb99656fe..000000000 --- a/bindings/python/triplestore/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""A package encapsulating different triplestores using the strategy design -pattern. - -See the README.md file for a description for how to use this package. -""" -import warnings - -warnings.warn( - "dlite.triplestore is deprecated.\n" - "Use tripper (https://github.com/EMMC-ASBL/tripper) instead.", - DeprecationWarning, - stacklevel=2, -) diff --git a/bindings/python/triplestore/test_units.py b/bindings/python/triplestore/test_units.py deleted file mode 100644 index 59857446f..000000000 --- a/bindings/python/triplestore/test_units.py +++ /dev/null @@ -1,31 +0,0 @@ -from units import get_pint_registry - - -ureg = get_pint_registry(force_recreate=True) - -# Test the registry. -test_quantity1 = 1234 * ureg.M -print(str(test_quantity1.to_base_units())) -assert str(test_quantity1) == "1234 m" - -test_quantity2 = 2345.6 * ureg.Watt_per_Kelvin -print(str(test_quantity2)) -assert str(test_quantity2) == "2345.6 w/K" - -test_quantity3 = test_quantity1 * test_quantity2 -print("".join([str(test_quantity3), " = ", - str(test_quantity3.to_base_units()), - " = ", - "{:~}".format(test_quantity3.to_base_units())])) - -test_quantity4 = 16 * ureg.S -print(f'{str(test_quantity4)} = {test_quantity4.to_base_units()}') -assert str(test_quantity4) == "16 S" - -test_quantity5 = 25 * ureg.s -print(f'{str(test_quantity5)} = {test_quantity5.to_base_units()}') -assert str(test_quantity5) == "25 s" - -test_quantity6 = 36 * ureg.exaAmpere -print(f'{str(test_quantity6)} = {test_quantity6.to_base_units()}') -assert str(test_quantity6) == "36 EA" \ No newline at end of file diff --git a/bindings/python/triplestore/units.py b/bindings/python/triplestore/units.py deleted file mode 100644 index dc8d97af5..000000000 --- a/bindings/python/triplestore/units.py +++ /dev/null @@ -1,364 +0,0 @@ -"""Populates a pint unit registry from an ontology. -""" -import os -import re -import logging -from pint import UnitRegistry -from tripper import Triplestore, RDFS -from appdirs import user_cache_dir - - -def load_qudt(): - """Returns a Triplestore instance with QUDT pre-loaded.""" - ts = Triplestore(backend="rdflib") - ts.parse(source="http://qudt.org/2.1/vocab/unit") - ts.parse(source="http://qudt.org/2.1/schema/qudt") - return ts - - -def parse_qudt_dimension_vector(dimension_vector: str) -> dict: - """Split the dimension vector string into separate dimensions.""" - dimensions = re.findall(r'[AELIMHTD]-?[0-9]+', dimension_vector) - - result = {} - for dimension in dimensions: - result[dimension[0]] = dimension[1:] - - for letter in "AELIMHTD": - if letter not in result.keys(): - raise Exception( - f'Missing dimension "{letter}" in dimension vector ' - f'"{dimension_vector}"' - ) - return result - - -def pint_definition_string(dimension_dict: dict) -> str: - # Base units defined by: - # https://qudt.org/schema/qudt/QuantityKindDimensionVector - # https://qudt.org/vocab/sou/SI - base_units = { - "A": "mol", - "E": "A", - "L": "m", - "I": "cd", - "M": "kg", - "H": "K", - "T": "s", - "D": "1", - } - - # Build the unit definition, dimension by dimension. - result = [] - for letter, unit in base_units.items(): - exponent = dimension_dict[letter] - if int(dimension_dict[letter]) < 0: - result.append(f"/ {unit}**{exponent[1:]} ") - elif int(dimension_dict[letter]) > 0: - result.append(f"* {unit}**{exponent} ") - return "".join(result) - - -def prepare_cache_file_path(filename: str) -> str: - """Return cache file name.""" - cache_directory = user_cache_dir("dlite") - if not os.path.exists(cache_directory): - os.makedirs(cache_directory) - return os.path.join(cache_directory, filename) - - -def get_pint_registry(sources=('qudt', ), force_recreate=False) -> UnitRegistry: - """Load units from one or more unit sources into a Pint unit registry. - - Arguments: - sources: Sequence of unit sources to load. The sources are loaded - in the provided order. In case of conflicts, the source listed - first has precedence. - force_recreate: Whether to recreate the unit registry cache. - - Returns: - Pint unit registry. - """ - registry_file_path = prepare_cache_file_path("pint_unit_registry.txt") - if force_recreate or not os.path.exists(registry_file_path): - with open(registry_file_path, "w", encoding="utf8") as f: - f.write("\n".join(pint_prefix_lines()) + "\n") - for source in sources: - pint_registry_lines = pint_registry_lines_from_qudt() - with open(registry_file_path, "a", encoding="utf8") as f: - f.write("\n".join(pint_registry_lines) + "\n") - - ureg = UnitRegistry(registry_file_path) - #ureg.default_format = "~P" #symbols, pretty print - ureg.default_format = "~" #symbols, standard print (preferred) - #ureg.default_format = "~C" #symbols, compact print - return ureg - - -def pint_prefix_lines(): - # Decimal prefixes from pint's default_en.txt registry. - prefixes = ['quecto- = 1e-30 = q-', - 'ronto- = 1e-27 = r-', - 'yocto- = 1e-24 = y-', - 'zepto- = 1e-21 = z-', - 'atto- = 1e-18 = a-', - 'femto- = 1e-15 = f-', - 'pico- = 1e-12 = p-', - 'nano- = 1e-9 = n-', - 'micro- = 1e-6 = µ- = μ- = u-', - 'milli- = 1e-3 = m-', - 'centi- = 1e-2 = c-', - 'deci- = 1e-1 = d-', - 'deca- = 1e+1 = da- = deka-', - 'hecto- = 1e2 = h-', - 'kilo- = 1e3 = k-', - 'mega- = 1e6 = M-', - 'giga- = 1e9 = G-', - 'tera- = 1e12 = T-', - 'peta- = 1e15 = P-', - 'exa- = 1e18 = E-', - 'zetta- = 1e21 = Z-', - 'yotta- = 1e24 = Y-', - 'ronna- = 1e27 = R-', - 'quetta- = 1e30 = Q-', - ] - return prefixes - - -def prefix_names(): - lines = pint_prefix_lines() - for i in range(len(lines)): - lines[i] = lines[i].split(" ")[0].replace("-", "") - return lines - - -def pint_registry_lines_from_qudt(): - ts = load_qudt() - - QUDTU = ts.bind("unit", "http://qudt.org/vocab/unit/", check=True) - QUDT = ts.bind("unit", "http://qudt.org/schema/qudt/", check=True) - DCTERMS = ts.bind("dcterms", "http://purl.org/dc/terms/") - - pint_registry_lines = [] - pint_definitions = {} - identifiers = PintIdentifiers() - - # Explicit definition of which QUDT units that will serve as base units for - # the pint unit registry. (i.e. the QUDT names for the SI units and the - # name of their physical dimension) - #base_unit_dimensions ={ - # "M": "length", - # "SEC": "time", - # "A": "current", - # "CD": "luminosity", - # "KiloGM": "mass", - # "MOL": "substance", - # "K": "temperature", - #} - - # Base units defined by rdfs:label (instead of QUDT name): - base_unit_dimensions ={ - "Meter": "length", - "Second": "time", - "Ampere": "current", - "Candela": "luminosity", - "Kilogram": "mass", - "Mole": "substance", - "Kelvin": "temperature", - } - - # Read info from all units. - for s, p, o in ts.triples([None, QUDT.hasDimensionVector, None]): - - # Check if this unit has been replaced; then skip it. - replaced_by = next( - ts.objects(subject=s, predicate=DCTERMS.isReplacedBy), None) - if replaced_by is not None: - continue - - unit = s.split("/")[-1] - unit_name = unit.replace("-", "_") - - omit_prefixed_unit = False - for prefix in prefix_names(): - if unit_name.startswith('KiloGM'): - pass - elif unit_name.lower().startswith(prefix): - omit_prefixed_unit = True - break - - if omit_prefixed_unit: - continue - - # Extract and parse the dimension vector. - dimension_vector = o.split("/")[-1] - pint_definition = pint_definition_string(parse_qudt_dimension_vector( - dimension_vector)) - - # Extract multiplier and offset. - multiplier = next( - ts.objects(subject=s, predicate=QUDT.conversionMultiplier), "1") - offset = next(ts.objects(subject=s, predicate=QUDT.conversionOffset), - None) - - pint_definitions[s] = { - "unit_in_SI": pint_definition, - "multiplier": multiplier, - "offset": offset, - } - - # Extract identifiers. - pint_name_is_set = False - prio_downgrade = 2 - for label in ts.objects(subject=s, predicate=RDFS.label): - label = label.replace(" ", "_") - label = label.replace("-", "_") - if pint_name_is_set: - identifiers.add_identifier( - URI=s, label_name="label", prio=5, identifier=label) - else: - if label in base_unit_dimensions.keys(): - prio_downgrade = 0 - identifiers.add_identifier( - URI=s, label_name="unit_name", prio=1+prio_downgrade, identifier=label) - pint_name_is_set = True - - identifiers.add_identifier( - URI=s, label_name="label", prio=6, identifier=unit_name) - # Can there be more than one symbol in QUDT? - symbol = next(ts.objects(subject=s, predicate=QUDT.symbol), None) - if symbol is not None: - symbol = symbol.replace(" ", "_") - identifiers.add_identifier( - URI=s, label_name="symbol", prio=2+prio_downgrade, identifier=symbol) - udunits_code = next( - ts.objects(subject=s, predicate=QUDT.udunitsCode), None) - if udunits_code is not None: - udunits_code = udunits_code.replace(" ", "_") - identifiers.add_identifier( - URI=s, label_name="udunits_code", prio=7, identifier=udunits_code) - - identifiers.remove_ambiguities() - - # Build the pint unit registry lines. - for URIb, definition in pint_definitions.items(): - - unit_identifiers = identifiers.get_identifiers(URI=URIb) - - # Start constructing the pint definition line. - unit_name = unit_identifiers["unit_name"] - if unit_name is None: - logging.warning(f'Omitting UNIT {URIb} due to name conflict.') - continue - if unit_name in base_unit_dimensions.keys(): - pint_definition_line = ( - f'{unit_name} = [{base_unit_dimensions[unit_name]}]' - ) - else: - pint_definition_line = ( - f'{unit_name} = {definition["multiplier"]} ' - f'{definition["unit_in_SI"]}' - ) - - # Add offset. - if definition["offset"] is not None: - pint_definition_line += f'; offset: {definition["offset"]}' - - # Add symbol. - symbol = unit_identifiers["symbol"] - if symbol is None: - symbol = "_" - pint_definition_line += f' = {symbol}' - - # Add any labels. - for label in unit_identifiers["labels"]: - if label is not None: - pint_definition_line += f' = {label}' - - # Add URI. - pint_definition_line += f' = {URIb}' - - # Add udunits code. - udunits_code = unit_identifiers["udunits_code"] - if udunits_code is not None: - pint_definition_line += f' = {udunits_code}' - - pint_registry_lines.append(pint_definition_line) - return pint_registry_lines - - -class PintIdentifiers: - """Class for handling the various identifiers, with the functionality - to remove any ambiguous definitions. - """ - def __init__(self): - self.URIs = [] - self.label_names = [] - self.prios = [] - self.identifiers = [] - - def add_identifier(self, URI: str, label_name: str, prio: int, - identifier:str): - self.URIs.append(URI) - self.label_names.append(label_name) - self.prios.append(prio) - self.identifiers.append(identifier) - - def remove_ambiguities(self): - """Remove ambiguities. - - Set ambiguous identifiers to None. - Keep the first occurence within each priority level. - """ - # Store used identifiers along with their URI. - used_identifiers = {} - - # For each priority level, remove any ambiguities. - for prio in sorted(list(set(self.prios))): - inds_prio = [i for i,value in enumerate(self.prios) if value==prio] - for i in inds_prio: - if self.identifiers[i] is not None: - # Check if the identifier has already been used. - if self.identifiers[i] in used_identifiers.keys(): - # Warn if this identifier belongs to another URI. - URI_of_identifier = used_identifiers[self.identifiers[i]] - if self.URIs[i] is not URI_of_identifier: - logging.warning( - f'Omitting {self.label_names[i]} ' - f'"{self.identifiers[i]}" from {self.URIs[i]} ' - f'(the identifier is used for ' - f'{URI_of_identifier})' - ) - self.identifiers[i] = None - else: - used_identifiers[self.identifiers[i]] = self.URIs[i] - - - def is_valid_identifier(self, identifier: str, URI: str, - label_name: str) -> bool: - """Check if an identifier is valid for use as a particular - label_name for a particular unit. - """ - identifier_index = self.identifiers.index(identifier) - return (self.URIs[identifier_index] == URI and - self.label_names[identifier_index] == label_name) - - def get_identifiers(self, URI:str) -> dict: - """Returns a dict containing all identifiers for a given URI.""" - identifiers = {} - identifiers["labels"] = [] - - inds = [i for i,value in enumerate(self.URIs) if value==URI] - for i in inds: - label_name = self.label_names[i] - identifier = self.identifiers[i] - if label_name == "unit_name": - identifiers["unit_name"] = identifier - elif label_name == "symbol": - identifiers["symbol"] = identifier - elif label_name == "label": - identifiers["labels"].append(identifier) - elif label_name == "udunits_code": - identifiers["udunits_code"] = identifier - - return identifiers diff --git a/bindings/python/triplestore/units_README.md b/bindings/python/triplestore/units_README.md deleted file mode 100644 index 8680a8e54..000000000 --- a/bindings/python/triplestore/units_README.md +++ /dev/null @@ -1,61 +0,0 @@ -Pint unit registry generator -============================ - -Introduction and usage ----------------------- - -The units.py file contains the get_pint_registry() function, which downloads -the [QUDT UNITS](https://www.qudt.org/doc/DOC_VOCAB-UNITS.html) vocabulary -and uses its contents to generate a unit registry file for -[Pint](https://pint.readthedocs.io). The function then uses the generated -registry file to generate and return a Pint UnitRegistry object. - -The unit registry file is cached, and the default behavior is to not -re-recreate it if it already exists in the cache directory. - -Any unit identifiers in QUDT UNITS that use the "-" or " " characters will -have these replaced with "_" (in order to be Pint compatible). - -The usage of the get_pint_registry() is demonstrated in test_units.py. - - -Technical details ------------------ -* Unit registry filename: `pint_unit_registry.txt` -* Cache directory in Unix/Linux: typically `~/.cache/dlite` -* Cache directory in Windows 10: typically `C:\Users\\AppData\Local\ -Packages\PythonSoftwareFoundation.Python.\LocalCache\Local\dlite\ -dlite\Cache` -* Cache directory in Mac OS X: presumably `~/Library/Caches/dlite` (not tested) - -For the units, all identifiers, alternative labels and definitions are -read from QUDT. The program resolves naming conflicts by omitting the -conflicting labels, following a certain prioritization. Highest priority is -given to the rdfs:label identifiers, which are used as the primary -identifier ("canonical name") in the Pint unit registry. - -Prefix definitions are hard-coded and not read from QUDT. Units in QUDT UNITS -that start with a prefix are omitted, since Pint performs reasoning based on -the prefix definitions in its unit registry. The "KiloGM" SI unit is -excepted from this rule. - - -Known problems --------------- -* The program does not yet work on Windows, see -[issue #497](https://github.com/SINTEF/dlite/issues/497). - -* The program provides warnings (at registry creation time) for **omitted -units and/or labels**, with details about any label conflicts. This output -can be used to identify duplications and inconsistencies within QUDT UNITS. - -* Since the QUDT UNITS vocabulary is subject to change, so are the omitted -units and labels. When a unit is assigned a new label in QUDT UNITS, this -may take precedence over currently existing labels on other units (depending -on the prioritization of the various label types). Since QUDT UNITS seems -not to be consistency-checked before release, this can result in (possibly -undetected) changed references to the units of interest. - -* Units that contain a prefix inside its name are currently included in the -generated Pint unit registry. - diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index 4c1b4b934..9e5667cdd 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -14,7 +14,14 @@ set(SPHINX_HTML_DIR "${CMAKE_CURRENT_BINARY_DIR}/html") set(SPHINX_INDEX_FILE "${SPHINX_HTML_DIR}/index.html") # Doxygen intput source directory -set(DOXYGEN_INPUT_DIR ${dlite_SOURCE_DIR}/src) +set(DOXYGEN_INPUT_DIRS + ${dlite_SOURCE_DIR}/src/utils + ${dlite_SOURCE_DIR}/src + ${dlite_SOURCE_DIR}/src/pyembed + #${dlite_BINARY_DIR}/bindings/python/dlite +) +string(REPLACE ";" " " DOXYGEN_INPUT "${DOXYGEN_INPUT_DIRS}") + # Doxygen output directory set(DOXYGEN_OUTPUT_XML_DIR "${CMAKE_CURRENT_BINARY_DIR}/xml") @@ -53,8 +60,8 @@ configure_file( # Doxygen command add_custom_command(OUTPUT ${DOXYGEN_INDEX_FILE} DEPENDS ${DLITE_PUBLIC_HEADERS} - COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_IN} ${DOXYFILE_OUT} - MAIN_DEPENDENCY ${DOXYFILE_OUT} ${DOXYFILE_IN} + COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYFILE_OUT} + MAIN_DEPENDENCY ${DOXYFILE_OUT} WORKING_DIRECTORY ${BINARY_BUILD_DIR} COMMENT "Generating docs" VERBATIM) diff --git a/doc/Doxyfile.in b/doc/Doxyfile.in index 202ecaf1e..0a9715e4a 100644 --- a/doc/Doxyfile.in +++ b/doc/Doxyfile.in @@ -775,7 +775,7 @@ WARN_LOGFILE = # spaces. See also FILE_PATTERNS and EXTENSION_MAPPING # Note: If this tag is empty the current directory is searched. -INPUT = "@DOXYGEN_INPUT_DIR@" +INPUT = @DOXYGEN_INPUT@ # This tag can be used to specify the character encoding of the source files # that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses @@ -831,7 +831,8 @@ EXCLUDE_SYMLINKS = NO # Note that the wildcards are matched against the file with absolute path, so to # exclude all test directories for example use the pattern */test/* -EXCLUDE_PATTERNS = +EXCLUDE_PATTERNS = */tests/* \ + */old/* # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names # (namespaces, classes, functions, etc.) that should be excluded from the @@ -848,7 +849,8 @@ EXCLUDE_SYMBOLS = # that contain example code fragments that are included (see the \include # command). -EXAMPLE_PATH = @dlite_SOURCE_DIR@/src/utils/tests +EXAMPLE_PATH = @dlite_SOURCE_DIR@/examples \ + @dlite_SOURCE_DIR@/src/utils/tests/tgen_example.c # If the value of the EXAMPLE_PATH tag contains directories, you can use the # EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and diff --git a/doc/conf.py.in b/doc/conf.py.in index f9bada93e..8f7ef6bc4 100644 --- a/doc/conf.py.in +++ b/doc/conf.py.in @@ -51,15 +51,7 @@ exclude_patterns = [ ] extensions = [ - # Temporary disable autoapi since it leads to the following error during - # build of HTML documentation - # - # Extension error (autoapi.extension): - # Handler for event - # 'builder-inited' threw an exception (exception: 'Module' object has no - # attribute 'doc') - # - #"autoapi.extension", + "autoapi.extension", "breathe", # Doxygen bridge "myst_nb", # markdown source support & support for Jupyter notebooks "sphinx.ext.graphviz", # Graphviz @@ -84,12 +76,13 @@ dlite_share_plugins = [ if plugin_dir.is_dir() ] -autoapi_dirs = [ - "@CMAKE_BINARY_DIR@/bindings/python/dlite" -] + [ - f"@CMAKE_BINARY_DIR@/bindings/python/dlite/share/dlite/{plugin_dir}" - for plugin_dir in dlite_share_plugins -] +#autoapi_dirs = [ +# "@CMAKE_BINARY_DIR@/bindings/python/dlite" +#] + [ +# f"@CMAKE_BINARY_DIR@/bindings/python/dlite/share/dlite/{plugin_dir}" +# for plugin_dir in dlite_share_plugins +#] +autoapi_dirs = ["@CMAKE_BINARY_DIR@/bindings/python/dlite"] autoapi_type = "python" autoapi_file_patterns = ["*.py", "*.pyi"] autoapi_template_dir = "_templates/autoapi" @@ -103,7 +96,10 @@ autoapi_options = [ "imported-members", ] autoapi_keep_files = True # Should be False in production +#autoapi_keep_files = False # Should be False in production autoapi_python_use_implicit_namespaces = True # True to avoid namespace being `python.dlite` +#autoapi_ignore = ["@CMAKE_BINARY_DIR@/doc/_build/autoapi/dlite/dlite"] + autodoc_typehints = "description" autodoc_typehints_format = "short" @@ -127,7 +123,7 @@ html_theme_options = { "use_issues_button": True, "use_fullscreen_button": True, "use_repository_button": True, - "logo_only": True, + #"logo_only": True, "show_navbar_depth": 1, "announcement": "This documentation is under development!", } diff --git a/doc/getting_started/build/patch_activate.md b/doc/getting_started/build/patch_activate.md index 1186ad031..668113733 100644 --- a/doc/getting_started/build/patch_activate.md +++ b/doc/getting_started/build/patch_activate.md @@ -1,3 +1,5 @@ +Patch activate +============== By default, [virtualenv] does not set `LD_LIBRARY_PATH`. This will result in errors when running applications that links to libdlite, like for example, `dlite-codegen`. To fix this, `$VIRTUAL_ENV/lib/` needs to be appended/prepended to `LD_LIBRARY_PATH`. diff --git a/requirements_doc.txt b/requirements_doc.txt index 638e6329c..b3b407834 100644 --- a/requirements_doc.txt +++ b/requirements_doc.txt @@ -1,17 +1,39 @@ importlib-metadata==6.8.0; python_version<'3.8' -beautifulsoup4==4.12.3 -lxml==5.2.2 +# beautifulsoup4==4.12.3 +# lxml==5.2.2 +# +# Sphinx==7.3.7 +# sphinx-autoapi==3.1.2 +# sphinx-autobuild==2024.4.16 +# sphinx-book-theme==1.1.3 +# sphinx-design==0.6.0 +# sphinx-toggleprompt==0.5.2 +# sphinx-copybutton==0.5.2 # does not work well with toggleprompt +# sphinxcontrib-plantuml==0.30 +# +# breathe==4.35.0 +# docutils==0.21.2 +# +# # Documentation of notebooks +# #myst-nb==1.1.1 # 1.1.2 +# #nbclient==0.10.0 # -Sphinx==7.3.7 -sphinx-autoapi==3.1.2 -sphinx-autobuild==2024.4.16 -sphinx-book-theme==1.1.3 -sphinx-design==0.6.0 -sphinxcontrib-plantuml==0.30 -sphinx-toggleprompt==0.5.2 -sphinx-copybutton==0.5.2 # does not work well with toggleprompt +beautifulsoup4==4.12.3 breathe==4.35.0 -myst-nb==1.1.1 -nbclient==0.10.0 docutils==0.21.2 +lxml==5.3.0 +Sphinx==7.4.7 +sphinx-autoapi==3.3.3 +sphinx-autobuild==2024.10.3 +sphinx-book-theme==1.1.3 +sphinx-copybutton==0.5.2 +sphinx-toggleprompt==0.5.2 +sphinx_design==0.6.1 +sphinxcontrib-applehelp==2.0.0 +sphinxcontrib-devhelp==2.0.0 +sphinxcontrib-htmlhelp==2.1.0 +sphinxcontrib-jsmath==1.0.1 +sphinxcontrib-plantuml==0.30 +sphinxcontrib-qthelp==2.0.0 +sphinxcontrib-serializinghtml==2.0.0 diff --git a/src/getuuid.h b/src/getuuid.h index f9365bd54..cd1eb61d8 100644 --- a/src/getuuid.h +++ b/src/getuuid.h @@ -55,7 +55,7 @@ int isuuid(const char *s); /** - Returns non-zero if `s` matches /. `len` is the length of `s`. + Returns non-zero if `s` matches `[URI]/[UUID]. `len` is the length of `s`. An optional final hash or slash will be ignored. */ int isinstanceuri(const char *s, int len); From 3eb6ed44221a42590928197af2d20c426e686678 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:20:51 +0100 Subject: [PATCH 2/6] Updated path to Python API reference documentation --- doc/index.rst | 2 +- requirements_doc.txt | 21 ++------------------- 2 files changed, 3 insertions(+), 20 deletions(-) diff --git a/doc/index.rst b/doc/index.rst index 5ad7fe4df..e1fa9b743 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -54,7 +54,7 @@ DLite :glob: :hidden: - Python API + Python API C-API diff --git a/requirements_doc.txt b/requirements_doc.txt index b3b407834..1c047492d 100644 --- a/requirements_doc.txt +++ b/requirements_doc.txt @@ -1,28 +1,11 @@ importlib-metadata==6.8.0; python_version<'3.8' -# beautifulsoup4==4.12.3 -# lxml==5.2.2 -# -# Sphinx==7.3.7 -# sphinx-autoapi==3.1.2 -# sphinx-autobuild==2024.4.16 -# sphinx-book-theme==1.1.3 -# sphinx-design==0.6.0 -# sphinx-toggleprompt==0.5.2 -# sphinx-copybutton==0.5.2 # does not work well with toggleprompt -# sphinxcontrib-plantuml==0.30 -# -# breathe==4.35.0 -# docutils==0.21.2 -# -# # Documentation of notebooks -# #myst-nb==1.1.1 # 1.1.2 -# #nbclient==0.10.0 # - beautifulsoup4==4.12.3 breathe==4.35.0 docutils==0.21.2 lxml==5.3.0 +myst-nb==1.1.2 +nbclient==0.10.0 Sphinx==7.4.7 sphinx-autoapi==3.3.3 sphinx-autobuild==2024.10.3 From 249ad75598c5eba395718daab40df0f1c1443d94 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:30:50 +0100 Subject: [PATCH 3/6] Correct link to Python reference API --- doc/api.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/api.rst b/doc/api.rst index 0ebc29681..d2323c8a9 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -5,5 +5,5 @@ API Reference :maxdepth: 4 :caption: API Reference - Python API + Python API C-API From f19caa814d1e3095a33d27badb7b3fbcd7432182 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Sun, 27 Oct 2024 23:40:54 +0100 Subject: [PATCH 4/6] Corrected index.rst files --- doc/api.rst | 4 ++-- doc/index.rst | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/doc/api.rst b/doc/api.rst index d2323c8a9..36c804900 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -2,8 +2,8 @@ API Reference ============= .. toctree:: - :maxdepth: 4 + :maxdepth: 2 :caption: API Reference - Python API + Python API C-API diff --git a/doc/index.rst b/doc/index.rst index e1fa9b743..ca1a43d17 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -49,12 +49,12 @@ DLite .. toctree:: - :maxdepth: 3 + :maxdepth: 2 :caption: API Reference :glob: :hidden: - Python API + Python API C-API From f87888fd42cd905e5b06f52c5a27c2de6781a5f7 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 12:03:14 +0100 Subject: [PATCH 5/6] Added a section about how to generate documentation --- bindings/python/dlite-collection-python.i | 9 ++-- bindings/python/quantity.py | 46 +++++++++---------- .../documentation_contributors.md | 39 ++++++++++++++++ 3 files changed, 67 insertions(+), 27 deletions(-) diff --git a/bindings/python/dlite-collection-python.i b/bindings/python/dlite-collection-python.i index 62839d0e7..2a28c9e55 100644 --- a/bindings/python/dlite-collection-python.i +++ b/bindings/python/dlite-collection-python.i @@ -64,10 +64,11 @@ class Collection(Instance): Relations are (s, p, o, d=None)-triples with an optional fourth field `d`, specifying the datatype of the object. The datatype may have the following values: - - None: object is an IRI. - - Starts with '@': object is a language-tagged plain literal. - The language identifier follows the '@'-sign. - - Otherwise: object is a literal with datatype `d`. + + - None: object is an IRI. + - Starts with '@': object is a language-tagged plain literal. + The language identifier follows the '@'-sign. + - Otherwise: object is a literal with datatype `d`. """ def __new__(cls, id=None): """Creates an empty collection.""" diff --git a/bindings/python/quantity.py b/bindings/python/quantity.py index 8ce31cd1c..fc9a4dc73 100644 --- a/bindings/python/quantity.py +++ b/bindings/python/quantity.py @@ -1,15 +1,13 @@ - -""" Define the singleton QuantityHelper to work with pint Quantity and dlite - instance properties. +"""Define the singleton QuantityHelper to work with pint Quantity and dlite +instance properties. """ from typing import Any import numpy as np -HAS_PINT = True -try: - import pint -except Exception: - HAS_PINT = False +from dlite.testutils import importcheck + +pint = importcheck("pint") + DLITE_QUANTITY_TYPES = [ 'int', 'float', 'double', 'uint', @@ -21,8 +19,16 @@ class QuantityHelper: + """Singleton class for working with pint Quantity and dlite instance + properties. + """ def __init__(self): + if not pint: + raise RuntimeError( + 'you must install "pint" to work with quantities, ' + 'try: pip install pint' + ) self.__dict__['_instance'] = None self.__dict__['_registry'] = None @@ -46,7 +52,7 @@ def _get_unit_as_string(self, unit: Any) -> str: unit_string = str(unit) return unit_string.replace('%', 'percent') - def __getitem__(self, name: str) -> pint.Quantity: + def __getitem__(self, name: str) -> "pint.Quantity": p = self._get_property(name) u = self._get_unit_as_string(p.unit) return self.quantity(self._instance[name], u) @@ -105,15 +111,15 @@ def get(self, *names): return None @property - def unit_registry(self) -> pint.UnitRegistry: + def unit_registry(self) -> "pint.UnitRegistry": """ Returns the current pint UnitRegistry object """ return self._registry.get() - def quantity(self, magnitude, unit) -> pint.Quantity: + def quantity(self, magnitude, unit) -> "pint.Quantity": """ Return a pint.Quantity object """ return self._registry.Quantity(magnitude, unit) - def parse(self, value: Any) -> pint.Quantity: + def parse(self, value: Any) -> "pint.Quantity": """ Parse the given value and return a pint.Quantity object """ if isinstance(value, (pint.Quantity, self._registry.Quantity)): return value @@ -129,8 +135,8 @@ def parse(self, value: Any) -> pint.Quantity: else: return self.quantity(value, '') - def parse_expression(self, value: str) -> pint.Quantity: - """ Parse an expression (str) and return a pint.Quantity object """ + def parse_expression(self, value: str) -> "pint.Quantity": + """Parse an expression (str) and return a pint.Quantity object """ result = None if value: value_str = self._get_unit_as_string(value) @@ -171,12 +177,6 @@ def to_dict(self, names=None, value_type='quantity', fmt=''): def get_quantity_helper(instance): global quantity_helper - if HAS_PINT: - if quantity_helper is None: - quantity_helper = QuantityHelper() - return quantity_helper(instance) - else: - raise RuntimeError( - 'you must install "pint" to work with quantities, ' - 'try: pip install pint' - ) + if quantity_helper is None: + quantity_helper = QuantityHelper() + return quantity_helper(instance) diff --git a/doc/contributors_guide/documentation_contributors.md b/doc/contributors_guide/documentation_contributors.md index 95f6c74d4..7fd652347 100644 --- a/doc/contributors_guide/documentation_contributors.md +++ b/doc/contributors_guide/documentation_contributors.md @@ -4,7 +4,45 @@ Guideline for contributing documentation The DLite documentation is written in [Markdown]. This include both the README files and documentation found in the `doc/` subdirectory. + +Generate documentation locally +------------------------------ +When writing documentation it is practically to build and check the documentation locally before submitting a pull request. + +The following steps are needed for building the documentation: + +1. Install dependencies. + + First you need [doxygen]. In Ubuntu it can be installed with + + sudo apt-install doxygen + + Python requirements can be installed with + + pip install --update -r requirements_doc.txt + +2. Ask cmake to build documentation + + ``` + cd + cmake -DWITH_DOC=YES . + ``` + + If you haven't build dlite before, you should replace the final dot with the + path to the root of the DLite source directory. + +3. Build the documentation + + cmake --build . + + Check and fix possible error and warning messages from doxygen and sphinx. + The generated documentation can be found in `/doc/html/index.html`. + + +Style recommendations and guidelines +------------------------------------ Common to both is that the text should be as easy and natural as possible to read and write both from the terminal, in an editor and rendered in a web browser. + Hence, the following recommendations: * Write one sentence per line, in order to get an easier to read output from `git diff`. @@ -97,6 +135,7 @@ If you click that button, it will toggle the prompt and output on or off, making +[doxygen]: https://www.doxygen.nl/ [Markdown]: https://en.wikipedia.org/wiki/Markdown [setext]: https://github.com/DavidAnson/markdownlint/blob/main/doc/md003.md [CommonMark]: https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet From 815e191bb6d7b000022cbc3b209430efba3f84c0 Mon Sep 17 00:00:00 2001 From: Jesper Friis Date: Mon, 28 Oct 2024 14:59:24 +0100 Subject: [PATCH 6/6] Apply suggestions from code review Co-authored-by: Francesca L. Bleken <48128015+francescalb@users.noreply.github.com> --- doc/contributors_guide/documentation_contributors.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/doc/contributors_guide/documentation_contributors.md b/doc/contributors_guide/documentation_contributors.md index 7fd652347..b85b45115 100644 --- a/doc/contributors_guide/documentation_contributors.md +++ b/doc/contributors_guide/documentation_contributors.md @@ -7,7 +7,7 @@ This include both the README files and documentation found in the `doc/` subdire Generate documentation locally ------------------------------ -When writing documentation it is practically to build and check the documentation locally before submitting a pull request. +When writing documentation it is practical to build and check the documentation locally before submitting a pull request. The following steps are needed for building the documentation: @@ -15,11 +15,11 @@ The following steps are needed for building the documentation: First you need [doxygen]. In Ubuntu it can be installed with - sudo apt-install doxygen + sudo apt install doxygen Python requirements can be installed with - pip install --update -r requirements_doc.txt + pip install --upgrade -r requirements_doc.txt 2. Ask cmake to build documentation