From af952a738f64f51166b8ca473bfc682f36ea212d Mon Sep 17 00:00:00 2001 From: Casper Welzel Andersen Date: Thu, 14 Oct 2021 01:40:27 +0200 Subject: [PATCH] Further satisfy pylint --- demo/horizontal/emmo2meta.py | 7 +- demo/horizontal/step1_generate_metadata.py | 6 +- demo/horizontal/step2_define_metadata.py | 11 +- demo/horizontal/step4_map_instance.py | 37 ++--- demo/vertical/define_ontology.py | 9 +- emmopy/__init__.py | 5 +- emmopy/emmocheck.py | 6 +- emmopy/emmopy.py | 8 +- ontopy/colortest.py | 4 +- ontopy/factpluspluswrapper/factppgraph.py | 52 +++--- .../factpluspluswrapper/owlapi_interface.py | 23 +-- ontopy/factpluspluswrapper/sync_factpp.py | 40 +++-- ontopy/ontology.py | 148 +++++++++--------- setup.py | 23 +-- tasks.py | 2 +- tools/emmocheck | 2 +- tools/ontoconvert | 21 +-- tools/ontodoc | 33 ++-- tools/ontograph | 30 ++-- tools/ontoversion | 27 ++-- 20 files changed, 274 insertions(+), 220 deletions(-) diff --git a/demo/horizontal/emmo2meta.py b/demo/horizontal/emmo2meta.py index bcab21f84..7950f4a7e 100644 --- a/demo/horizontal/emmo2meta.py +++ b/demo/horizontal/emmo2meta.py @@ -174,12 +174,13 @@ def get_properties(self, cls): # pylint: disable=too-many-locals def get_dim(restriction, name, descr=None): """Returns dimension index corresponding to dimension name `name` for property `restriction.value`.""" + # pylint: disable=protected-access result = [] restriction_type = ( owlready2.class_construct._restriction_type_2_label[ restriction.type ] - ) # pylint: disable=protected-access + ) if restriction_type in ("some", "only", "min") or ( restriction_type in ("max", "exactly") and restriction.cardinality > 1 @@ -272,9 +273,9 @@ def get_dim(restriction, name, descr=None): def add_restriction(self, restriction): """Adds owl restriction to collection and returns a reference to it.""" - restriction_type = owlready2.class_construct._restriction_type_2_label[ + restriction_type = owlready2.class_construct._restriction_type_2_label[ # pylint: disable=protected-access restriction.type - ] # pylint: disable=protected-access + ] cardinality = restriction.cardinality if restriction.cardinality else 0 entity = self.add_restriction_entity() inst = entity() diff --git a/demo/horizontal/step1_generate_metadata.py b/demo/horizontal/step1_generate_metadata.py index e997f2839..6f5bb2831 100755 --- a/demo/horizontal/step1_generate_metadata.py +++ b/demo/horizontal/step1_generate_metadata.py @@ -19,8 +19,8 @@ """ import os -from ontopy import get_ontology from emmo2meta import EMMO2Meta +from ontopy import get_ontology # Load our ontology from the vertical case @@ -51,5 +51,5 @@ e.save("json", "usercase_metadata.json", "mode=w") print("Generated metadata for the usercase ontology:") -print(" %d instances" % e.coll.count()) -print(" %d relations" % len(list(e.coll.relations()))) +print(f" {e.coll.count()} instances") +print(f" {len(list(e.coll.relations()))} relations") diff --git a/demo/horizontal/step2_define_metadata.py b/demo/horizontal/step2_define_metadata.py index 3eb0ac22d..fe665ba24 100755 --- a/demo/horizontal/step2_define_metadata.py +++ b/demo/horizontal/step2_define_metadata.py @@ -13,6 +13,7 @@ adds some methods for handling some special attributes. """ +# pylint: disable=import-error import ase from ase.spacegroup import Spacegroup @@ -23,14 +24,14 @@ BaseAtoms = dlite.classfactory(ase.Atoms, url="json://atoms.json?mode=r#") -class DLiteAtoms(BaseAtoms): +class DLiteAtoms(BaseAtoms): # pylint: disable=too-few-public-methods """ASE Atoms class extended as a dlite entity.""" def _dlite_get_info(self): - d = self.info.copy() - sg = Spacegroup(d.get("spacegroup", "P 1")) - d["spacegroup"] = sg.symbol - return [(k, str(v)) for k, v in d.items()] + info = self.info.copy() + space_group = Spacegroup(info.get("spacegroup", "P 1")) + info["spacegroup"] = space_group.symbol + return [(key, str(value)) for key, value in info.items()] def _dlite_set_info(self, value): self.info.update(value) diff --git a/demo/horizontal/step4_map_instance.py b/demo/horizontal/step4_map_instance.py index 863ce756c..cf79e8233 100755 --- a/demo/horizontal/step4_map_instance.py +++ b/demo/horizontal/step4_map_instance.py @@ -11,19 +11,20 @@ a atomistic Al-Fe4Al13 interface structure from a cif file and represents it using the same metadata framework as used in step 1. """ +# pylint: disable=import-error,invalid-name import dlite -def map_app2common(inst, metacoll, out_id=None): - """Maps atom structure `inst` from our application representation +def map_app2common(instance, meta_collection, out_id=None): + """Maps atom structure `instance` from our application representation (based on a not explicitly stated ontology) to the common - EMMO-based representation in `metacoll`. + EMMO-based representation in `meta_collection`. Parameters ---------- - inst : Instance of http://sintef.no/meta/soft/0.1/Atoms + instance : Instance of http://sintef.no/meta/soft/0.1/Atoms Input atom structure. - metacoll : Collection + meta_collection : Collection Collection of EMMO-based metadata generated from the ontology. out_id : None | string An optional id associated with the returned collection. @@ -32,21 +33,21 @@ def map_app2common(inst, metacoll, out_id=None): ------- atcoll : Collection New collection with the atom structure represented as instances - of metadata in `metacoll`. + of metadata in `meta_collection`. Notes ----- We use lowercase and underscore notation for the individuals. """ - infodict = dict(inst.info) # make dict out of the info field + infodict = dict(instance.info) # make dict out of the info field - # Create new collection representing `inst` in our case ontology + # Create new collection representing `instance` in our case ontology atcoll = dlite.Collection(out_id) - # Get metadata from metacoll - Crystal = metacoll["Crystal"] - UnitCell = metacoll["CrystalUnitCell"] - EBondedAtom = metacoll["BondedAtom"] + # Get metadata from meta_collection + Crystal = meta_collection["Crystal"] + UnitCell = meta_collection["CrystalUnitCell"] + EBondedAtom = meta_collection["BondedAtom"] # Instanciate the structure crystal = Crystal([]) @@ -58,12 +59,12 @@ def map_app2common(inst, metacoll, out_id=None): atcoll.add("unit_cell", unit_cell) atcoll.add_relation("crystal", "hasSpatialDirectPart", "unit_cell") - for i in range(inst.natoms): - label = "atom%d" % i - a = EBondedAtom([3]) - a.AtomicNumber = inst.numbers[i] - a.Position = inst.positions[i] - atcoll.add(label, a) + for index in range(instance.natoms): + label = f"atom{index}" + atom = EBondedAtom([3]) + atom.AtomicNumber = instance.numbers[index] + atom.Position = instance.positions[index] + atcoll.add(label, atom) atcoll.add_relation("unit_cell", "hasSpatialDirectPart", label) return atcoll diff --git a/demo/vertical/define_ontology.py b/demo/vertical/define_ontology.py index 5441e18b7..d02c41004 100755 --- a/demo/vertical/define_ontology.py +++ b/demo/vertical/define_ontology.py @@ -33,6 +33,7 @@ [1] Khalid et al. Proc. Manufact. 15 (2018) 1407 """ +# pylint: disable=fixme,invalid-name,too-few-public-methods from ontopy import World @@ -56,8 +57,6 @@ class hasType(emmo.hasConvention): """Associates a type (string, number...) to a property.""" - pass - class isTypeOf(emmo.hasConvention): """Associates a property to a type (string, number...).""" @@ -69,10 +68,14 @@ class isTypeOf(emmo.hasConvention): # TODO: remove class SquareLengthDimension(emmo.PhysicalDimension): + """Squared length dimension.""" + is_a = [emmo.hasSymbolData.value("T0 L2 M0 I0 Θ0 N0 J0")] # TODO: remove class SquareMetre(emmo.SICoherentDerivedUnit): + """A square metre unit.""" + emmo.altLabel = ["m²"] is_a = [emmo.hasPhysicalDimension.only(SquareLengthDimension)] @@ -177,6 +180,8 @@ class CrystalUnitCell(emmo.Material): ] class InterfaceModel(CrystalUnitCell): + """A crystal interface.""" + is_a = [emmo.hasProperty.some(Area)] class Crystal(emmo.Solid): diff --git a/emmopy/__init__.py b/emmopy/__init__.py index cba5d675c..0b12928f1 100644 --- a/emmopy/__init__.py +++ b/emmopy/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +"""# `emmopy`""" import sys from ontopy import __version__ as __ontopy_version__ @@ -9,11 +10,11 @@ raise RuntimeError("emmopy requires Python 3.6 or later") # Ensure emmopy is imported before owlready2... -if "owlready2" in sys.modules.keys() and "ontopy" not in sys.modules.keys(): +if "owlready2" in sys.modules and "ontopy" not in sys.modules: raise RuntimeError("emmopy must be imported before owlready2") # Import functions from emmopy -from .emmopy import get_emmo +from .emmopy import get_emmo # pylint: disable=wrong-import-position __all__ = ("get_emmo",) diff --git a/emmopy/emmocheck.py b/emmopy/emmocheck.py index fa8a6b14c..3070c72b8 100644 --- a/emmopy/emmocheck.py +++ b/emmopy/emmocheck.py @@ -15,7 +15,6 @@ - name_of_test_to_skip """ -import os import sys import re import unittest @@ -85,8 +84,9 @@ def test_number_of_labels(self): ) if ( - "prefLabel" in self.onto.world._props - ): # pylint: disable=protected-access + "prefLabel" + in self.onto.world._props # pylint: disable=protected-access + ): for entity in self.onto.get_entities(): if repr(entity) not in exceptions: with self.subTest( diff --git a/emmopy/emmopy.py b/emmopy/emmopy.py index c2c8f953d..082940134 100644 --- a/emmopy/emmopy.py +++ b/emmopy/emmopy.py @@ -1,6 +1,12 @@ -from ontopy import get_ontology +"""# `emmopy.emmopy` + +Automagically retrieve the EMMO utilizing +[`ontopy.get_ontology`][ontopy.get_ontology]. +""" from typing import Optional, TYPE_CHECKING +from ontopy import get_ontology + if TYPE_CHECKING: from ontopy.ontology import Ontology diff --git a/ontopy/colortest.py b/ontopy/colortest.py index c1abff631..cc3f5dd99 100644 --- a/ontopy/colortest.py +++ b/ontopy/colortest.py @@ -12,9 +12,9 @@ from blessings import Terminal from pygments import formatters, highlight -from pygments.lexers import ( +from pygments.lexers import ( # pylint: disable=no-name-in-module Python3TracebackLexer as Lexer, -) # pylint: disable=no-name-in-module +) class ColourTextTestResult(TestResult): diff --git a/ontopy/factpluspluswrapper/factppgraph.py b/ontopy/factpluspluswrapper/factppgraph.py index 41185f4db..92fd47732 100644 --- a/ontopy/factpluspluswrapper/factppgraph.py +++ b/ontopy/factpluspluswrapper/factppgraph.py @@ -1,3 +1,5 @@ +"""# `ontopy.factpluspluswrapper.factppgraph`""" +# pylint: disable=too-few-public-methods from rdflib import URIRef, OWL, RDF, RDFS from ontopy.factpluspluswrapper.owlapi_interface import OwlApiInterface @@ -72,29 +74,33 @@ def add_base_annotations(self): """Copy base annotations from original graph to the inferred graph.""" base = self.base_iri inferred = self.inferred - for s, p, o in self.graph.triples( + for _, predicate, obj in self.graph.triples( (self.asserted_base_iri(), None, None) ): - if p == OWL.versionIRI: - version = o.rsplit("/", 1)[-1] - o = URIRef("%s/%s" % (base, version)) - inferred.add((base, p, o)) + if predicate == OWL.versionIRI: + version = obj.rsplit("/", 1)[-1] + obj = URIRef(f"{base}/{version}") + inferred.add((base, predicate, obj)) def set_namespace(self): """Override namespace of inferred graph with the namespace of the original graph. """ inferred = self.inferred - for k, v in self.namespaces.items(): - inferred.namespace_manager.bind(k, v, override=True, replace=True) + for key, value in self.namespaces.items(): + inferred.namespace_manager.bind( + key, value, override=True, replace=True + ) def clean_base(self): """Remove all relations `s? a owl:Ontology` where `s?` is not `base_iri`. """ inferred = self.inferred - for s, p, o in inferred.triples((None, RDF.type, OWL.Ontology)): - inferred.remove((s, p, o)) + for subject, predicate, obj in inferred.triples( + (None, RDF.type, OWL.Ontology) + ): + inferred.remove((subject, predicate, obj)) inferred.add((self.base_iri, RDF.type, OWL.Ontology)) def remove_nothing_is_nothing(self): @@ -102,28 +108,30 @@ def remove_nothing_is_nothing(self): owl:Nothing rdfs:subClassOf owl:Nothing """ - t = OWL.Nothing, RDFS.subClassOf, OWL.Nothing + triple = OWL.Nothing, RDFS.subClassOf, OWL.Nothing inferred = self.inferred - if t in inferred: - inferred.remove(t) + if triple in inferred: + inferred.remove(triple) def clean_ancestors(self): """Remove redundant rdfs:subClassOf relations in inferred graph.""" inferred = self.inferred - for s in inferred.subjects(RDF.type, OWL.Class): - if isinstance(s, URIRef): + for ( # pylint: disable=too-many-nested-blocks + subject + ) in inferred.subjects(RDF.type, OWL.Class): + if isinstance(subject, URIRef): parents = set( - p - for p in inferred.objects(s, RDFS.subClassOf) - if isinstance(p, URIRef) + parent + for parent in inferred.objects(subject, RDFS.subClassOf) + if isinstance(parent, URIRef) ) if len(parents) > 1: for parent in parents: ancestors = set( inferred.transitive_objects(parent, RDFS.subClassOf) ) - for p in parents: - if p != parent and p in ancestors: - t = s, RDFS.subClassOf, p - if t in inferred: - inferred.remove(t) + for entity in parents: + if entity != parent and entity in ancestors: + triple = subject, RDFS.subClassOf, entity + if triple in inferred: + inferred.remove(triple) diff --git a/ontopy/factpluspluswrapper/owlapi_interface.py b/ontopy/factpluspluswrapper/owlapi_interface.py index ac2a9f170..8b3623248 100644 --- a/ontopy/factpluspluswrapper/owlapi_interface.py +++ b/ontopy/factpluspluswrapper/owlapi_interface.py @@ -4,12 +4,13 @@ Original author: Matthias Urban """ +import argparse +import logging import os import subprocess # nosec -import logging -import rdflib import tempfile -import argparse + +import rdflib logger = logging.getLogger(__name__) @@ -21,7 +22,6 @@ class OwlApiInterface: def __init__(self): """Initialize the interface.""" - pass def reason(self, graph): """Generate the inferred axioms for a given Graph. @@ -29,9 +29,9 @@ def reason(self, graph): Args: graph (Graph): An rdflib graph to execute the reasoner on. """ - with tempfile.NamedTemporaryFile("wt") as f: - graph.serialize(f.name, format="xml") - return self._run(f.name, command="--run-reasoner") + with tempfile.NamedTemporaryFile("wt") as tmpdir: + graph.serialize(tmpdir.name, format="xml") + return self._run(tmpdir.name, command="--run-reasoner") def reason_files(self, *owl_files): """Merge the given owl and generate the inferred axioms. @@ -49,7 +49,8 @@ def merge_files(self, *owl_files): """ return self._run(*owl_files, command="--merge-only") - def _run(self, *owl_files, command, output_file=None, return_graph=True): + @staticmethod + def _run(*owl_files, command, output_file=None, return_graph=True): """Run the FaCT++ reasoner using a java command. Args: @@ -74,11 +75,11 @@ def _run(self, *owl_files, command, output_file=None, return_graph=True): "-Djava.library.path=" + java_base + "/lib/so", "org.simphony.OntologyLoader", ] - + ["%s" % command] + + [command] + list(owl_files) ) logger.info("Running Reasoner") - logger.debug(f"Command {cmd}") + logger.debug("Command %s", cmd) subprocess.run(cmd, check=True) # nosec graph = None @@ -107,7 +108,7 @@ def reason_from_terminal(): parser.add_argument("output_file", help="Path to store inferred axioms to.") args = parser.parse_args() - OwlApiInterface()._run( + OwlApiInterface()._run( # pylint: disable=protected-access *args.owl_file, command="--run-reasoner", return_graph=False, diff --git a/ontopy/factpluspluswrapper/sync_factpp.py b/ontopy/factpluspluswrapper/sync_factpp.py index af7b46f07..402c8b5a8 100644 --- a/ontopy/factpluspluswrapper/sync_factpp.py +++ b/ontopy/factpluspluswrapper/sync_factpp.py @@ -1,3 +1,5 @@ +"""# `ontopy.factpluspluswrapper.syncfatpp`""" +# pylint: disable=protected-access from collections import defaultdict from collections.abc import Sequence @@ -24,7 +26,7 @@ } -def sync_reasoner_factpp( +def sync_reasoner_factpp( # pylint: disable=too-many-locals,too-many-branches ontology_or_world=None, infer_property_values=False, debug=1 ): """Run FaCT++ reasoner and load the inferred relations back into @@ -63,13 +65,15 @@ def sync_reasoner_factpp( print("*** Prepare graph") # Exclude owl:imports because they are not needed and can # cause trouble when loading the inferred ontology - g1 = rdflib.Graph() - for s, p, o in world.as_rdflib_graph().triples((None, None, None)): - if p != OWL.imports: - g1.add((s, p, o)) + graph1 = rdflib.Graph() + for subject, predicate, obj in world.as_rdflib_graph().triples( + (None, None, None) + ): + if predicate != OWL.imports: + graph1.add((subject, predicate, obj)) print("*** Run FaCT++ reasoner (and postprocess)") - g2 = FaCTPPGraph(g1).inferred_graph() + graph2 = FaCTPPGraph(graph1).inferred_graph() print("*** Load inferred ontology") # Check all rdfs:subClassOf relations in the inferred graph and add @@ -78,26 +82,30 @@ def sync_reasoner_factpp( new_equivs = defaultdict(list) entity_2_type = {} - for s, p, o in g2.triples((None, None, None)): + for subject, predicate, obj in graph2.triples((None, None, None)): if ( - isinstance(s, URIRef) - and p in OWL_2_TYPE - and isinstance(o, URIRef) + isinstance(subject, URIRef) + and predicate in OWL_2_TYPE + and isinstance(obj, URIRef) ): - s_storid = ontology._abbreviate(str(s), False) - p_storid = ontology._abbreviate(str(p), False) - o_storid = ontology._abbreviate(str(o), False) + s_storid = ontology._abbreviate(str(subject), False) + p_storid = ontology._abbreviate(str(predicate), False) + o_storid = ontology._abbreviate(str(obj), False) if ( s_storid is not None and p_storid is not None and o_storid is not None ): - if p in (RDFS.subClassOf, RDFS.subPropertyOf, RDF.type): + if predicate in ( + RDFS.subClassOf, + RDFS.subPropertyOf, + RDF.type, + ): new_parents[s_storid].append(o_storid) - entity_2_type[s_storid] = OWL_2_TYPE[p] + entity_2_type[s_storid] = OWL_2_TYPE[predicate] else: new_equivs[s_storid].append(o_storid) - entity_2_type[s_storid] = OWL_2_TYPE[p] + entity_2_type[s_storid] = OWL_2_TYPE[predicate] if infer_property_values: inferred_obj_relations = [] diff --git a/ontopy/ontology.py b/ontopy/ontology.py index 251f70716..ea10aad8e 100644 --- a/ontopy/ontology.py +++ b/ontopy/ontology.py @@ -8,7 +8,7 @@ If desirable some of this may be moved back into owlready2. """ -# pylint: disable=too-many-lines,fixme,arguments-differ +# pylint: disable=too-many-lines,fixme,arguments-differ,protected-access import os import itertools import inspect @@ -115,7 +115,9 @@ def get_ontology(self, base_iri="emmo-inferred"): return onto -class Ontology(owlready2.Ontology, OntoGraph): +class Ontology( # pylint: disable=too-many-public-methods + owlready2.Ontology, OntoGraph +): """A generic class extending owlready2.Ontology.""" # Properties controlling what annotations that are considered by @@ -221,11 +223,11 @@ def get_by_label(self, label, label_annotations=None, namespace=None): if "namespaces" in self.__dict__: if namespace: if namespace in self.namespaces: - for e in self.get_by_label_all( + for entity in self.get_by_label_all( label, label_annotations=label_annotations ): - if e.namespace == self.namespaces[namespace]: - return e + if entity.namespace == self.namespaces[namespace]: + return entity raise NoSuchLabelError( f"No label annotations matches {label!r} in namespace " f"{namespace!r}" @@ -234,7 +236,7 @@ def get_by_label(self, label, label_annotations=None, namespace=None): return self.namespaces[label] if label_annotations is None: - annotations = (la.name for la in self.label_annotations) + annotations = (_.name for _ in self.label_annotations) else: annotations = ( _.name if hasattr(_, "storid") else _ for _ in label_annotations @@ -378,7 +380,6 @@ def _load( # pylint: disable=too-many-arguments,too-many-locals,too-many-branch **kwargs, ): """Help function for _load().""" - # pylint: disable=protected-access web_protocol = "http://", "https://", "ftp://" url = filename if filename else self.base_iri.rstrip("/#") @@ -546,9 +547,9 @@ def save( if not _validate_installed_version( package="rdflib", min_version="6.0.0" ) and format == FMAP.get("ttl", ""): - from rdflib import ( + from rdflib import ( # pylint: disable=import-outside-toplevel __version__ as __rdflib_version__, - ) # pylint: disable=import-outside-toplevel + ) warnings.warn( IncompatibleVersion( @@ -645,8 +646,7 @@ def object_properties(self, imported=False): """ if imported: return self.world.object_properties() - else: - return super().object_properties() + return super().object_properties() def data_properties(self, imported=False): """Returns an generator over all data properties. @@ -763,9 +763,8 @@ def sync_reasoner( sync = sync_reasoner_factpp else: raise ValueError( - 'unknown reasoner %r. Supported reasoners are "Pellet", ' - '"HermiT" and "FaCT++".', - reasoner, + f"unknown reasoner {reasoner!r}. Supported reasoners are " + '"Pellet", "HermiT" and "FaCT++".' ) if include_imported: @@ -774,7 +773,7 @@ def sync_reasoner( else: sync([self], **kwargs) - def sync_attributes( + def sync_attributes( # pylint: disable=too-many-branches self, name_policy=None, name_prefix="", @@ -816,6 +815,8 @@ class docstrings are mapped to. Defaults to "comment". # no prefLabel - create new annotation property.. with self: + # pylint: disable=invalid-name,missing-class-docstring + # pylint: disable=unused-variable class prefLabel(owlready2.label): pass @@ -832,7 +833,9 @@ class prefLabel(owlready2.label): # no prefLabel - create new annotation property.. with self: - class prefLabel(owlready2.label): # noqa: F811 + # pylint: disable=invalid-name,missing-class-docstring + # pylint: disable=function-redefined + class prefLabel(owlready2.label): pass ind.prefLabel = [locstr(ind.name, lang="en")] @@ -853,12 +856,12 @@ class prefLabel(owlready2.label): # noqa: F811 ) elif name_policy == "sequential": for obj in chain: - n = 0 - while f"{self.base_iri}{name_prefix}{n}" in self: - n += 1 - obj.name = name_prefix + str(n) + counter = 0 + while f"{self.base_iri}{name_prefix}{counter}" in self: + counter += 1 + obj.name = name_prefix + str(counter) elif name_policy is not None: - raise TypeError("invalid name_policy: %r" % (name_policy,)) + raise TypeError(f"invalid name_policy: {name_policy!r}") if sync_imported: for onto in self.imported_ontologies: @@ -884,15 +887,17 @@ def get_annotations(self, entity): if isinstance(entity, str): entity = self.get_by_label(entity) - d = {"comment": getattr(entity, "comment", "")} - for a in self.annotation_properties(): - d[a.label.first()] = [ - o.strip('"') - for s, p, o in self.get_triples(entity.storid, a.storid, None) + res = {"comment": getattr(entity, "comment", "")} + for annotation in self.annotation_properties(): + res[annotation.label.first()] = [ + obj.strip('"') + for _, _, obj in self.get_triples( + entity.storid, annotation.storid, None + ) ] - return d + return res - def get_branch( + def get_branch( # pylint: disable=too-many-arguments self, root, leafs=(), @@ -928,7 +933,7 @@ def _branch(root, leafs): branch = { root, } - for c in root.subclasses(): + for cls in root.subclasses(): # Defining a branch is actually quite tricky. Consider # the case: # @@ -942,8 +947,8 @@ def _branch(root, leafs): # mentation will see that A is a child of the root and # include it. Requireing that the R should be a strict # parent of A solves this. - if root in c.get_parents(strict=True): - branch.update(_branch(c, leafs)) + if root in cls.get_parents(strict=True): + branch.update(_branch(cls, leafs)) else: branch = ( { @@ -987,7 +992,7 @@ def _branch(root, leafs): # Sort according to depth, then by label if sort: branch = sorted( - sorted(branch, key=lambda x: asstring(x)), + sorted(branch, key=asstring), key=lambda x: len(x.mro()), ) @@ -1013,18 +1018,17 @@ def get_version(self, as_iri=False): If `as_iri` is True, the full versionIRI is returned. """ - versionIRI_storid = self.world._abbreviate( + version_iri_storid = self.world._abbreviate( "http://www.w3.org/2002/07/owl#versionIRI" ) - tokens = self.get_triples(s=self.storid, p=versionIRI_storid) + tokens = self.get_triples(s=self.storid, p=version_iri_storid) if not tokens: - raise TypeError("No versionIRI in Ontology %r" % self.base_iri) - s, p, o = tokens[0] - versionIRI = self.world._unabbreviate(o) + raise TypeError(f"No versionIRI in Ontology {self.base_iri!r}") + _, _, obj = tokens[0] + version_iri = self.world._unabbreviate(obj) if as_iri: - return versionIRI - else: - return infer_version(self.base_iri, versionIRI) + return version_iri + return infer_version(self.base_iri, version_iri) def set_version(self, version=None, version_iri=None): """Assign version to ontology by asigning owl:versionIRI. @@ -1032,10 +1036,12 @@ def set_version(self, version=None, version_iri=None): If `version` but not `version_iri` is provided, the version IRI will be the combination of `base_iri` and `version`. """ - versionIRI = "http://www.w3.org/2002/07/owl#versionIRI" - versionIRI_storid = self.world._abbreviate(versionIRI) - if self._has_obj_triple_spo(s=self.storid, p=versionIRI_storid): - self._del_obj_triple_spo(s=self.storid, p=versionIRI_storid) + _version_iri = "http://www.w3.org/2002/07/owl#versionIRI" + version_iri_storid = self.world._abbreviate(_version_iri) + if self._has_obj_triple_spo( + subject=self.storid, predicate=version_iri_storid + ): + self._del_obj_triple_spo(s=self.storid, p=version_iri_storid) if not version_iri: if not version: @@ -1047,7 +1053,7 @@ def set_version(self, version=None, version_iri=None): self._add_obj_triple_spo( s=self.storid, - p=self.world._abbreviate(versionIRI), + p=self.world._abbreviate(_version_iri), o=self.world._abbreviate(version_iri), ) @@ -1056,11 +1062,13 @@ def get_graph(self, **kwargs): Note that this method requires the Python graphviz package. """ - from ontopy.graph import OntoGraph + # pylint: disable=import-outside-toplevel + from ontopy.graph import OntoGraph as NewOntoGraph - return OntoGraph(self, **kwargs) + return NewOntoGraph(self, **kwargs) - def common_ancestors(self, cls1, cls2): + @staticmethod + def common_ancestors(cls1, cls2): """Return a list of common ancestors for `cls1` and `cls2`.""" return set(cls1.ancestors()).intersection(cls2.ancestors()) @@ -1070,13 +1078,13 @@ def number_of_generations(self, descendant, ancestor): raise ValueError("Descendant is not a descendant of ancestor") return self._number_of_generations(descendant, ancestor, 0) - def _number_of_generations(self, descendant, ancestor, n): + def _number_of_generations(self, descendant, ancestor, counter): """Recursive help function to number_of_generations(), return - distance between a ancestor-descendant pair (n+1).""" + distance between a ancestor-descendant pair (counter+1).""" if descendant.name == ancestor.name: - return n + return counter return min( - self._number_of_generations(parent, ancestor, n + 1) + self._number_of_generations(parent, ancestor, counter + 1) for parent in descendant.get_parents() if ancestor in parent.ancestors() ) @@ -1094,7 +1102,8 @@ def closest_common_ancestors(self, cls1, cls2): if distance == min(distances.values()) ] - def closest_common_ancestor(self, *classes): + @staticmethod + def closest_common_ancestor(*classes): """Returns closest_common_ancestor for the given classes.""" mros = [cls.mro() for cls in classes] track = defaultdict(int) @@ -1124,33 +1133,32 @@ def get_ancestors(self, classes, include="all", strict=True): if not classes: return ancestors - def addancestors(e, n, s): - if n > 0: - for p in e.get_parents(strict=True): - s.add(p) - addancestors(p, n - 1, s) + def addancestors(entity, counter, subject): + if counter > 0: + for parent in entity.get_parents(strict=True): + subject.add(parent) + addancestors(parent, counter - 1, subject) if isinstance(include, str) and include.isdigit(): include = int(include) if include == "all": - ancestors.update(*(c.ancestors() for c in classes)) + ancestors.update(*(_.ancestors() for _ in classes)) elif include == "closest": closest = self.closest_common_ancestor(*classes) - for c in classes: + for cls in classes: ancestors.update( - a for a in c.ancestors() if closest in a.ancestors() + _ for _ in cls.ancestors() if closest in _.ancestors() ) elif isinstance(include, int): - for e in classes: - addancestors(e, int(include), ancestors) + for entity in classes: + addancestors(entity, int(include), ancestors) elif include not in (None, "None", "none", ""): raise ValueError('include must be "all", "closest" or None') if strict: return ancestors.difference(classes) - else: - return ancestors + return ancestors def get_wu_palmer_measure(self, cls1, cls2): """Return Wu-Palmer measure for semantic similarity. @@ -1162,9 +1170,9 @@ def get_wu_palmer_measure(self, cls1, cls2): """ cca = self.closest_common_ancestor(cls1, cls2) ccadepth = self.number_of_generations(cca, self.Thing) - n1 = self.number_of_generations(cls1, cca) - n2 = self.number_of_generations(cls2, cca) - return 2 * ccadepth / (n1 + n2 + 2 * ccadepth) + generations1 = self.number_of_generations(cls1, cca) + generations2 = self.number_of_generations(cls2, cca) + return 2 * ccadepth / (generations1 + generations2 + 2 * ccadepth) def new_entity(self, name, parent): """Create and return new entity @@ -1173,5 +1181,5 @@ def new_entity(self, name, parent): Return the new entity """ with self: - e = types.new_class(name, (parent,)) - return e + entity = types.new_class(name, (parent,)) + return entity diff --git a/setup.py b/setup.py index 80b56ccb5..07dd37228 100644 --- a/setup.py +++ b/setup.py @@ -3,10 +3,11 @@ Python reference API for the Elementary Multiperspective Material Ontology (EMMO). """ +from glob import glob import os import re + import setuptools -from glob import glob rootdir = os.path.dirname(__file__) @@ -14,27 +15,29 @@ def rglob(patt): """Recursive glob function that only returns ordinary files.""" - return [f for f in glob(patt, recursive=True) if os.path.isfile(f)] + return [_ for _ in glob(patt, recursive=True) if os.path.isfile(_)] def fglob(patt): """Glob function that only returns ordinary files.""" - return [f for f in glob(patt) if os.path.isfile(f) and not f.endswith("~")] + return [_ for _ in glob(patt) if os.path.isfile(_) and not _.endswith("~")] # Read long description from README.md file replacing references to local # files to github urls -baseurl = "https://raw.githubusercontent.com/emmo-repo/EMMO-python/master/" -with open(os.path.join(rootdir, "README.md"), "rt") as f: +BASE_URL = "https://raw.githubusercontent.com/emmo-repo/EMMO-python/master/" +with open(os.path.join(rootdir, "README.md"), "rt", encoding="utf8") as handle: long_description = re.sub( - r"(\[[^]]+\])\(([^:)]+)\)", r"\1(%s\2)" % baseurl, f.read() + r"(\[[^]]+\])\(([^:)]+)\)", fr"\1({BASE_URL}\2)", handle.read() ) # Read requirements from requirements.txt file -with open(os.path.join(rootdir, "requirements.txt"), "rt") as f: +with open( + os.path.join(rootdir, "requirements.txt"), "rt", encoding="utf8" +) as handle: REQUIREMENTS = [ f"{_.strip()}" - for _ in f.readlines() + for _ in handle.readlines() if not _.startswith("#") and "git+" not in _ ] @@ -57,7 +60,9 @@ def fglob(patt): ] + DOCS # Retrieve emmo-package version -with open(os.path.join(rootdir, "ontopy/__init__.py")) as handle: +with open( + os.path.join(rootdir, "ontopy/__init__.py"), encoding="utf8" +) as handle: for line in handle: match = re.match(r'__version__ = "(?P.*)"', line) if match is not None: diff --git a/tasks.py b/tasks.py index 98982ab9d..8809abcf5 100644 --- a/tasks.py +++ b/tasks.py @@ -67,7 +67,7 @@ def setver(_, ver=""): "Remove the 'api_reference' sub directory prior to (re)creation." ) } -) +) # pylint: disable=too-many-branches def create_api_reference_docs(context, pre_clean=False, pre_commit=False): """Create the API Reference in the documentation""" import os diff --git a/tools/emmocheck b/tools/emmocheck index 6ce15c13a..a390fa910 100755 --- a/tools/emmocheck +++ b/tools/emmocheck @@ -10,7 +10,7 @@ rootdir = os.path.abspath( ) sys.path.insert(1, rootdir) -from emmopy.emmocheck import main # noqa: E402 +from emmopy.emmocheck import main # pylint: disable=wrong-import-position if __name__ == "__main__": diff --git a/tools/ontoconvert b/tools/ontoconvert index 5923e5066..555919e8e 100755 --- a/tools/ontoconvert +++ b/tools/ontoconvert @@ -18,6 +18,7 @@ from ontopy.factpluspluswrapper.factppgraph import FaCTPPGraph def main(): + """Main run function.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument("input", help="IRI/file to OWL source.") parser.add_argument("output", help="Output file name.") @@ -111,12 +112,12 @@ def main(): url_from_catalog=args.url_from_catalog, ) elif args.inferred: - g = squash_imported(args.input, None, input_format=input_format) - fg = FaCTPPGraph(g) + graph = squash_imported(args.input, None, input_format=input_format) + factpp_graph = FaCTPPGraph(graph) if args.base_iri: - fg.base_iri = args.base_iri - g2 = fg.inferred_graph() - g2.serialize(destination=args.output, format=output_format) + factpp_graph.base_iri = args.base_iri + graph2 = factpp_graph.inferred_graph() + graph2.serialize(destination=args.output, format=output_format) elif args.squash: squash_imported( args.input, @@ -132,7 +133,9 @@ def main(): output_format == FMAP.get("ttl", "") or os.path.splitext(args.output)[1] == "ttl" ): - from rdflib import __version__ as __rdflib_version__ + from rdflib import ( # pylint: disable=import-outside-toplevel + __version__ as __rdflib_version__, + ) warnings.warn( IncompatibleVersion( @@ -144,9 +147,9 @@ def main(): ) ) - g = Graph() - g.parse(args.input, format=input_format) - g.serialize(destination=args.output, format=output_format) + graph = Graph() + graph.parse(args.input, format=input_format) + graph.serialize(destination=args.output, format=output_format) for warning in warnings_handle: print( diff --git a/tools/ontodoc b/tools/ontodoc index 357575c61..039bb6ca9 100755 --- a/tools/ontodoc +++ b/tools/ontodoc @@ -1,4 +1,6 @@ #!/usr/bin/env python3 +"""`ontodoc` CLI tool.""" +# pylint: disable=wrong-import-position,wrong-import-order import os import sys import argparse @@ -10,7 +12,7 @@ rootdir = os.path.abspath( ) sys.path.insert(1, rootdir) -from ontopy import World, onto_path # noqa: E402 +from ontopy import World, onto_path from ontopy.ontodoc import ( OntoDoc, get_format, @@ -18,12 +20,13 @@ from ontopy.ontodoc import ( get_figformat, get_maxwidth, get_docpp, -) # noqa: E402 +) -import owlready2 # noqa: E402 +import owlready2 def main(): + """Main run function.""" parser = argparse.ArgumentParser( description="Tool for documenting ontologies.", epilog=( @@ -211,20 +214,20 @@ def main(): url_from_catalog=args.url_from_catalog, catalog_file=args.catalog_file, ) - except owlready2.OwlReadyOntologyParsingError as e: - parser.error("error parsing %r: %s" % (args.iri, e)) + except owlready2.OwlReadyOntologyParsingError as exc: + parser.error(f"error parsing {args.iri!r}: {exc}") # Sync reasoner if args.reasoner: onto.sync_reasoner(reasoner=args.reasoner) # Instantiate ontodoc instance - format = get_format(args.outfile, args.format) - style = get_style(format) - figformat = args.figformat if args.figformat else get_figformat(format) - maxwidth = args.max_figwidth if args.max_figwidth else get_maxwidth(format) + fmt = get_format(args.outfile, args.format) + style = get_style(fmt) + figformat = args.figformat if args.figformat else get_figformat(fmt) + maxwidth = args.max_figwidth if args.max_figwidth else get_maxwidth(fmt) ontodoc = OntoDoc(onto, style=style) - docpp = get_docpp( + res = get_docpp( ontodoc, args.template, figdir=args.figdir, @@ -232,20 +235,20 @@ def main(): maxwidth=maxwidth, imported=args.imported, ) - docpp.process() + res.process() try: - docpp.write( + res.write( args.outfile, format=args.format, pandoc_option_files=args.pandoc_option_files, pandoc_options=args.pandoc_options, genfile=args.genfile, ) - except subprocess.CalledProcessError as e: - os._exit(e.returncode) # Exit without traceback on pandoc errors + except subprocess.CalledProcessError as exc: + sys.exit(exc.returncode) # Exit without traceback on pandoc errors - return docpp + return res if __name__ == "__main__": diff --git a/tools/ontograph b/tools/ontograph index 569e3f156..aceeee3dc 100755 --- a/tools/ontograph +++ b/tools/ontograph @@ -1,5 +1,6 @@ #!/usr/bin/env python3 """Tool for visualising ontologies.""" +# pylint: disable=wrong-import-position,wrong-import-order import sys import os import argparse @@ -11,13 +12,14 @@ rootdir = os.path.abspath( ) sys.path.insert(1, rootdir) -from ontopy import World, onto_path # noqa: E402 -from ontopy.graph import OntoGraph, plot_modules, _default_style # noqa: E402 +from ontopy import World, onto_path +from ontopy.graph import OntoGraph, plot_modules, _default_style from ontopy.utils import get_label -import owlready2 # noqa: E402 +import owlready2 -def main(): +def main(): # pylint: disable=too-many-locals,too-many-branches,too-many-statements + """Main run function.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "iri", @@ -205,13 +207,13 @@ def main(): # Update style from json file if args.style_file: - with open(args.style_file, "rt") as f: - style.update(json.load(f)) + with open(args.style_file, "rt", encoding="utf8") as handle: + style.update(json.load(handle)) # Write style to json file if args.generate_style_file: - with open(args.generate_style_file, "wt") as f: - json.dump(style, f, indent=4) + with open(args.generate_style_file, "wt", encoding="utf8") as handle: + json.dump(style, handle, indent=4) # Join all --leafs options leafs = set() @@ -223,11 +225,11 @@ def main(): # Join all --exclude options exclude = set() - for e in args.exclude: - if "," in e: - exclude.update(e.split(",")) + for exclude_option in args.exclude: + if "," in exclude_option: + exclude.update(exclude_option.split(",")) else: - exclude.add(e) + exclude.add(exclude_option) # Split relations relations = None @@ -248,8 +250,8 @@ def main(): url_from_catalog=args.url_from_catalog, catalog_file=args.catalog_file, ) - except owlready2.OwlReadyOntologyParsingError as e: - parser.error("error parsing %r: %s" % (args.iri, e)) + except owlready2.OwlReadyOntologyParsingError as exc: + parser.error(f"error parsing {args.iri!r}: {exc}") # Sync reasoner if args.reasoner: diff --git a/tools/ontoversion b/tools/ontoversion index c245612f3..74e10b2d6 100755 --- a/tools/ontoversion +++ b/tools/ontoversion @@ -4,8 +4,8 @@ This script uses rdflib and the versionIRI tag on an ontology to infer the version. """ -import os import argparse +import sys import rdflib @@ -15,17 +15,18 @@ def infer_version(iri, version_iri): if str(version_iri[: len(iri)]) == str(iri): version = version_iri[len(iri) :] else: - j = 0 - v = [] - for i in range(len(iri)): - while iri[i] != version_iri[i + j]: - v.append(version_iri[i + j]) - j += 1 - version = "".join(v) + counter = 0 + version_parts = [] + for index, _ in enumerate(iri): + while iri[index] != version_iri[index + counter]: + version_parts.append(version_iri[index + counter]) + counter += 1 + version = "".join(version_parts) return version.lstrip("/").rstrip("/#") def main(): + """Main run function.""" parser = argparse.ArgumentParser(description=__doc__) parser.add_argument( "iri", @@ -37,14 +38,14 @@ def main(): ) try: args = parser.parse_args() - except SystemExit as e: - os._exit(e.code) # Exit without traceback on invalid arguments + except SystemExit as exc: + sys.exit(exc.code) # Exit without traceback on invalid arguments # Extract base IRI and versionIRI - g = rdflib.Graph() - g.parse(args.iri.rstrip("/#"), format=args.format) + graph = rdflib.Graph() + graph.parse(args.iri.rstrip("/#"), format=args.format) iri, version_iri = list( - g.subject_objects( + graph.subject_objects( rdflib.URIRef("http://www.w3.org/2002/07/owl#versionIRI") ) )[0]