funowl
Advanced tools
| tests/data/cr.ofn.txt eol=crlf |
| [console_scripts] | ||
| funowl = funowl.cli:evaluate_cli | ||
| import sys | ||
| from argparse import ArgumentParser | ||
| from typing import Optional, Union, List | ||
| from rdflib import Graph | ||
| from rdflib.plugin import plugins as rdflib_plugins | ||
| from rdflib.serializer import Serializer as rdflib_serializer | ||
| from rdflib.util import guess_format | ||
| from .converters.functional_converter import to_python | ||
| valid_formats = ["ttl"] + sorted( | ||
| [x.name for x in rdflib_plugins(None, rdflib_serializer) if "/" not in str(x.name)] | ||
| ) | ||
| DEFAULT_FORMAT = "ttl" | ||
| def genargs(prog: Optional[str] = None) -> ArgumentParser: | ||
| """ | ||
| Create a command line parser | ||
| :return: parser | ||
| """ | ||
| parser = ArgumentParser(prog, description="Convert OWL Functional Syntax to RDF") | ||
| parser.add_argument("input", help="Input OWL functional syntax. Can be a file name or URL") | ||
| parser.add_argument("output", help="Output file. If omitted, output goes to stdout", nargs='?') | ||
| parser.add_argument("-f", "--format", help="Output RDF Format. If omitted, guess from output file suffix.\n" | ||
| " If guessing doesn't work, assume 'turtle'", | ||
| choices=valid_formats) | ||
| parser.add_argument("-np", "--noProgressBar", help="Don't output the progress indicators", action="store_true") | ||
| return parser | ||
| def evaluate_cli(argv: Optional[Union[str, List[str]]] = None, prog: Optional[str] = None) -> int: | ||
| if isinstance(argv, str): | ||
| argv = argv.split() | ||
| opts = genargs(prog).parse_args(argv if argv is not None else sys.argv[1:]) | ||
| # Read the functional syntax ontology | ||
| ontology = to_python(opts.input, print_progress=bool(opts.output) and not opts.noProgressBar) | ||
| # Convert to RDF | ||
| g = Graph() | ||
| ontology.to_rdf(g) | ||
| # Emit as appropriate | ||
| if opts.output: | ||
| g.serialize(opts.output, format=opts.format or guess_format(opts.output) or DEFAULT_FORMAT) | ||
| else: | ||
| print(g.serialize(format=opts.format or DEFAULT_FORMAT)) | ||
| return 0 | ||
| if __name__ == '__main__': | ||
| evaluate_cli(sys.argv[1:]) |
| Prefix(:=<http://www.w3.org/2002/07/owl#>) | ||
| Prefix(owl:=<http://www.w3.org/2002/07/owl#>) | ||
| Prefix(rdf:=<http://www.w3.org/1999/02/22-rdf-syntax-ns#>) | ||
| Prefix(xml:=<http://www.w3.org/XML/1998/namespace>) | ||
| Prefix(xsd:=<http://www.w3.org/2001/XMLSchema#>) | ||
| Prefix(rdfs:=<http://www.w3.org/2000/01/rdf-schema#>) | ||
| Ontology(<http://www.w3.org/2002/07/owl> | ||
| <http://www.w3.org/2002/07/owl> | ||
| Import(<http://www.w3.org/2000/01/rdf-schema>) | ||
| Annotation(<http://purl.org/dc/elements/1.1/title> "The OWL 2 Schema vocabulary (OWL 2)") | ||
| Annotation(rdfs:comment " | ||
| This ontology partially describes the built-in classes and | ||
| properties that together form the basis of the RDF/XML syntax of OWL 2. | ||
| The content of this ontology is based on Tables 6.1 and 6.2 | ||
| in Section 6.4 of the OWL 2 RDF-Based Semantics specification, | ||
| available at http://www.w3.org/TR/owl2-rdf-based-semantics/. | ||
| Please note that those tables do not include the different annotations | ||
| (labels, comments and rdfs:isDefinedBy links) used in this file. | ||
| Also note that the descriptions provided in this ontology do not | ||
| provide a complete and correct formal description of either the syntax | ||
| or the semantics of the introduced terms (please see the OWL 2 | ||
| recommendations for the complete and normative specifications). | ||
| Furthermore, the information provided by this ontology may be | ||
| misleading if not used with care. This ontology SHOULD NOT be imported | ||
| into OWL ontologies. Importing this file into an OWL 2 DL ontology | ||
| will cause it to become an OWL 2 Full ontology and may have other, | ||
| unexpected, consequences. | ||
| ") | ||
| Annotation(rdfs:isDefinedBy <http://www.w3.org/TR/owl2-mapping-to-rdf/>) | ||
| Declaration(AnnotationProperty(<http://purl.org/dc/elements/1.1/title>)) | ||
| ) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| usage: cli [-h] | ||
| [-f {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}] | ||
| [-np] | ||
| input [output] | ||
| Convert OWL Functional Syntax to RDF | ||
| positional arguments: | ||
| input Input OWL functional syntax. Can be a file name or URL | ||
| output Output file. If omitted, output goes to stdout | ||
| options: | ||
| -h, --help show this help message and exit | ||
| -f {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}, --format {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml} | ||
| Output RDF Format. If omitted, guess from output file | ||
| suffix. If guessing doesn't work, assume 'turtle' | ||
| -np, --noProgressBar Don't output the progress indicators |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
| import argparse | ||
| import os.path | ||
| import unittest | ||
| from contextlib import redirect_stdout | ||
| from io import StringIO | ||
| from typing import List | ||
| from funowl import cli | ||
| CWD = os.path.dirname(__file__) | ||
| TEST_DATA_DIR = os.path.join(CWD, 'data') | ||
| UPDATE_OUTPUT_FILES = False | ||
| CLI_NAME = 'cli' | ||
| # Override the annoying argparse insistence on doing a sys.exit after help printed | ||
| class HelpPrintedException(Exception): | ||
| pass | ||
| def exit(parser, status=0, message=None): | ||
| raise HelpPrintedException(message) | ||
| class CLITestCase(unittest.TestCase): | ||
| @staticmethod | ||
| def _file_or_text(inp: str) -> str: | ||
| if '\n' in inp: | ||
| return inp | ||
| with open(os.path.join(TEST_DATA_DIR, inp)) as f: | ||
| return f.read() | ||
| def _compare_output(self, expected, actual) -> bool: | ||
| e_str = self._file_or_text(expected) | ||
| a_str = self._file_or_text(actual) | ||
| if e_str == a_str: | ||
| return True | ||
| # If expected is in the form of a file name, update the output | ||
| if e_str != expected: # expected is a file name | ||
| print(f">>> File: {e_str} has changed") | ||
| if UPDATE_OUTPUT_FILES: | ||
| with open(os.path.join(TEST_DATA_DIR, expected), 'w') as f: | ||
| f.write(actual) | ||
| return False or not UPDATE_OUTPUT_FILES | ||
| def _generate_file(self, infilename: str, outfilename: str, addl_params: List[str] = None) -> None: | ||
| args = [os.path.join(TEST_DATA_DIR, infilename), os.path.join(TEST_DATA_DIR, outfilename)] | ||
| if addl_params: | ||
| args += addl_params | ||
| cli.evaluate_cli(args, CLI_NAME) | ||
| def test_cli_help(self): | ||
| """ Test help function """ | ||
| orig_exit = argparse.ArgumentParser.exit | ||
| argparse.ArgumentParser.exit = exit | ||
| with self.assertRaises(HelpPrintedException): | ||
| out_txt = StringIO() | ||
| with redirect_stdout(out_txt): | ||
| cli.evaluate_cli("-h", CLI_NAME) | ||
| argparse.ArgumentParser.exit = orig_exit | ||
| if not self._compare_output('cli_help.txt', out_txt.getvalue()): | ||
| self.fail(msg="Help output has changed!") | ||
| def test_cli_conversion(self): | ||
| """ Test basic conversion """ | ||
| self._generate_file('pizza.owl', 'pizza_out.ttl') | ||
| def test_auto_output_type(self): | ||
| """ Test that output file name suffixes are recognized """ | ||
| # Should generate json-ld | ||
| self._generate_file('pizza.owl', 'pizza_out.jsonld') | ||
| # Should generate owl (explicit) | ||
| self._generate_file('pizza.owl', 'pizza_out2.jsonld', ["-f", "hext"]) | ||
| # Not sure | ||
| self._generate_file('pizza.owl', 'pizza_out3.foo') | ||
| def test_cli_options(self): | ||
| """ Make sure that file name suffixes are recognized """ | ||
| cli.evaluate_cli( | ||
| [os.path.join(TEST_DATA_DIR, 'basic.owl'), os.path.join(TEST_DATA_DIR, 'basic_out.n3')], | ||
| CLI_NAME) | ||
| # Dots shouldn't be printed if "-np" or output is to stdout | ||
| output = StringIO() | ||
| with redirect_stdout(output): | ||
| cli.evaluate_cli( | ||
| [os.path.join(TEST_DATA_DIR, 'pizza.owl'), os.path.join(TEST_DATA_DIR, 'pizza_out4.n3'), '-np'], | ||
| CLI_NAME) | ||
| cli.evaluate_cli( | ||
| [os.path.join(TEST_DATA_DIR, 'pizza.owl')], | ||
| CLI_NAME) | ||
| self.assertNotIn(".....", output.getvalue()) | ||
| if __name__ == '__main__': | ||
| unittest.main() |
| import os | ||
| import unittest | ||
| from io import StringIO | ||
| from funowl.converters.functional_converter import to_python | ||
| from tests import datadir | ||
| class CRTestCase(unittest.TestCase): | ||
| """ Test issue_45 - error thrown if literals contain carriage returns """ | ||
| def test_cr_issue(self): | ||
| owl = None | ||
| with open(os.path.join(datadir, 'cr.ofn.txt')) as f: | ||
| owl = to_python(f.read()) | ||
| self.assertTrue(str(owl.ontology.annotations[1].value.v).startswith("\n This ontology partially")) | ||
| if __name__ == '__main__': | ||
| unittest.main() |
| import os | ||
| import unittest | ||
| from rdflib import Graph | ||
| from funowl.converters.functional_converter import to_python | ||
| from tests import datadir | ||
| class FunctionalToRDFTestCase(unittest.TestCase): | ||
| """ Demo of how one goes about converting functional syntax to RDF """ | ||
| def test_fun_to_rdf(self): | ||
| # The functional syntax input can be a string, URL, file loc or open file | ||
| function_pizza = os.path.join(datadir, 'pizza.owl') | ||
| internal_pizza = to_python(function_pizza) | ||
| # Emit the internal representation as an rdflib graph | ||
| g = Graph() | ||
| internal_pizza.to_rdf(g) | ||
| # Serialize the rdflib graph in your favorite format | ||
| turtle_pizza = os.path.join(datadir, 'pizza_out.ttl') | ||
| g.serialize(turtle_pizza) | ||
| if __name__ == '__main__': | ||
| unittest.main() |
| import unittest | ||
| from dataclasses import dataclass | ||
| from typing import Tuple, Optional, List, Dict | ||
| import rdflib | ||
| import rdflib_shim | ||
| # Make sure the import above works | ||
| shimed = rdflib_shim.RDFLIB_SHIM | ||
| # Code to evaluate how we can use the rdflib namespace library to manage our namespace behavior. What we need in | ||
| # funowl is: | ||
| # 1) Assign multiple prefixes to the same URI including the default ('') prefix | ||
| # 2) Map a curie (well - strictly speaking, PNAME_LN as defined in | ||
| # https://www.w3.org/TR/2008/REC-rdf-sparql-query-20080115/#QSynIRI) to a corresponding URI using _any_ | ||
| # prefix defined in the Prefixes section | ||
| # 3) Map a URI to a curie: | ||
| # a) Using the longest mapped URI possible and | ||
| # b) Using the most recent declared URI mapping in the case of duplicates | ||
| # 4) Validate and, when necessary escape URI syntax | ||
| # 5) Raise an exception when the same prefix is mapped to two different URIs | ||
| # 6) Raise an exception when the following prefixes (from table 2 in the functional spec) are declared | ||
| # rdf, rdfs, xsd, owl | ||
| # | ||
| # The closest we can get in rdflib 6.2.0 is | ||
| # 1) We NEVER want to use bind(..., replace=False, ...). Mangled namespaces are not good things. | ||
| # 2) We have to preserve ALL prefix/URI mappings, not just the latest | ||
| # 3) We have to reject any duplicate prefix declarations | ||
| # 2) RDFlib 6.2.0 treats namespaces as a unique dictionary -- you can never have more than one prefix. This is NOT | ||
| # the behavior we need to implement. | ||
| # 3) the namespace_with_bind_override_fix == False appears to replicate some sort of earlier bug that we don't care | ||
| # about. | ||
| EX = "http://example.org/ns1#" | ||
| EXNS = rdflib.Namespace(EX) | ||
| EXURI = rdflib.URIRef(EX) | ||
| EX2 = "http://example.org/ns2#" | ||
| EX2NS = rdflib.Namespace(EX2) | ||
| EX2URI = rdflib.URIRef(EX2) | ||
| PRINT_OUTPUT = False # Print tabular output | ||
| using_rdflib_v6 = rdflib.__version__ >= "6.2" | ||
| using_rdflib_v5 = rdflib.__version__.startswith("5.0.0") | ||
| ignore_prefixes = ['xml', 'rdf', 'rdfs', 'xsd', 'owl'] | ||
| print(f"===== TESTING RDFLIB VERSION: {rdflib.__version__}") | ||
| @unittest.skipUnless(using_rdflib_v5 or using_rdflib_v6, "Tests skipped on unrecognized versions of rdflib") | ||
| class RDFLIBNamespaceBindingTestCase(unittest.TestCase): | ||
| """ | ||
| Until recently, we have been using the rdflib namespace manager to handle the functional declarations. The 6.x | ||
| release of rdflib, however, has linked the namespace management more tightly with the behavior expected in | ||
| the RDF world. | ||
| """ | ||
| @dataclass | ||
| class EvalResult: | ||
| patch: bool # rdflib.namespace._with_bind_override_fix | ||
| replace: bool | ||
| override: bool | ||
| result: List[Tuple[str, str]] | ||
| @staticmethod | ||
| def hdr() -> str: | ||
| """ Print a table header """ | ||
| return "patch\treplace\toverride\tresult\n-----\t-------\t--------\t--------------" | ||
| def __str__(self): | ||
| """ Print the actual result """ | ||
| result_str = ' '.join([f"({prefix}:, {ns})" for prefix, ns in self.result]) | ||
| return f"{self.patch}\t{self.replace}\t{self.override}\t\t{result_str}" | ||
| @staticmethod | ||
| def print_output(hdr: str) -> None: | ||
| if PRINT_OUTPUT: | ||
| print(f"\n===== {hdr} =====") | ||
| print(RDFLIBNamespaceBindingTestCase.EvalResult.hdr()) | ||
| @staticmethod | ||
| def graph_bindings_dict(g: rdflib.Graph) -> Dict[str, str]: | ||
| """ Return non-iggnored graph namespaces as a dictionary """ | ||
| return {prefix: str(uri) for prefix, uri in g.namespaces() if prefix not in ignore_prefixes} | ||
| @staticmethod | ||
| def graph_bindings_tuples(g: rdflib.Graph) -> List[Tuple[str, str]]: | ||
| """ Return non-ignored graph namespaces as tuples """ | ||
| return [(prefix, uri) for prefix, uri in g.namespaces() if prefix not in ignore_prefixes] | ||
| @staticmethod | ||
| def eval_options(bindings: List[Tuple[Optional[str], str]]) -> List[EvalResult]: | ||
| """ Evaluate how _bindings_ are interpreted using the _replace_, _override_ and _patch_ variables """ | ||
| rval = [] | ||
| for patch in (True, False): | ||
| for replace in (True, False): | ||
| for override in (True, False): | ||
| rdflib.namespace._with_bind_override_fix = patch | ||
| g = rdflib.Graph() | ||
| for prefix, ns in bindings: | ||
| g.bind(prefix, ns, override=override, replace=replace) | ||
| rval.append( | ||
| RDFLIBNamespaceBindingTestCase.EvalResult( | ||
| patch, replace, override, RDFLIBNamespaceBindingTestCase.graph_bindings_tuples(g))) | ||
| return rval | ||
| def run_test(self, hdr: str, bindings: List[Tuple[Optional[str], str]]) -> List[EvalResult]: | ||
| """ Run a particular binding test and return the result """ | ||
| self.print_output(hdr) | ||
| rslt = self.eval_options(bindings) | ||
| if PRINT_OUTPUT: | ||
| for opt in self.eval_options(bindings): | ||
| print(str(opt)) | ||
| return rslt | ||
| def test_decl_default(self): | ||
| """ Test a namespace followed by a default for the same URI """ | ||
| rslt = self.run_test("EX: ns1, : ns1", [('EX', EX), (None, EX)]) | ||
| for er in rslt: | ||
| # We should have two declarations | ||
| if er.override: | ||
| self.assertEqual(2, len(er.result)) | ||
| self.assertEqual({('EX', EXURI), ('', EXURI)}, set(er.result)) | ||
| def test_decl_default_rev(self): | ||
| """ Test a default followed by a namespace for the same URI """ | ||
| rslt = self.run_test(": ns1, EX: ns1", [(None, EXURI), ('EX', EXURI)]) | ||
| for er in rslt: | ||
| # We should have two declarations | ||
| if er.override: | ||
| self.assertEqual(2, len(er.result)) | ||
| self.assertEqual({('EX', EXURI), ('', EXURI)}, set(er.result)) | ||
| def test_two_namespaces(self): | ||
| """ Test two prefixes with the same URI """ | ||
| rslt = self.run_test("EXA: ns1, EXB: ns1", [('EXA', EXURI), ('EXB', EXURI)]) | ||
| for er in rslt: | ||
| # We should have two declarations | ||
| if er.override: | ||
| self.assertEqual(2, len(er.result)) | ||
| self.assertEqual({('EXA', EXURI), ('EXB', EXURI)}, set(er.result)) | ||
| def test_two_diff_namespaces(self): | ||
| """ Test the same prefix with two different URIs | ||
| rdlib 6.2.0 -- if _replace_ and _override_ use last namespace | ||
| if _replace_ and not _override_ and _patch_ use first namespace | ||
| if _replace_ and not _override_ and not _patch_ use last namespace | ||
| if not _replace_ mangle second namespace (NEVER desirable) | ||
| """ | ||
| rslt = self.run_test("EXA: ns1, EXA: ns2", [('EXA', EX), ('EXA', EX2)]) | ||
| for er in rslt: | ||
| # We should have two declarations | ||
| if er.replace: | ||
| self.assertEqual(1, len(er.result)) | ||
| self.assertEqual({('EXA', EX2URI)}, set(er.result)) | ||
| def test_two_defaults(self): | ||
| """ | ||
| Test two default declarations for the same URI | ||
| rdlib 6.2.0 -- if _replace_ and _override_ use last namespace | ||
| -- if _replace_ and not _override_ use first namespace | ||
| -- if not _replace_ last namespace prefix is "default1" (NEVER desirable) | ||
| """ | ||
| rslt = self.run_test(": ns1, : ns2", [(None, EX), (None, EX2)]) | ||
| for evalresult in rslt: | ||
| # We should have two declarations | ||
| if evalresult.replace: | ||
| self.assertEqual(1, len(evalresult.result)) | ||
| self.assertEqual({('', EX2URI)}, set(evalresult.result)) | ||
| def test_three_turtle_namespaces(self): | ||
| """ Examine how rdflib handles two namespaces and a default w/ same URI """ | ||
| turtle = """@prefix : <http://example.org/ns1#> . | ||
| @prefix NSA: <http://example.org/ns1#> . | ||
| @prefix NSB: <http://example.org/ns1#> . | ||
| NSA:foo NSB:bar :fee .""" | ||
| # The n3 (turtle) parser maintains its own namespace system to map the above code to the correct URI's | ||
| g = rdflib.Graph() | ||
| g.parse(data=turtle, format="turtle") | ||
| output_ttl = g.serialize(format="turtle") | ||
| # V6 returns a string, V5 returns a bytearray. This is NOT backwards compatible | ||
| # rdflib_shim takes care of this issue | ||
| # In both versions, only the last version is preserved on output | ||
| self.assertEqual("""@prefix NSB: <http://example.org/ns1#> . | ||
| NSB:foo NSB:bar NSB:fee .""", output_ttl.strip()) | ||
| nsdict = self.graph_bindings_dict(g) | ||
| # NSB exists in both versions | ||
| self.assertIn("NSB", nsdict) | ||
| # But NSA and default is only available in v5 | ||
| if using_rdflib_v5: | ||
| self.assertIn("NSA", nsdict) | ||
| self.assertIn("", nsdict) | ||
| self.assertEqual({'': 'http://example.org/ns1#', | ||
| 'NSA': 'http://example.org/ns1#', | ||
| 'NSB': 'http://example.org/ns1#'}, nsdict) | ||
| elif using_rdflib_v6: | ||
| self.assertNotIn("NSA", nsdict) | ||
| self.assertNotIn("", nsdict) | ||
| self.assertEqual({'NSB': 'http://example.org/ns1#'}, nsdict) | ||
| # All the above are expected to fail in v6 and pass in v5 | ||
| test_decl_default.__unittest_expecting_failure__ = using_rdflib_v6 | ||
| test_decl_default_rev.__unittest_expecting_failure__ = using_rdflib_v6 | ||
| test_two_defaults.__unittest_expecting_failure__ = using_rdflib_v6 | ||
| test_two_diff_namespaces.__unittest_expecting_failure__ = using_rdflib_v6 | ||
| test_two_namespaces.__unittest_expecting_failure__ = using_rdflib_v6 | ||
| @unittest.skipUnless(using_rdflib_v6, "expand_curie is only a v6 function") | ||
| def test_rdflib_v6_expand_curie(self): | ||
| """ Evaluate v6 (only) curie expansion function """ | ||
| turtle = """@prefix : <http://example.org/ns1#> . | ||
| @prefix NSA: <http://example.org/ns1#> . | ||
| @prefix NSB: <http://example.org/ns1#> . | ||
| NSA:foo NSB:bar :fee .""" | ||
| # The n3 (turtle) parser maintains its own namespace system to map the above code to the correct URI's | ||
| g = rdflib.Graph() | ||
| g.parse(data=turtle, format="turtle") | ||
| # The good news is that v6 has an _expand_curie_ function: | ||
| self.assertEqual("http://example.org/ns1#test1", str(g.namespace_manager.expand_curie("NSB:test1"))) | ||
| # The bad news, however, is 1) It won't work against all curies | ||
| with self.assertRaises(ValueError) as e: | ||
| self.assertEqual("http://example.org/ns1#test1", str(g.namespace_manager.expand_curie("NSA:test1"))) | ||
| self.assertIn('Prefix "NSA" not bound to any namespace', str(e)) | ||
| # and 2) it doesn't recognize the default namespace, period | ||
| with self.assertRaises(ValueError) as e: | ||
| self.assertEqual("http://example.org/ns1#test1", str(g.namespace_manager.expand_curie(":test1"))) | ||
| self.assertIn('Malformed curie argument', str(e)) | ||
| # Even if we explicitly add it | ||
| g.bind("", "http://example.org/ns1#", override=True, replace=True) | ||
| self.assertIn("", {prefix: ns for prefix, ns in g.namespaces()}) | ||
| with self.assertRaises(ValueError) as e: | ||
| self.assertEqual("http://example.org/ns1#test1", str(g.namespace_manager.expand_curie(":test1"))) | ||
| self.assertIn('Malformed curie argument', str(e), "expand_curie simply doesn't recognize defaults") | ||
| def test_rdflib_heisenberg(self): | ||
| """ In which we demonstrate that the act of observing namespaces changes them """ | ||
| # If we wait until the end to print the bindings, the default namespace goes away | ||
| g = rdflib.Graph() | ||
| g.bind('', EX) | ||
| g.bind('NSA', EX) | ||
| g.add((EXNS.s1, EXNS.p1, EXNS.o1)) | ||
| g.add((EXNS.s2, EXNS.p2, EXNS.o2)) | ||
| self.assertEqual("""@prefix NSA: <http://example.org/ns1#> . | ||
| NSA:s1 NSA:p1 NSA:o1 . | ||
| NSA:s2 NSA:p2 NSA:o2 .""", g.serialize(format="turtle").strip()) | ||
| # If we print the bindings in the middle, both namespaces remain | ||
| g = rdflib.Graph() | ||
| g.bind('', EX) | ||
| g.add((EXNS.s1, EXNS.p1, EXNS.o1)) | ||
| g.serialize(format="turtle") | ||
| g.bind('NSA', EX) | ||
| g.add((EXNS.s2, EXNS.p2, EXNS.o2)) | ||
| self.assertEqual("""@prefix : <http://example.org/ns1#> . | ||
| @prefix NSA: <http://example.org/ns1#> . | ||
| :s1 :p1 :o1 . | ||
| NSA:s2 NSA:p2 NSA:o2 .""", g.serialize(format="turtle").strip()) | ||
| if __name__ == '__main__': | ||
| unittest.main() |
| import unittest | ||
| from rdflib import Graph, URIRef | ||
| class RDFLIBCacheBugTestCase(unittest.TestCase): | ||
| def test_cache_bug(self): | ||
| g = Graph() | ||
| # Prime the cache w/ myns | ||
| g.parse(data=""" | ||
| @prefix MYNS: <http://funkyurl.org/> . | ||
| MYNS:Foo a MYNS:Bar .""", format="turtle") | ||
| g.serialize(format="turtle") | ||
| # Remove MYNS | ||
| g.bind('YOURNS', 'http://funkyurl.org/') | ||
| # Re-add MYNS with a different URL | ||
| g.bind('MYNS', 'http://gotcha.org/', replace=True) | ||
| g.add((URIRef('http://funkyurl.org/SAM'), URIRef('http://funkyurl.org/SAM'), URIRef('http://funkyurl.org/SAM'))) | ||
| self.assertEqual("""@prefix MYNS: <http://funkyurl.org/> . | ||
| @prefix YOURNS: <http://funkyurl.org/> . | ||
| MYNS:Foo a MYNS:Bar . | ||
| YOURNS:SAM YOURNS:SAM YOURNS:SAM .""", g.serialize(format="turtle").decode().strip()) | ||
| if __name__ == '__main__': | ||
| unittest.main() |
| import re | ||
| from typing import Tuple, Set, Optional | ||
| from unittest import TestCase | ||
| from rdflib import RDFS | ||
| prefixes_re = re.compile(r"^(?:\s*Prefix\( (.*?) \))", flags=re.MULTILINE) | ||
| ontology_re = re.compile(r"Ontology\((.*)\)\s*$", flags=re.MULTILINE+re.DOTALL) | ||
| def _parse_output(t: str) -> Tuple[Set[str], str]: | ||
| """ Convert a functional syntax output into a list of prefixes and the ontology definition""" | ||
| prefixes = set() | ||
| m = None | ||
| for m in prefixes_re.finditer(t): | ||
| prefixes.add(m.group(1)) | ||
| lastcol = m.regs[0][1] if m is not None else 0 | ||
| ontology = ontology_re.findall(t[lastcol:]) | ||
| return prefixes, ontology[0] if len(ontology) else "" | ||
| def compare_functional_output(expected: str, actual: str, caller: TestCase, msg: Optional[str]) -> None: | ||
| """ Compare expected functional syntax to actual functional syntax taking random order of prefixes into account """ | ||
| expected_prefixes, expected_ontology = _parse_output(expected) | ||
| actual_prefixes, actual_ontology = _parse_output(actual) | ||
| if actual_prefixes != expected_prefixes: | ||
| in_actual_only = sorted(list(actual_prefixes - expected_prefixes)) | ||
| in_expected_only = sorted(list(expected_prefixes - actual_prefixes)) | ||
| if in_actual_only: | ||
| in_actual_list = "\n\t\t".join(sorted(in_actual_only)) | ||
| expanded_msg = f"""\n\tUnexpected prefixes:\n\t\t{in_actual_list}\n\t""" | ||
| else: | ||
| expanded_msg = "" | ||
| if in_expected_only: | ||
| # If the only issue is an extra RDFS, let it slide. Older rdflibs put it in unasked | ||
| if not in_actual_only and len(in_expected_only) == 1 and str(RDFS) in in_expected_only[0]: | ||
| pass | ||
| else: | ||
| in_expected_list = "\n\t\t".join(sorted(in_expected_only)) | ||
| expanded_msg += ("\n\t" if expanded_msg else "") + f"""Missing prefixes:\n\t\t{in_expected_list}""" | ||
| if expanded_msg: | ||
| caller.fail(expanded_msg + (msg if msg else "")) | ||
| caller.assertEqual(expected_ontology, actual_ontology, msg) | ||
| if __name__ == '__main__': | ||
| inside = """A bunch o | ||
| Inside | ||
| stuff | ||
| Including a "deeply 'embedded Prefix( foo = bar )` as well as a paren and an inner ) Ontology( NOT HERE!!! ) | ||
| """ | ||
| test = f"""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
| Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| Prefix( xsd: = <http://www.w3.org/2001/XMLSchema#> ) | ||
| Prefix( owl: = <http://www.w3.org/2002/07/owl#> ) | ||
| Ontology({inside} )""" | ||
| test2 = """ Prefix( xml: = <http://example.org/xml2#> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
| Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| Prefix( xsd: = <http://www.w3.org/2001/XMLSchema#> ) | ||
| Prefix( owl: = <http://www.w3.org/2002/07/owl#> ) | ||
| Prefix( : = <http://www.w3.org/2002/07/owl#> )""" | ||
| test2_prefixes = {'xsd: = <http://www.w3.org/2001/XMLSchema#>', | ||
| 'owl: = <http://www.w3.org/2002/07/owl#>', | ||
| 'rdfs: = <http://www.w3.org/2000/01/rdf-schema#>', | ||
| ': = <http://www.w3.org/2002/07/owl#>', | ||
| 'xml: = <http://example.org/xml2#>', | ||
| 'rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#>'} | ||
| class QuickTest(TestCase): | ||
| def test_comparator(self): | ||
| compare_functional_output(test, test, self, "Testing utility failed") | ||
| def test_parser(self): | ||
| self.assertEqual(_parse_output(test2), (test2_prefixes, "")) | ||
| compare_functional_output(test2, test2, self, "Testing utility failed 2") | ||
| qt = QuickTest() | ||
| qt.test_comparator() | ||
| qt.test_parser() | ||
| print("compare_functional_output passes") |
@@ -8,2 +8,3 @@ name: Build | ||
| update-requirements: | ||
| if: False | ||
| runs-on: ubuntu-latest | ||
@@ -14,3 +15,3 @@ steps: | ||
| with: | ||
| python-version: 3.8 | ||
| python-version: 3.11 | ||
| - uses: dschep/install-pipenv-action@v1 | ||
@@ -34,3 +35,2 @@ - name: Update requirements | ||
| test: | ||
| needs: update-requirements | ||
| name: Run TOX tests | ||
@@ -40,3 +40,3 @@ runs-on: ubuntu-latest | ||
| matrix: | ||
| python-version: [ 3.8, 3.9, "3.10" ] | ||
| python-version: [ 3.8, 3.9, "3.10", "3.11" ] | ||
@@ -43,0 +43,0 @@ steps: |
+1
-1
@@ -1,1 +0,1 @@ | ||
| Harold Solbrig <solbrig@jhu.edu> | ||
| Harold Solbrig <github@solbrigs.us> |
@@ -1,1 +0,1 @@ | ||
| {"git_version": "4086331", "is_release": false} | ||
| {"git_version": "d146cfd", "is_release": false} |
| Metadata-Version: 1.2 | ||
| Name: funowl | ||
| Version: 0.1.12 | ||
| Version: 0.1.13 | ||
| Summary: Python rendering of the OWL Functional syntax | ||
@@ -5,0 +5,0 @@ Home-page: http://biolink.github.io/hsolbrig |
| bcp47 | ||
| jsonasobj | ||
| pyjsg>=0.11.6 | ||
| rdflib-shim | ||
| rdflib>=5.0.0 | ||
| rdflib==6.2.0 | ||
| rfc3987 |
@@ -94,2 +94,4 @@ import logging | ||
| class FunOwlBase(FunOwlRoot, metaclass=FunOwlBaseMeta): | ||
| pass | ||
| def _subjects(self, g: Graph) -> List[SUBJ]: | ||
| # This should never get called. If it does, FunOwlRoot will raise a notimplemented error. | ||
| return super()._subjects(g) |
@@ -273,3 +273,4 @@ import logging | ||
| def fparse(inp: bytes, start: int, consumer: Callable[[FunOwlBase], None]) -> int: | ||
| def fparse(inp: bytes, start: int, consumer: Callable[[FunOwlBase], None], print_progress: bool = True) -> int: | ||
| """ | ||
@@ -280,2 +281,3 @@ Functional parser - work through inp pulling complete functions out and processing them. | ||
| :param consumer: OWLFunc entry consumer | ||
| :param print_progress: Print conversion progress indicator on command line | ||
| :return: final position | ||
@@ -303,3 +305,3 @@ """ | ||
| start = skip_comments(inp, start) | ||
| start = fparse(inp, start, lambda f: o.add_arg(f)) | ||
| start = fparse(inp, start, lambda f: o.add_arg(f, print_progress=print_progress)) | ||
| consumer(o) | ||
@@ -341,6 +343,7 @@ start = skip_comments(inp, start) | ||
| def to_python(defn: Union[str, bytes, IO]) -> Optional[OntologyDocument]: | ||
| def to_python(defn: Union[str, bytes, IO], print_progress: bool = True) -> Optional[OntologyDocument]: | ||
| """ | ||
| Convert the functional syntax in defn to a Python representation | ||
| :param defn: The ontology definition | ||
| :param print_progress: Print progress indicator on command line | ||
| :return: Ontology Document | ||
@@ -357,3 +360,3 @@ """ | ||
| ontology_doc = OntologyDocument() | ||
| fparse(to_bytes_array(defn), 0, consumer) | ||
| fparse(to_bytes_array(defn), 0, consumer, print_progress=print_progress) | ||
| return ontology_doc |
@@ -134,2 +134,12 @@ """ | ||
| prefix, name = self.split(':', 1) | ||
| return URIRef(g.namespace_manager.store.namespace(prefix or "") + name) | ||
| namespace = g.namespace_manager.store.namespace(prefix or "") | ||
| if namespace: | ||
| return URIRef(namespace + name) | ||
| # RDFLIB 6.2.0 and beyond only allow one namespace, meaning that our NS may no longer be there. | ||
| # We now keep track of the original declarations to see whether we can find it in a pinch | ||
| from funowl import IRI | ||
| rval = IRI.prefix_declarations.as_uri(prefix, name) if IRI.prefix_declarations else None | ||
| if not rval: | ||
| raise ValueError(f"Unrecognized prefix: {prefix}") | ||
| return rval |
+12
-14
| """ IRI := fullIRI | abbreviatedIRI """ | ||
| import logging | ||
| from dataclasses import dataclass, Field | ||
| from typing import Union, ClassVar, Optional, Type, List | ||
| from dataclasses import dataclass | ||
| from typing import Union, ClassVar, Optional, List | ||
@@ -12,2 +12,3 @@ from rdflib import URIRef, Namespace, Graph, RDF, OWL, XSD, RDFS | ||
| from funowl.general_definitions import FullIRI, AbbreviatedIRI | ||
| from funowl.prefix_declarations import PrefixDeclarations | ||
| from funowl.writers.FunctionalWriter import FunctionalWriter | ||
@@ -20,3 +21,5 @@ | ||
| v: Union[AbbreviatedIRI, FullIRI, URIRef, str] = exclude([URIRef, str]) | ||
| rdf_type: ClassVar[URIRef] = None | ||
| prefix_declarations: ClassVar[PrefixDeclarations] = None # Link to prefixes section, if declared | ||
@@ -27,17 +30,12 @@ # def __post_init__(self): | ||
| def full_uri(self, g: Graph) -> Optional[URIRef]: | ||
| if isinstance(self.v, URIRef): | ||
| return self.v | ||
| if isinstance(self.v, AbbreviatedIRI): | ||
| # TODO: find the code in rdflib that does this | ||
| ns, local = self.v.split(':', 1) | ||
| for ns1, uri in g.namespaces(): | ||
| if ns == ns1: | ||
| return(Namespace(uri)[local]) | ||
| logging.warning(f"IRI: {self.v} - {ns} not a valid prefix") | ||
| return None | ||
| if isinstance(self.v, FullIRI): | ||
| if not isinstance(self.v, AbbreviatedIRI): | ||
| return URIRef(self.v) | ||
| prefix, lname = str(self).split(':', 1) | ||
| if self.prefix_declarations and prefix in self.prefix_declarations: | ||
| return self.prefix_declarations[prefix] + lname | ||
| return self.v.to_rdf(g) | ||
| def to_functional(self, w: FunctionalWriter) -> FunctionalWriter: | ||
| fulluri = self.full_uri(w.g) | ||
| """ Emit an abbreviated URI if possible, otherwise a full one """ | ||
| fulluri = None if isinstance(self.v, AbbreviatedIRI) else self.full_uri(w.g) | ||
| return w + (fulluri.n3(w.g.namespace_manager) if fulluri else self.v) | ||
@@ -44,0 +42,0 @@ |
@@ -24,2 +24,3 @@ """ | ||
| class AnonymousIndividual(NodeID): | ||
@@ -26,0 +27,0 @@ def to_rdf(self, g: Graph, emit_type_arc: bool = False) -> BNode: |
@@ -35,3 +35,3 @@ """ | ||
| from funowl.objectproperty_expressions import ObjectPropertyExpression | ||
| from funowl.prefix_declarations import Prefix | ||
| from funowl.prefix_declarations import Prefix, PrefixDeclarations | ||
| from funowl.terminals.TypingHelper import isinstance_, proc_forwards | ||
@@ -46,2 +46,3 @@ from funowl.writers.FunctionalWriter import FunctionalWriter | ||
| @dataclass | ||
@@ -57,3 +58,3 @@ class Import(FunOwlBase): | ||
| def to_rdf(self, _: Graph) -> Optional[NODE]: | ||
| def to_rdf(self, _: Graph, emit_type_arc: Optional[bool] = False) -> Optional[NODE]: | ||
| return URIRef(str(self.ontology_iri())) | ||
@@ -97,16 +98,17 @@ | ||
| def add_arg(self, arg: [IRI.types(), Import, Axiom, Annotation]): | ||
| def add_arg(self, arg: [IRI.types(), Import, Axiom, Annotation], print_progress: bool = True): | ||
| if isinstance_(arg, Axiom): | ||
| self.axioms.append(arg) | ||
| self._naxioms += 1 | ||
| if not self._naxioms % 100000: | ||
| print(self._naxioms) | ||
| elif not self._naxioms % 10000: | ||
| print(self._naxioms) | ||
| elif not self._naxioms % 1000: | ||
| print('k', end='') | ||
| sys.stdout.flush() | ||
| elif not self._naxioms % 100: | ||
| print('.', end='') | ||
| sys.stdout.flush() | ||
| if print_progress: | ||
| self._naxioms += 1 | ||
| if not self._naxioms % 100000: | ||
| print(self._naxioms) | ||
| elif not self._naxioms % 10000: | ||
| print(self._naxioms) | ||
| elif not self._naxioms % 1000: | ||
| print('k', end='') | ||
| sys.stdout.flush() | ||
| elif not self._naxioms % 100: | ||
| print('.', end='') | ||
| sys.stdout.flush() | ||
| elif isinstance(arg, IRI): | ||
@@ -140,5 +142,5 @@ if not self.iri: | ||
| if not issubclass(type(sub), Class) and isinstance(sub, Class): | ||
| sub = Class(sub) | ||
| pass | ||
| if not issubclass(type(sup), Class) and isinstance(sup, Class): | ||
| sup = Class(sup) | ||
| pass | ||
| self.axioms.append(SubClassOf(sub, sup)) | ||
@@ -191,3 +193,3 @@ return self | ||
| for individual in individuals: | ||
| self.axioms.append(NamedIndividual(individual)) | ||
| self.axioms.append(Declaration(NamedIndividual(individual))) | ||
| return self | ||
@@ -256,7 +258,8 @@ | ||
| """ | ||
| prefixDeclarations: List[Prefix] = empty_list_wrapper(Prefix) | ||
| prefixDeclarations: PrefixDeclarations = None | ||
| ontology: Ontology = None | ||
| def __init__(self, default_prefix: FullIRI = None, ontology: Optional[Ontology] = None, **prefixes: FullIRI): | ||
| self.prefixDeclarations = [] | ||
| def __init__(self, default_prefix: Union[FullIRI, Namespace, str] = None, ontology: Optional[Ontology] = None, | ||
| **prefixes: Union[FullIRI, Namespace, str]): | ||
| self.prefixDeclarations = PrefixDeclarations() | ||
| self.ontology = ontology if ontology is not None else Ontology() | ||
@@ -285,3 +288,3 @@ if default_prefix: | ||
| if isinstance(item, PrefixName): | ||
| for p in self.prefixDeclarations: | ||
| for p in self.prefixDeclarations.as_prefixes(): | ||
| if p.prefixName == item: | ||
@@ -295,4 +298,17 @@ return p.fullIRI | ||
| def add_namespaces(self, g: Graph, add_funowl_namespace: bool = False) -> Graph: | ||
| for prefix in self.prefixDeclarations: | ||
| g.namespace_manager.bind(str(prefix.prefixName or ''), str(prefix.fullIRI), True, True) | ||
| """ | ||
| Transfer the namespace declarations included in the prefixDeclarations section into the rdflib Graph. | ||
| Note: rdflib 6.2.0 and up only associates ONE prefix with every unique URI. Adding a second prefix will | ||
| delete the first, which can pose real issues for code like this. You have to use the prefixDeclarations | ||
| package to transform AbbreviatedIRI's into URI's, as they might disappear in rdflib. When going from IRIs | ||
| back to Abbreviated IRIs, we use the _last_prefix declared with one exception: the default prefix is always | ||
| preferred over a named prefix. | ||
| :param g: rdflib graph to transfer to | ||
| :param add_funowl_namespace: True means add our own namespace in | ||
| :return: g with namespaces added. | ||
| """ | ||
| for prefix in self.prefixDeclarations.as_prefixes(): | ||
| g.namespace_manager.bind(str(prefix.prefixName or ''), str(prefix.fullIRI), override=True, replace=False) | ||
| if add_funowl_namespace: | ||
@@ -304,5 +320,6 @@ g.namespace_manager.bind(FUNOWL_NAMESPACE, FUNOWL_URI) | ||
| """ Return a FunctionalWriter instance with the representation of the OntologyDocument in functional syntax """ | ||
| IRI.prefix_declarations = self.prefixDeclarations | ||
| w = w or FunctionalWriter() | ||
| self.add_namespaces(w.g) | ||
| return w.iter([Prefix(ns, uri) for ns, uri in w.g.namespaces()], indent=False).hardbr() + \ | ||
| return w.iter(self.prefixDeclarations.as_prefixes(), indent=False).hardbr() + \ | ||
| (self.ontology or Ontology()) | ||
@@ -312,2 +329,3 @@ | ||
| """ Convert the ontology document into RDF representation """ | ||
| IRI.prefix_declarations = self.prefixDeclarations | ||
| self.add_namespaces(g, add_funowl_namespace=emit_functional_definitions) | ||
@@ -314,0 +332,0 @@ return self.ontology.to_rdf(g, emit_type_arc, emit_functional_definitions) |
| from dataclasses import dataclass | ||
| from typing import Optional, List, Union | ||
| from typing import Optional, Union, Iterable, Dict | ||
| from rdflib import Graph | ||
| from rdflib.namespace import NamespaceManager, OWL | ||
| from rdflib import Graph, RDF, RDFS, XSD, OWL, Namespace | ||
| from rdflib.namespace import NamespaceManager | ||
| from rdflib.term import URIRef | ||
| from funowl.base.cast_function import exclude | ||
| from funowl.base.fun_owl_base import FunOwlBase | ||
@@ -13,7 +12,10 @@ from funowl.general_definitions import PrefixName, FullIRI | ||
| PREFIX_NAME_TYPE = Optional[Union[PrefixName, str]] | ||
| FULL_IRI_TYPE = Union[FullIRI, Namespace, URIRef, str] | ||
| @dataclass | ||
| class Prefix(FunOwlBase): | ||
| prefixName: Optional[Union[PrefixName, str]] | ||
| fullIRI: FullIRI | ||
| prefixName: PREFIX_NAME_TYPE # Always recorded as PrefixName | ||
| fullIRI: FULL_IRI_TYPE # Always recorded as FullURI | ||
@@ -23,3 +25,8 @@ def __post_init__(self): | ||
| self.prefixName = PrefixName(self.prefixName) | ||
| if not isinstance(self.fullIRI, FullIRI): | ||
| self.fullIRI = FullIRI(self.fullIRI) | ||
| def __hash__(self): | ||
| return hash(self.prefixName) | ||
| def to_functional(self, w: FunctionalWriter) -> FunctionalWriter: | ||
@@ -30,12 +37,36 @@ return w.func(self, lambda: w.concat((self.prefixName or '') + ':', '=', | ||
| # Table 2. Declarations of the Standard Prefix Names | ||
| # Prefix name Prefix IRI | ||
| # rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> | ||
| # rdfs: <http://www.w3.org/2000/01/rdf-schema#> | ||
| # xsd: <http://www.w3.org/2001/XMLSchema#> | ||
| # owl: <http://www.w3.org/2002/07/owl#> | ||
| RDF_PREFIX = Prefix('rdf', str(RDF)) | ||
| RDFS_PREFIX = Prefix('rdfs', str(RDFS)) | ||
| XSD_PREFIX = Prefix('xsd', str(XSD)) | ||
| OWL_PREFIX = Prefix('owl', str(OWL)) | ||
| # DEFAULT_PREFIX = Prefix(None, str(OWL)) | ||
| PREFIX_PRESETS = {RDF_PREFIX, RDFS_PREFIX, XSD_PREFIX, OWL_PREFIX} | ||
| class PrefixDeclarations(NamespaceManager): | ||
| def __init__(self, g: Optional[Graph] = None) -> None: | ||
| self._init = True | ||
| super().__init__(g if g is not None else Graph()) | ||
| self.bind('owl', OWL) | ||
| self._prefixMap: Dict[str, Prefix] = dict() | ||
| super().__init__(g or Graph()) | ||
| for prefix in PREFIX_PRESETS: | ||
| self.bind(prefix.prefixName, prefix.fullIRI) | ||
| self._init = False | ||
| def as_prefixes(self) -> List[Prefix]: | ||
| def __contains__(self, item: Union[Prefix, str]) -> bool: | ||
| return str(item) in self._prefixMap | ||
| def __getitem__(self, item: Union[Prefix, str]) -> URIRef: | ||
| entry = self._prefixMap.get(str(item)) | ||
| return URIRef(entry.fullIRI) | ||
| def as_prefixes(self) -> Iterable[Prefix]: | ||
| """ Return the contents of the manager as a list of Prefixes """ | ||
| return [Prefix(ns if ns else None, uri) for (ns, uri) in self.namespaces()] | ||
| return self._prefixMap.values() | ||
@@ -46,3 +77,3 @@ def __setattr__(self, key, value): | ||
| else: | ||
| self.append(Prefix(key, value)) | ||
| self.bind(key, value) | ||
@@ -52,7 +83,21 @@ def append(self, decl: Prefix) -> None: | ||
| def bind(self, prefix, namespace, override=True, replace=True): | ||
| """ Bind with override and replace defaults changed """ | ||
| super().bind(str(prefix) if prefix is not None else None, str(namespace), override, replace) | ||
| def as_uri(self, prefix: Union[Prefix, str], namespace: str) -> Optional[URIRef]: | ||
| """ | ||
| Map prefix/namespace into a URI | ||
| :param prefix: | ||
| :param namespace: | ||
| :return: | ||
| """ | ||
| prefix = str(prefix) if prefix else '' # Guard against None creeping in | ||
| if prefix in self._prefixMap: | ||
| return URIRef(self._prefixMap[prefix].fullIRI + namespace) | ||
| return None | ||
| def bind(self, prefix: PREFIX_NAME_TYPE, namespace: FULL_IRI_TYPE, _=True, __=True): | ||
| """ Bind w/ defaults overriden """ | ||
| prefix = str(prefix) if prefix else '' | ||
| self._prefixMap[prefix] = Prefix(prefix, str(namespace)) | ||
| super().bind(prefix, str(namespace), override=True, replace=True) | ||
| def to_functional(self, w: FunctionalWriter) -> FunctionalWriter: | ||
| return w.iter(self.as_prefixes(), indent=False) |
@@ -1,2 +0,2 @@ | ||
| from typing import List, Optional, Callable, Union, Any | ||
| from typing import List, Optional, Callable, Union, Any, Iterable | ||
@@ -143,3 +143,3 @@ from rdflib import Graph, OWL, URIRef | ||
| Generate a functional method in the form of "func( ... )" | ||
| :param func_name: Function name or object. If object, the class name is used | ||
@@ -160,3 +160,3 @@ :param contents: Invoked to generate function contents | ||
| def iter(self, *objs: List[Any], f: Optional[Callable[[Any], "FunctionalWriter"]] = None, indent: bool=True) \ | ||
| def iter(self, *objs: Iterable[Any], f: Optional[Callable[[Any], "FunctionalWriter"]] = None, indent: bool = True) \ | ||
| -> "FunctionalWriter": | ||
@@ -186,3 +186,3 @@ """ | ||
| def opt(self, v: Optional[Any], sep: str = ' ') -> "FunctionalWriter": | ||
| """ Emit v if it exists | ||
| """ Emit v if it exists | ||
| :param v: Optional item to emit | ||
@@ -189,0 +189,0 @@ :param sep: separator |
+2
-1
@@ -10,3 +10,3 @@ [[source]] | ||
| [packages] | ||
| rdflib = ">=5.0.0" | ||
| rdflib = "==6.2.0" | ||
| rdflib-shim = "*" | ||
@@ -16,1 +16,2 @@ pyjsg = ">=0.11.6" | ||
| bcp47 = "*" | ||
| jsonasobj = "*" |
+1
-1
| Metadata-Version: 1.2 | ||
| Name: funowl | ||
| Version: 0.1.12 | ||
| Version: 0.1.13 | ||
| Summary: Python rendering of the OWL Functional syntax | ||
@@ -5,0 +5,0 @@ Home-page: http://biolink.github.io/hsolbrig |
+100
-0
@@ -124,3 +124,103 @@  | ||
| ``` | ||
| ## Transforming Functional Syntax to RDF | ||
| See: [test_issue_57.py](tests/test_issues/test_issue_57.py) for full example | ||
| ```python | ||
| from rdflib import Graph | ||
| from funowl.converters.functional_converter import to_python | ||
| # The functional syntax input can be a string, URL, file loc or open file | ||
| function_pizza = "https://github.com/Harold-Solbrig/funowl/blob/main/tests/data/pizza.owl" | ||
| internal_pizza = to_python(function_pizza) | ||
| # Emit the internal representation as an rdflib graph | ||
| g = Graph() | ||
| internal_pizza.to_rdf(g) | ||
| # Serialize the rdflib graph in your favorite format | ||
| print(g.serialize(format="ttl")) | ||
| ``` | ||
| ## Command Line Interface | ||
| `funowl` can be installed with either `pip` or `pipenv`. | ||
| ```shell | ||
| > funowl -h | ||
| usage: funowl [-h] | ||
| [-f {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}] | ||
| [-np] | ||
| input [output] | ||
| Convert OWL Functional Syntax to RDF | ||
| positional arguments: | ||
| input Input OWL functional syntax. Can be a file name or URL | ||
| output Output file. If omitted, output goes to stdout | ||
| options: | ||
| -h, --help show this help message and exit | ||
| -f {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml}, --format {ttl,hext,json-ld,longturtle,n3,nquads,nt,nt11,ntriples,pretty-xml,trig,trix,ttl,turtle,xml} | ||
| Output RDF Format. If omitted, guess from output file | ||
| suffix. If guessing doesn't work, assume 'turtle' | ||
| -np, --noProgressBar Don't output the progress indicators | ||
| ``` | ||
| To convert an OWL functional representation of the pizza ontology to RDF: | ||
| ```shell | ||
| > funowl https://raw.githubusercontent.com/Harold-Solbrig/funowl/main/tests/data/pizza.owl | ||
| @prefix dc: <http://purl.org/dc/elements/1.1/> . | ||
| @prefix owl: <http://www.w3.org/2002/07/owl#> . | ||
| @prefix pizza: <http://www.co-ode.org/ontologies/pizza/pizza.owl#> . | ||
| @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . | ||
| @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> . | ||
| @prefix skos: <http://www.w3.org/2004/02/skos/core#> . | ||
| @prefix terms: <http://purl.org/dc/terms/> . | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| dc:description a owl:AnnotationProperty . | ||
| dc:title a owl:AnnotationProperty . | ||
| terms:contributor a owl:AnnotationProperty . | ||
| terms:license a owl:AnnotationProperty . | ||
| terms:provenance a owl:AnnotationProperty . | ||
| <http://www.co-ode.org/ontologies/pizza> a owl:Ontology ; | ||
| rdfs:label "pizza"^^xsd:string ; | ||
| dc:description """An ontology about pizzas and their toppings. | ||
| ... | ||
| ``` | ||
| To convert the same ontology into XML, either: | ||
| `funowl https://raw.githubusercontent.com/Harold-Solbrig/funowl/main/tests/data/pizza.owl -f xml > pizza.xml` | ||
| or | ||
| `funowl https://raw.githubusercontent.com/Harold-Solbrig/funowl/main/tests/data/pizza.owl pizza.xml` | ||
| ```shell | ||
| > cat pizza.xml | ||
| <?xml version="1.0" encoding="utf-8"?> | ||
| <rdf:RDF | ||
| xmlns:dc="http://purl.org/dc/elements/1.1/" | ||
| xmlns:owl="http://www.w3.org/2002/07/owl#" | ||
| xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||
| xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#" | ||
| xmlns:skos="http://www.w3.org/2004/02/skos/core#" | ||
| xmlns:terms="http://purl.org/dc/terms/" | ||
| > | ||
| <rdf:Description rdf:nodeID="Nd1a614092c234a3b90971238bb6550e8"> | ||
| <rdf:type rdf:resource="http://www.w3.org/2002/07/owl#Restriction"/> | ||
| <owl:onProperty rdf:resource="http://www.co-ode.org/ontologies/pizza/pizza.owl#hasTopping"/> | ||
| <owl:someValuesFrom rdf:resource="http://www.co-ode.org/ontologies/pizza/pizza.owl#TomatoTopping"/> | ||
| </rdf:Description> | ||
| ... | ||
| </rdf:RDF> | ||
| ``` | ||
| ## Other packages | ||
| While we would be happy to be corrected, to the best of our knowledge there is to be minimal support for OWL in python. | ||
@@ -127,0 +227,0 @@ * [OwlReady2](https://owlready2.readthedocs.io/en/latest/) appears to be the closest thing to what we are |
+2
-1
@@ -14,5 +14,6 @@ ################################################################################ | ||
| bcp47 | ||
| jsonasobj | ||
| pyjsg>=0.11.6 | ||
| rdflib-shim | ||
| rdflib>=5.0.0 | ||
| rdflib==6.2.0 | ||
| rfc3987 |
+4
-0
@@ -31,2 +31,6 @@ [metadata] | ||
| [entry_points] | ||
| console_scripts = | ||
| funowl = funowl.cli:evaluate_cli | ||
| [egg_info] | ||
@@ -33,0 +37,0 @@ tag_build = |
+1
-0
@@ -13,2 +13,3 @@ #!/usr/bin/env python | ||
| pbr=True, | ||
| py_modules=['funowl'] | ||
| ) |
@@ -28,11 +28,11 @@ import unittest | ||
| doc = OntologyDocument() | ||
| self.assertEqual(expected([]), str(doc.to_functional().getvalue())) | ||
| self.assertEqualOntology(expected([]), str(doc.to_functional().getvalue())) | ||
| doc.ontology.iri = "http://snomed.info/sct/" | ||
| self.assertEqual(expected(['<http://snomed.info/sct/>']), str(doc.to_functional().getvalue())) | ||
| self.assertEqualOntology(expected(['<http://snomed.info/sct/>']), str(doc.to_functional().getvalue())) | ||
| doc.ontology.version = "http://snomed.info/sct/version/201802017" | ||
| self.assertEqual(expected(['<http://snomed.info/sct/> <http://snomed.info/sct/version/201802017>']), | ||
| self.assertEqualOntology(expected(['<http://snomed.info/sct/> <http://snomed.info/sct/version/201802017>']), | ||
| doc.to_functional().getvalue()) | ||
| doc.prefixes(RDFS, owl=OWL, rdf=RDF) | ||
| self.assertEqual('''Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology('''Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -48,3 +48,3 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| doc.ontology.annotations.append(Annotation(RDFS.label, "foo")) | ||
| self.assertEqual('''Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology('''Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -51,0 +51,0 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) |
@@ -27,3 +27,3 @@ import unittest | ||
| doc = OntologyDocument(A, ex=EX) | ||
| self.assertEqual("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -46,3 +46,3 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| od.ontology.subClassOf(EX.Child, OWL.Thing) | ||
| self.assertEqual("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -49,0 +49,0 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) |
@@ -12,7 +12,7 @@ import unittest | ||
| def test_prefix(self): | ||
| self.assertEqual('Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> )', | ||
| self.assertEqualOntology('Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> )', | ||
| str(Prefix('rdf', RDF).to_functional(self.w))) | ||
| self.assertEqual('Prefix( ex: = <http://www.example.org/test/> )', | ||
| self.assertEqualOntology('Prefix( ex: = <http://www.example.org/test/> )', | ||
| str(Prefix('ex', "http://www.example.org/test/").to_functional(self.w.reset()))) | ||
| self.assertEqual('Prefix( : = <http://example.org/mt/> )', | ||
| self.assertEqualOntology('Prefix( : = <http://example.org/mt/> )', | ||
| str(Prefix(None, URIRef("http://example.org/mt/")).to_functional(self.w.reset()))) | ||
@@ -32,3 +32,3 @@ with self.assertRaises(TypeError): | ||
| pds.bind(None, RDFS.label) | ||
| self.assertEqual('''Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology('''Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -44,3 +44,3 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| pds = PrefixDeclarations() | ||
| self.assertEqual("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -52,3 +52,3 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| pds.bind(None, "http://www.w3.org/2002/07/owl#") | ||
| self.assertEqual(""" Prefix( xml: = <http://example.org/xml2#> ) | ||
| self.assertEqualOntology(""" Prefix( xml: = <http://example.org/xml2#> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -61,3 +61,3 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| pds.foaf = "http://foaf.org/" | ||
| self.assertEqual('''Prefix( xml: = <http://example.org/xml2#> ) | ||
| self.assertEqualOntology('''Prefix( xml: = <http://example.org/xml2#> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -64,0 +64,0 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) |
@@ -9,2 +9,3 @@ import os | ||
| from tests import datadir, PREFIXES_BROKEN_MESSAGE, RDFLIB_PREFIXES_ARE_BROKEN | ||
| from tests.utils.base import TestBase | ||
@@ -15,3 +16,3 @@ pizza = os.path.join(datadir, 'pizza.owl') | ||
| class FunctionalConverterTestCase(unittest.TestCase): | ||
| class FunctionalConverterTestCase(TestBase): | ||
| def verify(self, loc: Any) -> None: | ||
@@ -27,3 +28,3 @@ self.maxDiff = None | ||
| else: | ||
| self.assertEqual(expected, str(doc.to_functional())) | ||
| self.assertEqualOntology(expected, str(doc.to_functional())) | ||
| else: | ||
@@ -30,0 +31,0 @@ with open(pizza_fun, 'w') as f: |
@@ -9,2 +9,3 @@ import unittest | ||
| from tests import RDFLIB_PREFIXES_ARE_BROKEN, PREFIXES_BROKEN_MESSAGE | ||
| from tests.utils.base import TestBase | ||
@@ -21,3 +22,3 @@ text = """ | ||
| class DeclarationTestCase(unittest.TestCase): | ||
| class DeclarationTestCase(TestBase): | ||
| def test_base_declaration(self): | ||
@@ -33,3 +34,3 @@ decl = Declaration( Class( PIZZA.American)) | ||
| actual = str(parsed.to_functional()) | ||
| self.assertEqual("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -36,0 +37,0 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) |
@@ -7,5 +7,6 @@ import unittest | ||
| from tests import RDFLIB_PREFIXES_ARE_BROKEN, PREFIXES_BROKEN_MESSAGE | ||
| from tests.utils.base import TestBase | ||
| class Issue2TestCase(unittest.TestCase): | ||
| class Issue2TestCase(TestBase): | ||
| @unittest.skipIf(RDFLIB_PREFIXES_ARE_BROKEN, PREFIXES_BROKEN_MESSAGE) | ||
@@ -19,3 +20,3 @@ def test_cyclic_issue(self): | ||
| o.declarations(DataProperty(RELA['#hasLowerBound'])) | ||
| self.assertEqual("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -38,3 +39,3 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) | ||
| o.axioms.append(ClassAssertion(URIRef("Interval"), REPR['#NormalizedRange'])) | ||
| self.assertEqual("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| self.assertEqualOntology("""Prefix( xml: = <http://www.w3.org/XML/1998/namespace> ) | ||
| Prefix( rdf: = <http://www.w3.org/1999/02/22-rdf-syntax-ns#> ) | ||
@@ -41,0 +42,0 @@ Prefix( rdfs: = <http://www.w3.org/2000/01/rdf-schema#> ) |
| import logging | ||
| import unittest | ||
| from typing import Optional | ||
@@ -8,2 +9,3 @@ from rdflib import Namespace | ||
| from tests import LOGLEVEL | ||
| from tests.utils.functional_comparator import compare_functional_output | ||
@@ -25,1 +27,5 @@ logging.basicConfig(level=LOGLEVEL) | ||
| self.wa.reset() | ||
| def assertEqualOntology(self, expected: str, actual: str, msg:Optional[str] = None): | ||
| compare_functional_output(expected, actual, self, msg) | ||
+1
-0
@@ -6,2 +6,3 @@ [tox] | ||
| py310 | ||
| py311 | ||
@@ -8,0 +9,0 @@ [testenv] |
-151
| { | ||
| "_meta": { | ||
| "hash": { | ||
| "sha256": "c8f2218262cd18a9584b6bfe4dfe8f417789a79d5ab4e45bd5ec3dc898cf034c" | ||
| }, | ||
| "pipfile-spec": 6, | ||
| "requires": {}, | ||
| "sources": [ | ||
| { | ||
| "name": "pypi", | ||
| "url": "https://pypi.org/simple", | ||
| "verify_ssl": true | ||
| } | ||
| ] | ||
| }, | ||
| "default": { | ||
| "antlr4-python3-runtime": { | ||
| "hashes": [ | ||
| "sha256:f224469b4168294902bb1efa80a8bf7855f24c99aef99cbefc1bcd3cce77881b" | ||
| ], | ||
| "version": "==4.9.3" | ||
| }, | ||
| "bcp47": { | ||
| "hashes": [ | ||
| "sha256:309d3bbaef8d6c9ac59d37ba2167cc6620b4e7467ec8f1e09641b659bb1c0c6d", | ||
| "sha256:4878d2f3e697ef39ef3891a147280705e4377d5a8d7eb0702129b8d4a3718702" | ||
| ], | ||
| "index": "pypi", | ||
| "version": "==0.0.4" | ||
| }, | ||
| "isodate": { | ||
| "hashes": [ | ||
| "sha256:0751eece944162659049d35f4f549ed815792b38793f07cf73381c1c87cbed96", | ||
| "sha256:48c5881de7e8b0a0d648cb024c8062dc84e7b840ed81e864c7614fd3c127bde9" | ||
| ], | ||
| "version": "==0.6.1" | ||
| }, | ||
| "jsonasobj": { | ||
| "hashes": [ | ||
| "sha256:b9e329dc1ceaae7cf5d5b214684a0b100e0dad0be6d5bbabac281ec35ddeca65", | ||
| "sha256:d52e0544a54a08f6ea3f77fa3387271e3648655e0eace2f21e825c26370e44a2" | ||
| ], | ||
| "version": "==1.3.1" | ||
| }, | ||
| "pyjsg": { | ||
| "hashes": [ | ||
| "sha256:10af60ff42219be7e85bf7f11c19b648715b0b29eb2ddbd269e87069a7c3f26d", | ||
| "sha256:4bd6e3ff2833fa2b395bbe803a2d72a5f0bab5b7285bccd0da1a1bc0aee88bfa" | ||
| ], | ||
| "index": "pypi", | ||
| "version": "==0.11.10" | ||
| }, | ||
| "pyparsing": { | ||
| "hashes": [ | ||
| "sha256:2b020ecf7d21b687f219b71ecad3631f644a47f01403fa1d1036b0c6416d70fb", | ||
| "sha256:5026bae9a10eeaefb61dab2f09052b9f4307d44aee4eda64b309723d8d206bbc" | ||
| ], | ||
| "markers": "python_full_version >= '3.6.8'", | ||
| "version": "==3.0.9" | ||
| }, | ||
| "rdflib": { | ||
| "hashes": [ | ||
| "sha256:8dbfa0af2990b98471dacbc936d6494c997ede92fd8ed693fb84ee700ef6f754", | ||
| "sha256:fc81cef513cd552d471f2926141396b633207109d0154c8e77926222c70367fe" | ||
| ], | ||
| "index": "pypi", | ||
| "version": "==6.1.1" | ||
| }, | ||
| "rdflib-jsonld": { | ||
| "hashes": [ | ||
| "sha256:bcf84317e947a661bae0a3f2aee1eced697075fc4ac4db6065a3340ea0f10fc2", | ||
| "sha256:eda5a42a2e09f80d4da78e32b5c684bccdf275368f1541e6b7bcddfb1382a0e0" | ||
| ], | ||
| "version": "==0.6.1" | ||
| }, | ||
| "rdflib-shim": { | ||
| "hashes": [ | ||
| "sha256:7a853e7750ef1e9bf4e35dea27d54e02d4ed087de5a9e0c329c4a6d82d647081", | ||
| "sha256:d955d11e2986aab42b6830ca56ac6bc9c893abd1d049a161c6de2f1b99d4fc0d" | ||
| ], | ||
| "index": "pypi", | ||
| "version": "==1.0.3" | ||
| }, | ||
| "rfc3987": { | ||
| "hashes": [ | ||
| "sha256:10702b1e51e5658843460b189b185c0366d2cf4cff716f13111b0ea9fd2dce53", | ||
| "sha256:d3c4d257a560d544e9826b38bc81db676890c79ab9d7ac92b39c7a253d5ca733" | ||
| ], | ||
| "index": "pypi", | ||
| "version": "==1.3.8" | ||
| }, | ||
| "setuptools": { | ||
| "hashes": [ | ||
| "sha256:5a844ad6e190dccc67d6d7411d119c5152ce01f7c76be4d8a1eaa314501bba77", | ||
| "sha256:bf8a748ac98b09d32c9a64a995a6b25921c96cc5743c1efa82763ba80ff54e91" | ||
| ], | ||
| "markers": "python_version >= '3.7'", | ||
| "version": "==62.4.0" | ||
| }, | ||
| "six": { | ||
| "hashes": [ | ||
| "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", | ||
| "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" | ||
| ], | ||
| "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", | ||
| "version": "==1.16.0" | ||
| } | ||
| }, | ||
| "develop": { | ||
| "certifi": { | ||
| "hashes": [ | ||
| "sha256:84c85a9078b11105f04f3036a9482ae10e4621616db313fe045dd24743a0820d", | ||
| "sha256:fe86415d55e84719d75f8b69414f6438ac3547d2078ab91b67e779ef69378412" | ||
| ], | ||
| "markers": "python_version >= '3.6'", | ||
| "version": "==2022.6.15" | ||
| }, | ||
| "charset-normalizer": { | ||
| "hashes": [ | ||
| "sha256:2857e29ff0d34db842cd7ca3230549d1a697f96ee6d3fb071cfa6c7393832597", | ||
| "sha256:6881edbebdb17b39b4eaaa821b438bf6eddffb4468cf344f09f89def34a8b1df" | ||
| ], | ||
| "markers": "python_full_version >= '3.5.0'", | ||
| "version": "==2.0.12" | ||
| }, | ||
| "idna": { | ||
| "hashes": [ | ||
| "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff", | ||
| "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d" | ||
| ], | ||
| "markers": "python_full_version >= '3.5.0'", | ||
| "version": "==3.3" | ||
| }, | ||
| "requests": { | ||
| "hashes": [ | ||
| "sha256:bc7861137fbce630f17b03d3ad02ad0bf978c844f3536d0edda6499dafce2b6f", | ||
| "sha256:d568723a7ebd25875d8d1eaf5dfa068cd2fc8194b2e483d7b1f7c81918dbec6b" | ||
| ], | ||
| "index": "pypi", | ||
| "version": "==2.28.0" | ||
| }, | ||
| "urllib3": { | ||
| "hashes": [ | ||
| "sha256:44ece4d53fb1706f667c9bd1c648f5469a2ec925fcf3a776667042d645472c14", | ||
| "sha256:aabaf16477806a5e1dd19aa41f8c2b7950dd3c746362d7e3223dbe6de6ac448e" | ||
| ], | ||
| "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", | ||
| "version": "==1.26.9" | ||
| } | ||
| } | ||
| } |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
51411684
1.77%2212
1%5790
10.45%