pymantic
Advanced tools
+26
| Copyright (c) 2009-2023, O'Reilly Media, Inc, Gavin Carothers, Nick Pilon | ||
| and contributors | ||
| All rights reserved. | ||
| Redistribution and use in source and binary forms, with or without modification, | ||
| are permitted provided that the following conditions are met: | ||
| * Redistributions of source code must retain the above copyright notice, | ||
| this list of conditions and the following disclaimer. | ||
| * Redistributions in binary form must reproduce the above copyright notice, | ||
| this list of conditions and the following disclaimer in the documentation and/or | ||
| other materials provided with the distribution. | ||
| * Neither the name of the author or contributors nor the names of its contributors | ||
| may be used to endorse or promote products derived from this software without | ||
| specific prior written permission. | ||
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
| ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
| WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
| DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | ||
| ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
| (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
| LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | ||
| ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
| (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| [build-system] | ||
| requires = ["setuptools"] | ||
| build-backend = "setuptools.build_meta" | ||
| [tool.pytest.ini_options] | ||
| minversion = "6.0" | ||
| addopts = "-W always" | ||
| testpaths = [ | ||
| "tests", | ||
| ] | ||
| [tool.isort] | ||
| profile = "black" | ||
| multi_line_output = 3 | ||
| src_paths = ["src", "tests"] | ||
| skip_glob = ["docs/*"] | ||
| include_trailing_comma = true | ||
| force_grid_wrap = false | ||
| combine_as_imports = true | ||
| line_length = 79 | ||
| force_sort_within_sections = true | ||
| no_lines_before = "THIRDPARTY" | ||
| sections = "FUTURE,THIRDPARTY,FIRSTPARTY,LOCALFOLDER" | ||
| default_section = "THIRDPARTY" | ||
| known_first_party = "pymantic" |
| Metadata-Version: 2.1 | ||
| Name: pymantic | ||
| Version: 1.0.0 | ||
| Summary: Semantic Web and RDF library for Python | ||
| Home-page: https://github.com/norcalrdf/pymantic/ | ||
| Author: Gavin Carothers, Nick Pilon | ||
| Author-email: gavin@carothers.name, npilon@gmail.com | ||
| License: BSD | ||
| Keywords: RDF N3 Turtle Semantics | ||
| Classifier: Development Status :: 5 - Production/Stable | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: License :: OSI Approved :: BSD License | ||
| Classifier: Topic :: Internet :: WWW/HTTP | ||
| Classifier: Topic :: Scientific/Engineering :: Information Analysis | ||
| Classifier: Topic :: Text Processing :: Markup | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Description-Content-Type: text/x-rst | ||
| Provides-Extra: testing | ||
| License-File: LICENSE | ||
| ======== | ||
| Pymantic | ||
| ======== | ||
| --------------------------------------- | ||
| Semantic Web and RDF library for Python | ||
| --------------------------------------- | ||
| Quick Start | ||
| =========== | ||
| :: | ||
| >>> from pymantic.rdf import * | ||
| >>> from pymantic.parsers import turtle_parser | ||
| >>> import requests | ||
| >>> Resource.prefixes['foaf'] = Prefix('http://xmlns.com/foaf/0.1/') | ||
| >>> graph = turtle_parser.parse(requests.get('https://raw.github.com/norcalrdf/pymantic/master/examples/foaf-bond.ttl').text) | ||
| >>> bond_james = Resource(graph, 'http://example.org/stuff/Bond') | ||
| >>> print("%s knows:" % (bond_james.get_scalar('foaf:name'),)) | ||
| >>> for person in bond_james['foaf:knows']: | ||
| print(person.get_scalar('foaf:name')) | ||
| Requirements | ||
| ============ | ||
| ``pymantic`` requires Python 3.9 or higher. | ||
| ``lark`` is used for the Turtle and NTriples parser. | ||
| The ``requests`` library is used for HTTP requests and the SPARQL client. | ||
| ``lxml`` and ``rdflib`` are required by the SPARQL client as well. | ||
| Install | ||
| ======= | ||
| :: | ||
| $ pip install pymantic | ||
| This will install ``pymantic`` and all its dependencies. | ||
| Documentation | ||
| ============= | ||
| Generating a local copy of the documentation requires Sphinx: | ||
| :: | ||
| $ pip install Sphinx | ||
| requests | ||
| lxml | ||
| pytz | ||
| rdflib | ||
| lark<1.2.0,>=1.1.0 | ||
| pyld | ||
| [testing] | ||
| pytest | ||
| coverage | ||
| betamax |
| LICENSE | ||
| README.rst | ||
| pyproject.toml | ||
| setup.cfg | ||
| src/pymantic/__init__.py | ||
| src/pymantic/primitives.py | ||
| src/pymantic/rdf.py | ||
| src/pymantic/serializers.py | ||
| src/pymantic/sparql.py | ||
| src/pymantic/uri_schemes.py | ||
| src/pymantic/util.py | ||
| src/pymantic.egg-info/PKG-INFO | ||
| src/pymantic.egg-info/SOURCES.txt | ||
| src/pymantic.egg-info/dependency_links.txt | ||
| src/pymantic.egg-info/requires.txt | ||
| src/pymantic.egg-info/top_level.txt | ||
| src/pymantic.egg-info/zip-safe | ||
| src/pymantic/parsers/__init__.py | ||
| src/pymantic/parsers/base.py | ||
| src/pymantic/parsers/jsonld.py | ||
| src/pymantic/parsers/rdfxml.py | ||
| src/pymantic/parsers/lark/__init__.py | ||
| src/pymantic/parsers/lark/base.py | ||
| src/pymantic/parsers/lark/nquads.py | ||
| src/pymantic/parsers/lark/ntriples.py | ||
| src/pymantic/parsers/lark/turtle.py | ||
| src/pymantic/scripts/__init__.py | ||
| src/pymantic/vocab/__init__.py | ||
| src/pymantic/vocab/skos.py | ||
| tests/test_RDF.py | ||
| tests/test_SPARQL.py | ||
| tests/test_parsers.py | ||
| tests/test_primitives.py | ||
| tests/test_serializers.py | ||
| tests/test_turtle.py | ||
| tests/test_util.py |
| pymantic |
Sorry, the diff of this file is not supported yet
| # | ||
| version = "1.0.0" | ||
| release = version |
| __all__ = ["ntriples_parser", "nquads_parser", "turtle_parser", "jsonld_parser"] | ||
| from .jsonld import jsonld_parser | ||
| from .lark import nquads_parser, ntriples_parser, turtle_parser |
| from collections import defaultdict | ||
| from threading import local | ||
| import pymantic.primitives | ||
| class BaseParser: | ||
| """Common base class for all parsers | ||
| Provides shared utilities for creating RDF objects, handling IRIs, and | ||
| tracking parser state. | ||
| """ | ||
| def __init__(self, environment=None): | ||
| super().__init__() | ||
| self.env = environment or pymantic.primitives.RDFEnvironment() | ||
| self.profile = self.env.createProfile() | ||
| self._call_state = local() | ||
| def make_datatype_literal(self, value, datatype): | ||
| return self.env.createLiteral(value=value, datatype=datatype) | ||
| def make_language_literal(self, value, lang=None): | ||
| if lang: | ||
| return self.env.createLiteral(value=value, language=lang) | ||
| else: | ||
| return self.env.createLiteral(value=value) | ||
| def make_named_node(self, iri): | ||
| return self.env.createNamedNode(iri) | ||
| def make_blank_node(self, label=None): | ||
| if label: | ||
| return self._call_state.bnodes[label] | ||
| else: | ||
| return self.env.createBlankNode() | ||
| def make_triple(self, subject, predicate, object): | ||
| return self.env.createTriple(subject, predicate, object) | ||
| def make_quad(self, subject, predicate, object, graph): | ||
| return self.env.createQuad(subject, predicate, object, graph) | ||
| def _prepare_parse(self, graph): | ||
| self._call_state.bnodes = defaultdict(self.env.createBlankNode) | ||
| self._call_state.graph = graph | ||
| def _cleanup_parse(self): | ||
| del self._call_state.bnodes | ||
| del self._call_state.graph | ||
| def _make_graph(self): | ||
| return self.env.createGraph() |
| """Parse RDF serialized as jsonld | ||
| Usage:: | ||
| from pymantic.parsers.jsonld import jsonld_parser | ||
| graph = jsonld_parser.parse_json(json.load(io.open('file.jsonld', mode='rt'))) | ||
| """ | ||
| import json | ||
| from .base import BaseParser | ||
| class PyLDLoader(BaseParser): | ||
| class _Loader: | ||
| def __init__(self, pyld_loader): | ||
| self.pyld_loader = pyld_loader | ||
| def parse_file(self, f): | ||
| jobj = json.load(f) | ||
| self.pyld_loader.process_jobj(jobj) | ||
| def parse(self, string): | ||
| jobj = json.loads(string) | ||
| self.pyld_loader.process_jobj(jobj) | ||
| def parse_json(self, jobj, sink=None, options=None): | ||
| if sink is None: | ||
| sink = self._make_graph() | ||
| self._prepare_parse(sink) | ||
| self.process_jobj(jobj, options) | ||
| self._cleanup_parse() | ||
| return sink | ||
| def make_quad(self, values): | ||
| quad = self.env.createQuad(*values) | ||
| self._call_state.graph.add(quad) | ||
| return quad | ||
| def _make_graph(self): | ||
| return self.env.createDataset() | ||
| def __init__(self, *args, **kwargs): | ||
| self.document = self._Loader(self) | ||
| super(PyLDLoader, self).__init__(*args, **kwargs) | ||
| def process_triple_fragment(self, triple_fragment): | ||
| if triple_fragment["type"] == "IRI": | ||
| return self.env.createNamedNode(triple_fragment["value"]) | ||
| elif triple_fragment["type"] == "blank node": | ||
| return self._call_state.bnodes[triple_fragment["value"]] | ||
| elif triple_fragment["type"] == "literal": | ||
| language = None | ||
| if "language" in triple_fragment: | ||
| language = triple_fragment["language"] | ||
| return self.env.createLiteral( | ||
| value=triple_fragment["value"], | ||
| datatype=self.env.createNamedNode(triple_fragment["datatype"]), | ||
| language=language, | ||
| ) | ||
| def process_jobj(self, jobj, options=None): | ||
| from pyld.jsonld import to_rdf | ||
| dataset = to_rdf(jobj, options=options) | ||
| for graph_name, triples in dataset.items(): | ||
| graph_iri = ( | ||
| self.env.createNamedNode(graph_name) | ||
| if graph_name != "@default" | ||
| else None | ||
| ) | ||
| for triple in triples: | ||
| self.make_quad( | ||
| ( | ||
| self.process_triple_fragment(triple["subject"]), | ||
| self.process_triple_fragment(triple["predicate"]), | ||
| self.process_triple_fragment(triple["object"]), | ||
| graph_iri, | ||
| ) | ||
| ) | ||
| jsonld_parser = PyLDLoader() |
| from . import turtle as turtle_parser | ||
| from .nquads import nquads_parser | ||
| from .ntriples import ntriples_parser | ||
| __all__ = [ | ||
| "ntriples_parser", | ||
| "turtle_parser", | ||
| "nquads_parser", | ||
| ] |
| class LarkParser: | ||
| """Provide a consistent interface for parsing serialized RDF using one | ||
| of the lark parsers. | ||
| """ | ||
| def __init__(self, lark): | ||
| self.lark = lark | ||
| def line_by_line_parser(self, stream): | ||
| for line in stream: # Equivalent to readline | ||
| if line: | ||
| yield next(self.lark.parse(line)) | ||
| def parse(self, string_or_stream, graph=None): | ||
| """Parse a string or file-like object into RDF primitives and add | ||
| them to either the provided graph or a new graph. | ||
| """ | ||
| tf = self.lark.options.transformer | ||
| try: | ||
| if graph is None: | ||
| graph = tf._make_graph() | ||
| tf._prepare_parse(graph) | ||
| if hasattr(string_or_stream, "readline"): | ||
| triples = self.line_by_line_parser(string_or_stream) | ||
| else: | ||
| # Presume string. | ||
| triples = self.lark.parse(string_or_stream) | ||
| graph.addAll(triples) | ||
| finally: | ||
| tf._cleanup_parse() | ||
| return graph | ||
| def parse_string(self, string_or_bytes, graph=None): | ||
| """Parse a string, decoding it from bytes to UTF-8 if necessary.""" | ||
| if isinstance(string_or_bytes, bytes): | ||
| string = string_or_bytes.decode("utf-8") | ||
| else: | ||
| string = string_or_bytes | ||
| return self.parse(string, graph) |
| """Parse RDF serialized as nquads files. | ||
| Usage:: | ||
| from pymantic.parsers.lark import nquads_parser | ||
| graph = nquads_parser.parse(io.open('a_file.nq', mode='rt')) | ||
| graph2 = nquads_parser.parse("<http://a.example/s> <http://a.example/p> <http://a.example/o> <http://a.example/g> .") | ||
| If ``.parse()`` is called with a file-like object implementing ``readline``, | ||
| it will efficiently parse line by line rather than parsing the entire file. | ||
| """ | ||
| from lark import Lark | ||
| from pymantic.primitives import Quad | ||
| from .base import LarkParser | ||
| from .ntriples import NTriplesTransformer, grammar | ||
| class NQuadsTransformer(NTriplesTransformer): | ||
| """Transform the tokenized nquads into RDF primitives.""" | ||
| def quad(self, children): | ||
| subject, predicate, object_, graph = children | ||
| return self.make_quad(subject, predicate, object_, graph) | ||
| def quads_start(self, children): | ||
| for child in children: | ||
| if isinstance(child, Quad): | ||
| yield child | ||
| nq_lark = Lark( | ||
| grammar, | ||
| start="quads_start", | ||
| parser="lalr", | ||
| transformer=NQuadsTransformer(), | ||
| ) | ||
| # A fully-instantiated nquads parser | ||
| nquads_parser = LarkParser(nq_lark) |
| """Parse RDF serialized as ntriples files. | ||
| Usage:: | ||
| from pymantic.parsers.lark import ntriples_parser | ||
| graph = ntriples_parser.parse(io.open('a_file.nt', mode='rt')) | ||
| graph2 = ntriples_parser.parse("<http://a.example/s> <http://a.example/p> <http://a.example/o> .") | ||
| If ``.parse()`` is called with a file-like object implementing ``readline``, | ||
| it will efficiently parse line by line rather than parsing the entire file. | ||
| """ | ||
| from lark import Lark, Transformer | ||
| from pymantic.parsers.base import BaseParser | ||
| from pymantic.primitives import NamedNode, Triple | ||
| from pymantic.util import decode_literal | ||
| from .base import LarkParser | ||
| grammar = r"""triples_start: triple? (EOL triple)* EOL? | ||
| triple: subject predicate object "." | ||
| quads_start: quad? (EOL quad)* EOL? | ||
| quad: subject predicate object graph "." | ||
| ?subject: iriref | ||
| | BLANK_NODE_LABEL -> blank_node_label | ||
| ?predicate: iriref | ||
| ?object: iriref | ||
| | BLANK_NODE_LABEL -> blank_node_label | ||
| | literal | ||
| ?graph: iriref | ||
| literal: STRING_LITERAL_QUOTE ("^^" iriref | LANGTAG)? | ||
| LANGTAG: "@" /[a-zA-Z]/+ ("-" /[a-zA-Z0_9]/+)* | ||
| EOL: /[\r\n]/+ | ||
| iriref: "<" (/[^\x00-\x20<>"{}|^`\\]/ | UCHAR)* ">" | ||
| STRING_LITERAL_QUOTE: "\"" (/[^\x22\\\x0A\x0D]/ | ECHAR | UCHAR)* "\"" | ||
| BLANK_NODE_LABEL: "_:" (PN_CHARS_U | "0".."9") ((PN_CHARS | ".")* PN_CHARS)? | ||
| UCHAR: "\\u" HEX~4 | "\\U" HEX~8 | ||
| ECHAR: "\\" /[tbnrf"'\\]/ | ||
| PN_CHARS_BASE: /[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF]/ | ||
| PN_CHARS_U: PN_CHARS_BASE | "_" | ":" | ||
| PN_CHARS: PN_CHARS_U | /[\-0-9\u00B7\u0300-\u036F\u203F-\u2040]/ | ||
| HEX: /[0-9A-Fa-f]/ | ||
| %ignore /[ \t]/+ | ||
| """ | ||
| class NTriplesTransformer(BaseParser, Transformer): | ||
| """Transform the tokenized ntriples into RDF primitives.""" | ||
| def blank_node_label(self, children): | ||
| (bn_label,) = children | ||
| return self.make_blank_node(bn_label.value) | ||
| def iriref(self, children): | ||
| iri = "".join(children) | ||
| iri = decode_literal(iri) | ||
| return self.make_named_node(iri) | ||
| def literal(self, children): | ||
| quoted_literal = children[0] | ||
| quoted_literal = quoted_literal[1:-1] # Remove ""s | ||
| literal = decode_literal(quoted_literal) | ||
| if len(children) == 2 and isinstance(children[1], NamedNode): | ||
| type_ = children[1] | ||
| return self.make_datatype_literal(literal, type_) | ||
| elif len(children) == 2 and children[1].type == "LANGTAG": | ||
| lang = children[1][1:] # Remove @ | ||
| return self.make_language_literal(literal, lang) | ||
| else: | ||
| return self.make_language_literal(literal) | ||
| def triple(self, children): | ||
| subject, predicate, object_ = children | ||
| return self.make_triple(subject, predicate, object_) | ||
| def triples_start(self, children): | ||
| for child in children: | ||
| if isinstance(child, Triple): | ||
| yield child | ||
| nt_lark = Lark( | ||
| grammar, | ||
| start="triples_start", | ||
| parser="lalr", | ||
| transformer=NTriplesTransformer(), | ||
| ) | ||
| # A fully-instantiated ntriples parser | ||
| ntriples_parser = LarkParser(nt_lark) |
| """Parse RDF serialized as turtle files. | ||
| Usage:: | ||
| from pymantic.parsers.lark import turtle_parser | ||
| graph = turtle_parser.parse(io.open('a_file.ttl', mode='rt')) | ||
| graph2 = turtle_parser.parse(\"\"\"@prefix p: <http://a.example/s>. | ||
| p: <http://a.example/p> <http://a.example/o> .\"\"\") | ||
| Unlike :mod:`pymantic.parsers.lark.ntriples`, this parser cannot efficiently | ||
| parse turtle line by line. If a file-like object is provided, the entire file | ||
| will be read into memory and parsed there. | ||
| """ | ||
| from lark import Lark, Transformer, Tree | ||
| from lark.lexer import Token | ||
| import re | ||
| from pymantic.parsers.base import BaseParser | ||
| from pymantic.primitives import BlankNode, Literal, NamedNode, Triple | ||
| from pymantic.util import decode_literal, grouper, smart_urljoin | ||
| RDF_TYPE = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type") | ||
| RDF_NIL = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#nil") | ||
| RDF_FIRST = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#first") | ||
| RDF_REST = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest") | ||
| XSD_DECIMAL = NamedNode("http://www.w3.org/2001/XMLSchema#decimal") | ||
| XSD_DOUBLE = NamedNode("http://www.w3.org/2001/XMLSchema#double") | ||
| XSD_INTEGER = NamedNode("http://www.w3.org/2001/XMLSchema#integer") | ||
| XSD_BOOLEAN = NamedNode("http://www.w3.org/2001/XMLSchema#boolean") | ||
| XSD_STRING = NamedNode("http://www.w3.org/2001/XMLSchema#string") | ||
| grammar = r"""turtle_doc: statement* | ||
| ?statement: directive | triples "." | ||
| directive: prefix_id | base | sparql_prefix | sparql_base | ||
| prefix_id: "@prefix" PNAME_NS IRIREF "." | ||
| base: BASE_DIRECTIVE IRIREF "." | ||
| sparql_base: /BASE/i IRIREF | ||
| sparql_prefix: /PREFIX/i PNAME_NS IRIREF | ||
| triples: subject predicate_object_list | ||
| | blank_node_property_list predicate_object_list? | ||
| predicate_object_list: verb object_list (";" (verb object_list)?)* | ||
| ?object_list: object ("," object)* | ||
| ?verb: predicate | /a/ | ||
| ?subject: iri | blank_node | collection | ||
| ?predicate: iri | ||
| ?object: iri | blank_node | collection | blank_node_property_list | literal | ||
| ?literal: rdf_literal | numeric_literal | boolean_literal | ||
| blank_node_property_list: "[" predicate_object_list "]" | ||
| collection: "(" object* ")" | ||
| numeric_literal: INTEGER | DECIMAL | DOUBLE | ||
| rdf_literal: string (LANGTAG | "^^" iri)? | ||
| boolean_literal: /true|false/ | ||
| string: STRING_LITERAL_QUOTE | ||
| | STRING_LITERAL_SINGLE_QUOTE | ||
| | STRING_LITERAL_LONG_SINGLE_QUOTE | ||
| | STRING_LITERAL_LONG_QUOTE | ||
| iri: IRIREF | prefixed_name | ||
| prefixed_name: PNAME_LN | PNAME_NS | ||
| blank_node: BLANK_NODE_LABEL | ANON | ||
| BASE_DIRECTIVE: "@base" | ||
| IRIREF: "<" (/[^\x00-\x20<>"{}|^`\\]/ | UCHAR)* ">" | ||
| PNAME_NS: PN_PREFIX? ":" | ||
| PNAME_LN: PNAME_NS PN_LOCAL | ||
| BLANK_NODE_LABEL: "_:" (PN_CHARS_U | /[0-9]/) ((PN_CHARS | ".")* PN_CHARS)? | ||
| LANGTAG: "@" /[a-zA-Z]+/ ("-" /[a-zA-Z0-9]+/)* | ||
| INTEGER: /[+-]?[0-9]+/ | ||
| DECIMAL: /[+-]?[0-9]*/ "." /[0-9]+/ | ||
| DOUBLE: /[+-]?/ (/[0-9]+/ "." /[0-9]*/ EXPONENT | ||
| | "." /[0-9]+/ EXPONENT | /[0-9]+/ EXPONENT) | ||
| EXPONENT: /[eE][+-]?[0-9]+/ | ||
| STRING_LITERAL_QUOTE: "\"" (/[^\x22\\\x0A\x0D]/ | ECHAR | UCHAR)* "\"" | ||
| STRING_LITERAL_SINGLE_QUOTE: "'" (/[^\x27\\\x0A\x0D]/ | ECHAR | UCHAR)* "'" | ||
| STRING_LITERAL_LONG_SINGLE_QUOTE: "'''" (/'|''/? (/[^'\\]/ | ECHAR | UCHAR))* "'''" | ||
| STRING_LITERAL_LONG_QUOTE: "\"\"\"" (/"|""/? (/[^"\\]/ | ECHAR | UCHAR))* "\"\"\"" | ||
| UCHAR: "\\u" HEX~4 | "\\U" HEX~8 | ||
| ECHAR: "\\" /[tbnrf"'\\]/ | ||
| WS: /[\x20\x09\x0D\x0A]/ | ||
| ANON: "[" WS* "]" | ||
| PN_CHARS_BASE: /[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\U00010000-\U000EFFFF]/ | ||
| PN_CHARS_U: PN_CHARS_BASE | "_" | ||
| PN_CHARS: PN_CHARS_U | /[\-0-9\u00B7\u0300-\u036F\u203F-\u2040]/ | ||
| PN_PREFIX: PN_CHARS_BASE ((PN_CHARS | ".")* PN_CHARS)? | ||
| PN_LOCAL: (PN_CHARS_U | ":" | /[0-9]/ | PLX) ((PN_CHARS | "." | ":" | PLX)* (PN_CHARS | ":" | PLX))? | ||
| PLX: PERCENT | PN_LOCAL_ESC | ||
| PERCENT: "%" HEX~2 | ||
| HEX: /[0-9A-Fa-f]/ | ||
| PN_LOCAL_ESC: "\\" /[_~\.\-!$&'()*+,;=\/?#@%]/ | ||
| %ignore WS | ||
| COMMENT: "#" /[^\n]/* | ||
| %ignore COMMENT | ||
| """ | ||
| turtle_lark = Lark(grammar, start="turtle_doc", parser="lalr") | ||
| LEGAL_IRI = re.compile(r'^[^\x00-\x20<>"{}|^`\\]*$') | ||
| def validate_iri(iri): | ||
| if not LEGAL_IRI.match(iri): | ||
| raise ValueError("Illegal characters in IRI: " + iri) | ||
| return iri | ||
| def unpack_predicate_object_list(subject, pol): | ||
| if not isinstance(subject, (NamedNode, BlankNode)): | ||
| for triple_or_node in subject: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| else: | ||
| subject = triple_or_node | ||
| break | ||
| for predicate, object_ in grouper(pol, 2): | ||
| if isinstance(predicate, Token): | ||
| if predicate.value != "a": | ||
| raise ValueError(predicate) | ||
| predicate = RDF_TYPE | ||
| if not isinstance(object_, (NamedNode, Literal, BlankNode)): | ||
| if isinstance(object_, Tree): | ||
| object_ = object_.children | ||
| for triple_or_node in object_: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| else: | ||
| object_ = triple_or_node | ||
| yield Triple(subject, predicate, object_) | ||
| else: | ||
| yield Triple(subject, predicate, object_) | ||
| class TurtleTransformer(BaseParser, Transformer): | ||
| def __init__(self, base_iri=""): | ||
| super().__init__() | ||
| self.base_iri = base_iri | ||
| self.prefixes = self.profile.prefixes | ||
| def decode_iriref(self, iriref): | ||
| return validate_iri(decode_literal(iriref[1:-1])) | ||
| def iri(self, children): | ||
| (iriref_or_pname,) = children | ||
| if iriref_or_pname.startswith("<"): | ||
| return self.make_named_node( | ||
| smart_urljoin(self.base_iri, self.decode_iriref(iriref_or_pname)) | ||
| ) | ||
| return iriref_or_pname | ||
| def predicate_object_list(self, children): | ||
| return children | ||
| def triples(self, children): | ||
| if len(children) == 2: | ||
| subject = children[0] | ||
| for triple in unpack_predicate_object_list(subject, children[1]): | ||
| yield triple | ||
| elif len(children) == 1: | ||
| for triple_or_node in children[0]: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| def prefixed_name(self, children): | ||
| (pname,) = children | ||
| ns, _, ln = pname.partition(":") | ||
| return self.make_named_node(self.prefixes[ns] + decode_literal(ln)) | ||
| def prefix_id(self, children): | ||
| ns, iriref = children | ||
| iri = smart_urljoin(self.base_iri, self.decode_iriref(iriref)) | ||
| ns = ns[:-1] # Drop trailing : from namespace | ||
| self.prefixes[ns] = iri | ||
| return [] | ||
| def sparql_prefix(self, children): | ||
| return self.prefix_id(children[1:]) | ||
| def base(self, children): | ||
| base_directive, base_iriref = children | ||
| # Workaround for lalr parser token ambiguity in python 2.7 | ||
| if base_directive.startswith("@") and base_directive != "@base": | ||
| raise ValueError("Unexpected @base: " + base_directive) | ||
| self.base_iri = smart_urljoin(self.base_iri, self.decode_iriref(base_iriref)) | ||
| return [] | ||
| def sparql_base(self, children): | ||
| return self.base(children) | ||
| def blank_node(self, children): | ||
| (bn,) = children | ||
| if bn.type == "ANON": | ||
| return self.make_blank_node() | ||
| elif bn.type == "BLANK_NODE_LABEL": | ||
| return self.make_blank_node(bn.value) | ||
| else: | ||
| raise NotImplementedError() | ||
| def blank_node_property_list(self, children): | ||
| pl_root = self.make_blank_node() | ||
| for pl_item in unpack_predicate_object_list(pl_root, children[0]): | ||
| yield pl_item | ||
| yield pl_root | ||
| def collection(self, children): | ||
| prev_node = RDF_NIL | ||
| for value in reversed(children): | ||
| this_bn = self.make_blank_node() | ||
| if not isinstance(value, (NamedNode, Literal, BlankNode)): | ||
| for triple_or_node in value: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| else: | ||
| value = triple_or_node | ||
| break | ||
| yield self.make_triple(this_bn, RDF_FIRST, value) | ||
| yield self.make_triple(this_bn, RDF_REST, prev_node) | ||
| prev_node = this_bn | ||
| yield prev_node | ||
| def numeric_literal(self, children): | ||
| (numeric,) = children | ||
| if numeric.type == "DECIMAL": | ||
| return self.make_datatype_literal(numeric, datatype=XSD_DECIMAL) | ||
| elif numeric.type == "DOUBLE": | ||
| return self.make_datatype_literal(numeric, datatype=XSD_DOUBLE) | ||
| elif numeric.type == "INTEGER": | ||
| return self.make_datatype_literal(numeric, datatype=XSD_INTEGER) | ||
| else: | ||
| raise NotImplementedError() | ||
| def rdf_literal(self, children): | ||
| literal_string = children[0] | ||
| lang = None | ||
| type_ = None | ||
| if len(children) == 2 and isinstance(children[1], NamedNode): | ||
| type_ = children[1] | ||
| return self.make_datatype_literal(literal_string, type_) | ||
| elif len(children) == 2 and children[1].type == "LANGTAG": | ||
| lang = children[1][1:] # Remove @ | ||
| return self.make_language_literal(literal_string, lang) | ||
| else: | ||
| return self.make_datatype_literal(literal_string, datatype=XSD_STRING) | ||
| def boolean_literal(self, children): | ||
| (boolean,) = children | ||
| return self.make_datatype_literal(boolean, datatype=XSD_BOOLEAN) | ||
| def string(self, children): | ||
| (literal,) = children | ||
| if literal.type in ( | ||
| "STRING_LITERAL_QUOTE", | ||
| "STRING_LITERAL_SINGLE_QUOTE", | ||
| ): | ||
| string = decode_literal(literal[1:-1]) | ||
| if literal.type in ( | ||
| "STRING_LITERAL_LONG_SINGLE_QUOTE", | ||
| "STRING_LITERAL_LONG_QUOTE", | ||
| ): | ||
| string = decode_literal(literal[3:-3]) | ||
| return string | ||
| def turtle_doc(self, children): | ||
| for child in children: | ||
| if not isinstance(child, Tree): | ||
| for triple in child: | ||
| yield triple | ||
| def parse(string_or_stream, graph=None, base=""): | ||
| if hasattr(string_or_stream, "readline"): | ||
| string = string_or_stream.read() | ||
| else: | ||
| # Presume string. | ||
| string = string_or_stream | ||
| if isinstance(string_or_stream, bytes): | ||
| string = string_or_stream.decode("utf-8") | ||
| else: | ||
| string = string_or_stream | ||
| tree = turtle_lark.parse(string) | ||
| tr = TurtleTransformer(base_iri=base) | ||
| if graph is None: | ||
| graph = tr._make_graph() | ||
| tr._prepare_parse(graph) | ||
| graph.addAll(tr.transform(tree)) | ||
| return graph | ||
| def parse_string(string_or_bytes, graph=None, base=""): | ||
| return parse(string_or_bytes, graph, base) |
| from lxml import etree | ||
| import re | ||
| from threading import local | ||
| from urllib.parse import urljoin | ||
| from pymantic.primitives import BlankNode, NamedNode | ||
| scheme_re = re.compile(r"[a-zA-Z](?:[a-zA-Z0-9]|\+|-|\.)*") | ||
| class RDFXMLParser: | ||
| RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type" | ||
| def __init__(self): | ||
| self.namespaces = { | ||
| "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", | ||
| } | ||
| self._call_state = local() | ||
| def clark(self, prefix, tag): | ||
| return "{%s}%s" % (self.namespaces[prefix], tag) | ||
| def parse(self, f, sink=None): | ||
| self._call_state.bnodes = {} | ||
| tree = etree.parse(f) | ||
| if tree.getroot() != self.clark("rdf", "RDF"): | ||
| raise ValueError("Invalid XML document.") | ||
| for element in tree.getroot(): | ||
| self._handle_resource(element, sink) | ||
| def _handle_resource(self, element, sink): | ||
| from pymantic.primitives import NamedNode, Triple | ||
| subject = self._determine_subject(element) | ||
| if element.tag != self.clark("rdf", "Description"): | ||
| resource_class = self._resolve_tag(element) | ||
| sink.add(Triple(subject, NamedNode(self.RDF_TYPE), resource_class)) | ||
| for property_element in element: | ||
| if property_element.tag == self.clark("rdf", "li"): | ||
| pass | ||
| else: | ||
| predicate = self._resolve_tag(property_element) | ||
| if self.clark("rdf", "resource") in property_element.attrib: | ||
| object_ = self._resolve_uri( | ||
| property_element, | ||
| property_element.attrib[self.clark("rdf", "resource")], | ||
| ) | ||
| sink.add(Triple(subject, NamedNode(predicate), NamedNode(object_))) | ||
| return subject | ||
| def _resolve_tag(self, element): | ||
| if element.tag[0] == "{": | ||
| tag_bits = element[1:].partition("}") | ||
| return NamedNode(tag_bits[0] + tag_bits[2]) | ||
| else: | ||
| return NamedNode(urljoin(element.base, element.tag)) | ||
| def _determine_subject(self, element): | ||
| if ( | ||
| self.clark("rdf", "about") not in element.attrib | ||
| and self.clark("rdf", "nodeID") not in element.attrib | ||
| and self.clark("rdf", "ID") not in element.attrib | ||
| ): | ||
| return BlankNode() | ||
| elif self.clark("rdf", "nodeID") in element.attrib: | ||
| node_id = element.attrib[self.clark("rdf", "nodeID")] | ||
| if node_id not in self._call_state.bnodes: | ||
| self._call_state.bnodes[node_id] = BlankNode() | ||
| return self._call_state.bnodes[node_id] | ||
| elif self.clark("rdf", "ID") in element.attrib: | ||
| if not element.base: | ||
| raise ValueError("No XML base for %r", element) | ||
| return NamedNode( | ||
| element.base + "#" + element.attrib[self.clark("rdf", "ID")] | ||
| ) | ||
| elif self.clark("rdf", "about") in element.attrib: | ||
| return self._resolve_uri( | ||
| element, element.attrib[self.clark("rdf", "resource")] | ||
| ) | ||
| def _resolve_uri(self, element, uri): | ||
| if not scheme_re.match(uri): | ||
| return NamedNode(urljoin(element.base, uri)) | ||
| else: | ||
| return NamedNode(uri) |
| __all__ = [ | ||
| "Triple", | ||
| "Quad", | ||
| "q_as_t", | ||
| "t_as_q", | ||
| "Literal", | ||
| "NamedNode", | ||
| "Prefix", | ||
| "BlankNode", | ||
| "Graph", | ||
| "Dataset", | ||
| "PrefixMap", | ||
| "TermMap", | ||
| "parse_curie", | ||
| "is_language", | ||
| "lang_match", | ||
| "to_curie", | ||
| "Profile", | ||
| ] | ||
| import collections | ||
| from collections import defaultdict | ||
| import datetime | ||
| from operator import itemgetter | ||
| from pymantic.serializers import nt_escape | ||
| import pymantic.uri_schemes as uri_schemes | ||
| from pymantic.util import quote_normalized_iri | ||
| def is_language(lang): | ||
| """Is something a valid XML language?""" | ||
| if isinstance(lang, NamedNode): | ||
| return False | ||
| return True | ||
| def lang_match(lang1, lang2): | ||
| """Determines if two languages are, in fact, the same language. | ||
| Eg: en is the same as en-us and en-uk.""" | ||
| if lang1 is None and lang2 is None: | ||
| return True | ||
| elif lang1 is None or lang2 is None: | ||
| return False | ||
| lang1 = lang1.partition("-") | ||
| lang2 = lang2.partition("-") | ||
| return lang1[0] == lang2[0] and ( | ||
| lang1[2] == "" or lang2[2] == "" or lang1[2] == lang2[2] | ||
| ) | ||
| def parse_curie(curie, prefixes): | ||
| """ | ||
| Parses a CURIE within the context of the given namespaces. Will also accept | ||
| explicit URIs and wrap them in an rdflib URIRef. | ||
| Specifically: | ||
| 1) If the CURIE is not of the form [stuff] and the prefix is in the list of | ||
| standard URIs, it is wrapped in a URIRef and returned unchanged. | ||
| 2) Otherwise, the CURIE is parsed by the rules of CURIE Syntax 1.0: | ||
| http://www.w3.org/TR/2007/WD-curie-20070307/ The default namespace is | ||
| the namespace keyed by the empty string in the namespaces dictionary. | ||
| 3) If the CURIE's namespace cannot be resolved, a ValueError is raised. | ||
| """ | ||
| definitely_curie = False | ||
| if curie[0] == "[" and curie[-1] == "]": | ||
| curie = curie[1:-1] | ||
| definitely_curie = True | ||
| prefix, sep, reference = curie.partition(":") | ||
| if not definitely_curie: | ||
| if prefix in uri_schemes.schemes: | ||
| return NamedNode(curie) | ||
| if not reference and "" in prefixes: | ||
| reference = prefix | ||
| return Prefix(prefixes[""])(reference) | ||
| if prefix in prefixes: | ||
| return Prefix(prefixes[prefix])(reference) | ||
| else: | ||
| raise ValueError( | ||
| f"Could not parse CURIE prefix {prefix} from prefixes {prefixes}" | ||
| ) | ||
| def parse_curies(curies, namespaces): | ||
| """Parse multiple CURIEs at once.""" | ||
| for curie in curies: | ||
| yield parse_curie(curie, namespaces) | ||
| def to_curie(uri, namespaces, seperator=":", explicit=False): | ||
| """Converts a URI to a CURIE using the prefixes defined in namespaces. If | ||
| there is no matching prefix, return the URI unchanged. | ||
| namespaces - a dictionary of prefix -> namespace mappings. | ||
| separator - the character to use as the separator between the prefix and | ||
| the local name. | ||
| explicit - if True and the URI can be abbreviated, wrap the abbreviated | ||
| form in []s to indicate that it is definitely a CURIE.""" | ||
| matches = [] | ||
| for prefix, namespace in namespaces.items(): | ||
| if uri.startswith(namespace): | ||
| matches.append((prefix, namespace)) | ||
| if len(matches) > 0: | ||
| prefix, namespace = sorted(matches, key=lambda pair: -len(pair[1]))[0] | ||
| if explicit: | ||
| return f"[{uri.replace(namespace, prefix + seperator)}]" | ||
| else: | ||
| return uri.replace(namespace, prefix + seperator) | ||
| return uri | ||
| class Triple(tuple): | ||
| """Triple(subject, predicate, object) | ||
| The Triple interface represents an RDF Triple. The stringification of a | ||
| Triple results in an N-Triples. | ||
| """ | ||
| __slots__ = () | ||
| _fields = ("subject", "predicate", "object") | ||
| def __new__(_cls, subject, predicate, object): | ||
| return tuple.__new__(_cls, (subject, predicate, object)) | ||
| @classmethod | ||
| def _make(cls, iterable, new=tuple.__new__, len=len): | ||
| "Make a new Triple object from a sequence or iterable" | ||
| result = new(cls, iterable) | ||
| if len(result) != 3: | ||
| raise TypeError("Expected 3 arguments, got %d" % len(result)) | ||
| return result | ||
| def __repr__(self): | ||
| return "Triple(subject=%r, predicate=%r, object=%r)" % self | ||
| def _asdict(t): | ||
| "Return a new dict which maps field names to their values" | ||
| return {"subject": t[0], "predicate": t[1], "object": t[2]} | ||
| def _replace(_self, **kwds): | ||
| "Return a new Triple object replacing specified fields with new values" | ||
| result = _self._make(map(kwds.pop, ("subject", "predicate", "object"), _self)) | ||
| if kwds: | ||
| raise ValueError("Got unexpected field names: %r" % kwds.keys()) | ||
| return result | ||
| def __getnewargs__(self): | ||
| return tuple(self) | ||
| subject = property(itemgetter(0)) | ||
| predicate = property(itemgetter(1)) | ||
| object = property(itemgetter(2)) | ||
| def __str__(self): | ||
| return f"{self.subject.toNT()} {self.predicate.toNT()} {self.object.toNT()} .\n" | ||
| def toString(self): | ||
| return str(self) | ||
| class Quad(tuple): | ||
| "Quad(subject, predicate, object, graph)" | ||
| __slots__ = () | ||
| _fields = ("subject", "predicate", "object", "graph") | ||
| def __new__(_cls, subject, predicate, object, graph): | ||
| return tuple.__new__(_cls, (subject, predicate, object, graph)) | ||
| @classmethod | ||
| def _make(cls, iterable, new=tuple.__new__, len=len): | ||
| "Make a new Quad object from a sequence or iterable" | ||
| result = new(cls, iterable) | ||
| if len(result) != 4: | ||
| raise TypeError("Expected 4 arguments, got %d" % len(result)) | ||
| return result | ||
| def __repr__(self): | ||
| return "Quad(subject=%r, predicate=%r, object=%r, graph=%r)" % self | ||
| def _asdict(t): | ||
| "Return a new dict which maps field names to their values" | ||
| return { | ||
| "subject": t[0], | ||
| "predicate": t[1], | ||
| "object": t[2], | ||
| "graph": t[3], | ||
| } | ||
| def _replace(_self, **kwds): | ||
| "Return a new Quad object replacing specified fields with new values" | ||
| result = _self._make( | ||
| map(kwds.pop, ("subject", "predicate", "object", "graph"), _self) | ||
| ) | ||
| if kwds: | ||
| raise ValueError("Got unexpected field names: %r" % kwds.keys()) | ||
| return result | ||
| def __getnewargs__(self): | ||
| return tuple(self) | ||
| subject = property(itemgetter(0)) | ||
| predicate = property(itemgetter(1)) | ||
| object = property(itemgetter(2)) | ||
| graph = property(itemgetter(3)) | ||
| def __str__(self): | ||
| return f"{str(self.subject)} {str(self.predicate)} {str(self.object)} {str(self.graph)} .\n" | ||
| def q_as_t(quad): | ||
| return Triple(quad.subject, quad.predicate, quad.object) | ||
| def t_as_q(graph_name, triple): | ||
| return Quad(triple.subject, triple.predicate, triple.object, graph_name) | ||
| class Literal(tuple): | ||
| """Literal(`value`, `language`, `datatype`) | ||
| Literals represent values such as numbers, dates and strings in RDF data. A | ||
| Literal is comprised of three attributes: | ||
| * a lexical representation of the nominalValue | ||
| * an optional language represented by a string token | ||
| * an optional datatype specified by a NamedNode | ||
| Literals representing plain text in a natural language may have a language | ||
| attribute specified by a text string token, as specified in [BCP47], | ||
| normalized to lowercase (e.g., 'en', 'fr', 'en-gb'). | ||
| Literals may not have both a datatype and a language.""" | ||
| __slots__ = () | ||
| _fields = ("value", "language", "datatype") | ||
| types = { | ||
| int: lambda v: (str(v), XSD("integer")), | ||
| datetime.datetime: lambda v: (v.isoformat(), XSD("dateTime")), | ||
| } | ||
| def __new__(_cls, value, language=None, datatype=None): | ||
| if not isinstance(value, str): | ||
| value, auto_datatype = _cls.types[type(value)](value) | ||
| if datatype is None: | ||
| datatype = auto_datatype | ||
| return tuple.__new__(_cls, (value, language, datatype)) | ||
| @classmethod | ||
| def _make(cls, iterable, new=tuple.__new__, len=len): | ||
| "Make a new Literal object from a sequence or iterable" | ||
| result = new(cls, iterable) | ||
| if len(result) != 3: | ||
| raise TypeError("Expected 3 arguments, got %d" % len(result)) | ||
| return result | ||
| def __repr__(self): | ||
| return "Literal(value=%r, language=%r, datatype=%r)" % self | ||
| def _asdict(t): | ||
| "Return a new dict which maps field names to their values" | ||
| return {"value": t[0], "language": t[1], "datatype": t[2]} | ||
| def _replace(_self, **kwds): | ||
| "Return a new Literal object replacing specified fields with new value" | ||
| result = _self._make(map(kwds.pop, ("value", "language", "datatype"), _self)) | ||
| if kwds: | ||
| raise ValueError("Got unexpected field names: %r" % kwds.keys()) | ||
| return result | ||
| def __getnewargs__(self): | ||
| return tuple(self) | ||
| value = property(itemgetter(0)) | ||
| language = property(itemgetter(1)) | ||
| datatype = property(itemgetter(2)) | ||
| interfaceName = "Literal" | ||
| def __str__(self): | ||
| return str(self.value) | ||
| def toNT(self): | ||
| quoted = '"' + nt_escape(self.value) + '"' | ||
| if self.language: | ||
| return f"{quoted}@{self.language}" | ||
| elif self.datatype: | ||
| return f"{quoted}^^{self.datatype.toNT()}" | ||
| else: | ||
| return quoted | ||
| class NamedNode(str): | ||
| """A node identified by an IRI.""" | ||
| interfaceName = "NamedNode" | ||
| @property | ||
| def value(self): | ||
| return self | ||
| def __repr__(self): | ||
| return f"NamedNode({self.toNT()})" | ||
| def __str__(self): | ||
| return self.value | ||
| def toNT(self): | ||
| return f"<{nt_escape(quote_normalized_iri(self.value))}>" | ||
| class Prefix(NamedNode): | ||
| """Node that when called returns the the argument conctantated with | ||
| self.""" | ||
| def __call__(self, name): | ||
| return NamedNode(self + name) | ||
| XSD = Prefix("http://www.w3.org/2001/XMLSchema#") | ||
| class BlankNode: | ||
| """A BlankNode is a reference to an unnamed resource (one for which an IRI | ||
| is not known), and may be used in a Triple as a unique reference to that | ||
| unnamed resource. | ||
| BlankNodes are stringified by prepending "_:" to a unique value, for | ||
| instance _:b142 or _:me, this stringified form is referred to as a | ||
| "blank node identifier".""" | ||
| interfaceName = "BlankNode" | ||
| @property | ||
| def value(self): | ||
| return "".join(chr(ord(c) + 17) for c in hex(id(self))[2:]) | ||
| def __repr__(self): | ||
| return "BlankNode()" | ||
| def __str__(self): | ||
| return "_:" + self.value | ||
| def toNT(self): | ||
| return str(self) | ||
| def Index(): | ||
| return defaultdict(Index) | ||
| class Graph: | ||
| """A `Graph` holds a set of one or more `Triple`. Implements the Python | ||
| set/sequence API for `in`, `for`, and `len`""" | ||
| def __init__(self, graph_uri=None): | ||
| if not isinstance(graph_uri, NamedNode): | ||
| graph_uri = NamedNode(graph_uri) | ||
| self._uri = graph_uri | ||
| self._triples = set() | ||
| self._spo = Index() | ||
| self._pos = Index() | ||
| self._osp = Index() | ||
| self._actions = set() | ||
| @property | ||
| def uri(self): | ||
| """URI name of the graph, if it has been given a name""" | ||
| return self._uri | ||
| def addAction(self, action): | ||
| self._actions.add(action) | ||
| return self | ||
| def add(self, triple): | ||
| """Adds the specified Triple to the graph. This method returns the | ||
| graph instance it was called on.""" | ||
| self._triples.add(triple) | ||
| self._spo[triple.subject][triple.predicate][triple.object] = triple | ||
| self._pos[triple.predicate][triple.object][triple.subject] = triple | ||
| self._osp[triple.object][triple.subject][triple.predicate] = triple | ||
| return self | ||
| def remove(self, triple): | ||
| """Removes the specified Triple from the graph. This method returns the | ||
| graph instance it was called on.""" | ||
| self._triples.remove(triple) | ||
| del self._spo[triple.subject][triple.predicate][triple.object] | ||
| del self._pos[triple.predicate][triple.object][triple.subject] | ||
| del self._osp[triple.object][triple.subject][triple.predicate] | ||
| return self | ||
| def match(self, subject=None, predicate=None, object=None): | ||
| """This method returns a new sequence of triples which is comprised of | ||
| all those triples in the current instance which match the given | ||
| arguments, that is, for each triple in this graph, it is included in | ||
| the output graph, if: | ||
| * calling triple.subject.equals with the specified subject as an | ||
| argument returns true, or the subject argument is null, AND | ||
| * calling triple.property.equals with the specified property as an | ||
| argument returns true, or the property argument is null, AND | ||
| * calling triple.object.equals with the specified object as an argument | ||
| returns true, or the object argument is null | ||
| This method implements AND functionality, so only triples matching all | ||
| of the given non-null arguments will be included in the result. | ||
| """ | ||
| if subject: | ||
| if predicate: # s, p, ??? | ||
| if object: # s, p, o | ||
| if Triple(subject, predicate, object) in self: | ||
| yield Triple(subject, predicate, object) | ||
| else: # s, p, ?var | ||
| if subject in self._spo and predicate in self._spo[subject]: | ||
| for triple in self._spo[subject][predicate].values(): | ||
| yield triple | ||
| else: # s, ?var, ??? | ||
| if object: # s, ?var, o | ||
| if object in self._osp and subject in self._osp[object]: | ||
| for triple in self._osp[object][subject].values(): | ||
| yield triple | ||
| else: # s, ?var, ?var | ||
| if subject in self._spo: | ||
| for predicate in self._spo[subject]: | ||
| for triple in self._spo[subject][predicate].values(): | ||
| yield triple | ||
| elif predicate: # ?var, p, ??? | ||
| if object: # ?var, p, o | ||
| if predicate in self._pos and object in self._pos[predicate]: | ||
| for triple in self._pos[predicate][object].values(): | ||
| yield triple | ||
| else: # ?var, p, ?var | ||
| if predicate in self._pos: | ||
| for object in self._pos[predicate]: | ||
| for triple in self._pos[predicate][object].values(): | ||
| yield triple | ||
| elif object: # ?var, ?var, o | ||
| if object in self._osp: | ||
| for subject in self._osp[object]: | ||
| for triple in self._osp[object][subject].values(): | ||
| yield triple | ||
| else: | ||
| for triple in self._triples: | ||
| yield triple | ||
| def removeMatches(self, subject, predicate, object): | ||
| """This method removes those triples in the current graph which match | ||
| the given arguments.""" | ||
| for triple in self.match(subject, predicate, object): | ||
| self.remove(triple) | ||
| return self | ||
| def addAll(self, graph_or_triples): | ||
| """Imports the graph or set of triples in to this graph. This method | ||
| returns the graph instance it was called on.""" | ||
| for triple in graph_or_triples: | ||
| self.add(triple) | ||
| return self | ||
| def merge(self, graph): | ||
| """Returns a new Graph which is a concatenation of this graph and the | ||
| graph given as an argument.""" | ||
| new_graph = Graph() | ||
| for triple in graph: | ||
| new_graph.add(triple) | ||
| for triple in self: | ||
| new_graph.add(triple) | ||
| return new_graph | ||
| def __contains__(self, item): | ||
| return item in self._triples | ||
| def __len__(self): | ||
| return len(self._triples) | ||
| def __iter__(self): | ||
| return iter(self._triples) | ||
| def toArray(self): | ||
| """Return the set of :py:class:`Triple` within the :py:class:`Graph`""" | ||
| return frozenset(self._triples) | ||
| def subjects(self): | ||
| """Returns an iterator over subjects in the graph.""" | ||
| return self._spo.keys() | ||
| def predicates(self): | ||
| """Returns an iterator over predicates in the graph.""" | ||
| return self._pos.keys() | ||
| def objects(self): | ||
| """Returns an iterator over objects in the graph.""" | ||
| return self._osp.keys() | ||
| class Dataset: | ||
| def __init__(self): | ||
| self._graphs = defaultdict(Graph) | ||
| def add(self, quad): | ||
| self._graphs[quad.graph]._uri = quad.graph | ||
| self._graphs[quad.graph].add(q_as_t(quad)) | ||
| def remove(self, quad): | ||
| self._graphs[quad.graph].remove(q_as_t(quad)) | ||
| def add_graph(self, graph, named=None): | ||
| name = named or graph.uri | ||
| if name: | ||
| graph._uri = name | ||
| self._graphs[graph.uri] = graph | ||
| else: | ||
| raise ValueError("Graph must be named") | ||
| def remove_graph(self, graph_or_uri): | ||
| pass | ||
| @property | ||
| def graphs(self): | ||
| return self._graphs.values() | ||
| def match(self, subject=None, predicate=None, object=None, graph=None): | ||
| if graph: | ||
| matches = self._graphs[graph].match(subject, predicate, object) | ||
| for match in matches: | ||
| yield t_as_q(graph, match) | ||
| else: | ||
| for graph_uri, graph in self._graphs.items(): | ||
| for match in graph.match(subject, predicate, object): | ||
| yield t_as_q(graph_uri, match) | ||
| def removeMatches(self, subject=None, predicate=None, object=None, graph=None): | ||
| """This method removes those triples in the current graph which match | ||
| the given arguments.""" | ||
| for quad in self.match(subject, predicate, object, graph): | ||
| self.remove(quad) | ||
| return self | ||
| def addAll(self, dataset_or_quads): | ||
| """Imports the graph or set of triples in to this graph. This method | ||
| returns the graph instance it was called on.""" | ||
| for quad in dataset_or_quads: | ||
| self.add(quad) | ||
| return self | ||
| def __len__(self): | ||
| return sum(len(g) for g in self.graphs) | ||
| def __contains__(self, item): | ||
| if hasattr(item, "graph"): | ||
| if item.graph in self._graphs: | ||
| graph = self._graphs[item.graph] | ||
| return q_as_t(item) in graph | ||
| else: | ||
| for graph in self._graphs.values(): | ||
| if item in graph: | ||
| return True | ||
| def __iter__(self): | ||
| for graph in self._graphs.values(): | ||
| for triple in graph: | ||
| yield t_as_q(graph.uri, triple) | ||
| def toArray(self): | ||
| return frozenset(self) | ||
| # RDF Enviroment Interfaces | ||
| class PrefixMap(collections.OrderedDict): | ||
| """A map of prefixes to IRIs, and provides methods to | ||
| turn one in to the other. | ||
| Example Usage: | ||
| >>> prefixes = PrefixMap() | ||
| Create a new prefix mapping for the prefix "rdfs" | ||
| >>> prefixes['rdfs'] = "http://www.w3.org/2000/01/rdf-schema#" | ||
| Resolve a known CURIE | ||
| >>> prefixes.resolve("rdfs:label") | ||
| u"http://www.w3.org/2000/01/rdf-schema#label" | ||
| Shrink an IRI for a known CURIE in to a CURIE | ||
| >>> prefixes.shrink("http://www.w3.org/2000/01/rdf-schema#label") | ||
| u"rdfs:label" | ||
| Attempt to resolve a CURIE with an empty prefix | ||
| >>> prefixes.resolve(":me") | ||
| ":me" | ||
| Set the default prefix and attempt to resolve a CURIE with an empty prefix | ||
| >>> prefixes.setDefault("http://example.org/bob#") | ||
| >>> prefixes.resolve(":me") | ||
| u"http://example.org/bob#me" | ||
| """ | ||
| def resolve(self, curie): | ||
| """Given a valid CURIE for which a prefix is known (for example | ||
| "rdfs:label"), this method will return the resulting IRI (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label")""" | ||
| return parse_curie(curie, self) | ||
| def shrink(self, iri): | ||
| """Given an IRI for which a prefix is known (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label") this method returns a | ||
| CURIE (for example "rdfs:label"), if no prefix is known the original | ||
| IRI is returned.""" | ||
| return to_curie(iri, self) | ||
| def addAll(self, other, override=False): | ||
| if override: | ||
| self.update(other) | ||
| else: | ||
| for key, value in other.items(): | ||
| if key not in self: | ||
| self[key] = value | ||
| return self | ||
| def setDefault(self, iri): | ||
| """Set the iri to be used when resolving CURIEs without a prefix, for | ||
| example ":this".""" | ||
| self[""] = iri | ||
| class TermMap(dict): | ||
| """A map of simple string terms to IRIs, and provides methods to turn one | ||
| in to the other. | ||
| Example usage: | ||
| >>> terms = TermMap() | ||
| Create a new term mapping for the term "member" | ||
| >>> terms['member'] = "http://www.w3.org/ns/org#member" | ||
| Resolve a known term to an IRI | ||
| >>> terms.resolve("member") | ||
| u"http://www.w3.org/ns/org#member" | ||
| Shrink an IRI for a known term to a term | ||
| >>> terms.shrink("http://www.w3.org/ns/org#member") | ||
| u"member" | ||
| Attempt to resolve an unknown term | ||
| >>> terms.resolve("label") | ||
| None | ||
| Set the default term vocabulary and then attempt to resolve an unknown term | ||
| >>> terms.setDefault("http://www.w3.org/2000/01/rdf-schema#") | ||
| >>> terms.resolve("label") | ||
| u"http://www.w3.org/2000/01/rdf-schema#label" | ||
| """ | ||
| def addAll(self, other, override=False): | ||
| if override: | ||
| self.update(other) | ||
| else: | ||
| for key, value in other.items(): | ||
| if key not in self: | ||
| self[key] = value | ||
| return self | ||
| def resolve(self, term): | ||
| """Given a valid term for which an IRI is known (for example "label"), | ||
| this method will return the resulting IRI (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label"). | ||
| If no term is known and a default has been set, the IRI is obtained by | ||
| concatenating the term and the default iri. | ||
| If no term is known and no default is set, then this method returns | ||
| null.""" | ||
| if hasattr(self, "default"): | ||
| return self.get(term, self.default + term) | ||
| else: | ||
| return self.get(term) | ||
| def setDefault(self, iri): | ||
| """The default iri to be used when an term cannot be resolved, the | ||
| resulting IRI is obtained by concatenating this iri with the term being | ||
| resolved.""" | ||
| self.default = iri | ||
| def shrink(self, iri): | ||
| """Given an IRI for which an term is known (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label") this method returns a | ||
| term (for example "label"), if no term is known the original IRI is | ||
| returned.""" | ||
| for term, v in self.items(): | ||
| if v == iri: | ||
| return term | ||
| return iri | ||
| class Profile: | ||
| """Profiles provide an easy to use context for negotiating between CURIEs, | ||
| Terms and IRIs.""" | ||
| def __init__(self, prefixes=None, terms=None): | ||
| self.prefixes = prefixes or PrefixMap() | ||
| self.terms = terms or TermMap() | ||
| if "rdf" not in self.prefixes: | ||
| self.prefixes["rdf"] = "http://www.w3.org/1999/02/22-rdf-syntax-ns#" | ||
| if "xsd" not in self.prefixes: | ||
| self.prefixes["xsd"] = "http://www.w3.org/2001/XMLSchema#" | ||
| def resolve(self, toresolve): | ||
| """Given an Term or CURIE this method will return an IRI, or null if it | ||
| cannot be resolved. | ||
| If toresolve contains a : (colon) then this method returns the result | ||
| of calling prefixes.resolve(toresolve) | ||
| otherwise this method returns the result of calling | ||
| terms.resolve(toresolve)""" | ||
| if ":" in toresolve: | ||
| return self.prefixes.resolve(toresolve) | ||
| else: | ||
| return self.terms.resolve(toresolve) | ||
| def setDefaultVocabulary(self, iri): | ||
| """This method sets the default vocabulary for use when resolving | ||
| unknown terms, it is identical to calling the setDefault method on | ||
| terms.""" | ||
| self.terms.setDefault(iri) | ||
| def setDefaultPrefix(self, iri): | ||
| """This method sets the default prefix for use when resolving CURIEs | ||
| without a prefix, for example ":me", it is identical to calling the | ||
| setDefault method on prefixes.""" | ||
| self.prefixes.setDefault(iri) | ||
| def setTerm(self, term, iri): | ||
| """This method associates an IRI with a term, it is identical to | ||
| calling the set method on term.""" | ||
| self.terms[term] = iri | ||
| def setPrefix(self, prefix, iri): | ||
| """This method associates an IRI with a prefix, it is identical to | ||
| calling the set method on prefixes.""" | ||
| self.prefixes[prefix] = iri | ||
| def importProfile(self, profile, override=False): | ||
| """This method functions the same as calling | ||
| prefixes.addAll(profile.prefixes, override) and | ||
| terms.addAll(profile.terms, override), and allows easy updating and | ||
| merging of different profiles. | ||
| This method returns the instance on which it was called.""" | ||
| self.prefixes.addAll(profile.prefixes, override) | ||
| self.terms.addAll(profile.terms, override) | ||
| return self | ||
| class RDFEnvironment(Profile): | ||
| """The RDF Environment is an interface which exposes a high level API for | ||
| working with RDF in a programming environment.""" | ||
| def createBlankNode(self): | ||
| """Creates a new :py:class:`BlankNode`.""" | ||
| return BlankNode() | ||
| def createNamedNode(self, value): | ||
| """Creates a new :py:class:`NamedNode`.""" | ||
| return NamedNode(value) | ||
| def createLiteral(self, value, language=None, datatype=None): | ||
| """Creates a :py:class:`Literal` given a value, an optional language | ||
| and/or an | ||
| optional datatype.""" | ||
| return Literal(value, language, datatype) | ||
| def createTriple(self, subject, predicate, object): | ||
| """Creates a :py:class:`Triple` given a subject, predicate and | ||
| object.""" | ||
| return Triple(subject, predicate, object) | ||
| def createGraph(self, triples=tuple()): | ||
| """Creates a new :py:class:`Graph`, an optional sequence of | ||
| :py:class:`Triple` to include within the graph may be specified, this | ||
| allows easy transition between native sequences and Graphs and is the | ||
| counterpart for :py:meth:`Graph.toArray`.""" | ||
| g = Graph() | ||
| g.addAll(triples) | ||
| return g | ||
| def createAction(self, test, action): | ||
| raise NotImplementedError() | ||
| def createProfile(self, empty=False): | ||
| if empty: | ||
| return Profile() | ||
| else: | ||
| return Profile(self.prefixes, self.terms) | ||
| def createTermMap(self, empty=False): | ||
| if empty: | ||
| return TermMap() | ||
| else: | ||
| return TermMap(self.terms) | ||
| def createPrefixMap(self, empty=False): | ||
| if empty: | ||
| return PrefixMap() | ||
| else: | ||
| return PrefixMap(self.prefixes) | ||
| # Pymantic DataSet Extensions | ||
| def createQuad(self, subject, predicate, object, graph): | ||
| return Quad(subject, predicate, object, graph) | ||
| def createDataset(self, quads=tuple()): | ||
| ds = Dataset() | ||
| ds.addAll(quads) | ||
| return ds |
| """Provides common classes and functions for modelling an RDF graph using | ||
| Python objects.""" | ||
| import logging | ||
| from pymantic.primitives import ( | ||
| BlankNode, | ||
| Literal, | ||
| NamedNode, | ||
| Prefix, | ||
| PrefixMap, | ||
| Profile, | ||
| Triple, | ||
| is_language, | ||
| lang_match, | ||
| parse_curie, | ||
| ) | ||
| import pymantic.util as util | ||
| log = logging.getLogger(__name__) | ||
| class MetaResource(type): | ||
| """Aggregates Prefix and scalar information.""" | ||
| _classes = {} # Map of RDF classes to Python classes. | ||
| def __new__(cls, name, bases, dct): | ||
| prefixes = PrefixMap() | ||
| scalars = set() | ||
| for base in bases: | ||
| if hasattr(base, "prefixes"): | ||
| prefixes.update(base.prefixes) | ||
| if hasattr(base, "scalars"): | ||
| scalars.update(base.scalars) | ||
| if "prefixes" in dct: | ||
| for prefix in dct["prefixes"]: | ||
| prefixes[prefix] = Prefix(dct["prefixes"][prefix]) | ||
| dct["prefixes"] = prefixes | ||
| if "scalars" in dct: | ||
| for scalar in dct["scalars"]: | ||
| scalars.add(parse_curie(scalar, prefixes)) | ||
| dct["scalars"] = frozenset(scalars) | ||
| dct["_meta_resource"] = cls | ||
| return type.__new__(cls, name, bases, dct) | ||
| def register_class(rdf_type): | ||
| """Register a class for automatic instantiation VIA Resource.classify.""" | ||
| def _register_class(python_class): | ||
| MetaResource._classes[python_class.resolve(rdf_type)] = python_class | ||
| python_class.rdf_classes = frozenset((python_class.resolve(rdf_type),)) | ||
| return python_class | ||
| return _register_class | ||
| class URLRetrievalError(Exception): | ||
| """Raised when an attempt to retrieve a resource returns a status other | ||
| than 200 OK.""" | ||
| pass | ||
| class Resource(metaclass=MetaResource): | ||
| """Provides necessary context and utility methods for accessing a Resource | ||
| in an RDF graph. Resources can be used as-is, but are likely somewhat | ||
| unwieldy, since all predicate access must be by complete URL and produces | ||
| sets. By subclassing Resource, you can take advantage of a number of | ||
| quality-of-life features: | ||
| 1) Bind prefixes to prefixes, and refer to them using CURIEs when | ||
| accessing predicates or explicitly resolving CURIEs. Store a dictionary | ||
| mapping prefixes to URLs in the 'prefixes' attribute of your subclass. | ||
| The prefixes dictionaries on all parents are merged with this | ||
| dictionary, and those at the bottom are prioritized. The values in the | ||
| dictionaries will automatically be turned into rdflib Prefix objects. | ||
| 2) Define predicates as scalars. This asserts that a given predicate on this | ||
| resource will only have zero or one value for a given language or | ||
| data-type, or one reference to another resource. This is done using the | ||
| 'scalars' set, which is processed and merged just like prefixes. | ||
| 3) Automatically classify certain RDF types as certain Resource subclasses. | ||
| Decorate your class with the pymantic.RDF.register_class decorator, and | ||
| provide it with the corresponding RDF type. Whenever this type is | ||
| encountered when retrieving objects from a predicate it will | ||
| automatically be instantiated as your class rather than a generic Resource. | ||
| RDF allows for resources to have multiple types. When a resource is | ||
| encountered with two or more types that have different python classes | ||
| registered for them, a new python class is created. This new class | ||
| subclasses all applicable registered classes. | ||
| If you want to perform this classification manually (to, for example, | ||
| instantiate the correct class for an arbitrary URI), you can do so by | ||
| calling Resource.classify. You can also create a new instance of a | ||
| Resource by calling .new on a subclass. | ||
| Automatic retrieval of resources with no type information is currently | ||
| implemented here, but is likely to be refactored into a separate persistence | ||
| layer in the near future.""" | ||
| prefixes = { | ||
| "rdf": "http://www.w3.org/1999/02/22-rdf-syntax-ns#", | ||
| "rdfs": "http://www.w3.org/2000/01/rdf-schema#", | ||
| } | ||
| scalars = frozenset(("rdfs:label",)) | ||
| lang = "en" | ||
| rdf_classes = frozenset() | ||
| global_profile = Profile() | ||
| def __init__(self, graph, subject): | ||
| self.graph = graph | ||
| if not isinstance(subject, NamedNode) and not isinstance(subject, BlankNode): | ||
| subject = NamedNode(subject) | ||
| self.subject = subject | ||
| @classmethod | ||
| def new(cls, graph, subject=None): | ||
| """Add type information to the graph for a new instance of this Resource.""" | ||
| # for prefix, Prefix in cls.prefixes.iteritems(): | ||
| # graph.bind(prefix, Prefix) | ||
| if subject is None: | ||
| subject = BlankNode() | ||
| if not isinstance(subject, NamedNode): | ||
| subject = NamedNode(subject) | ||
| for rdf_class in cls.rdf_classes: | ||
| graph.add(Triple(subject, cls.resolve("rdf:type"), rdf_class)) | ||
| return cls(graph, subject) | ||
| def erase(self): | ||
| """Erase all tripes for this resource from the graph.""" | ||
| for triple in list(self.graph.match(self.subject, None, None)): | ||
| self.graph.remove(triple) | ||
| def is_a(self): | ||
| """Test to see if the subject of this resource has all the necessary | ||
| RDF classes applied to it.""" | ||
| if hasattr(self, "rdf_classes"): | ||
| for rdf_class in self.rdf_classes: | ||
| if not any( | ||
| self.graph.match(self.subject, self.resolve("rdf:type"), rdf_class) | ||
| ): | ||
| return False | ||
| return True | ||
| @classmethod | ||
| def resolve(cls, key): | ||
| """Use this class's prefixes to resolve a curie""" | ||
| try: | ||
| return cls.prefixes.resolve(key) | ||
| except ValueError: | ||
| return cls.global_profile.resolve(key) | ||
| def __eq__(self, other): | ||
| if isinstance(other, Resource): | ||
| return self.subject == other.subject | ||
| elif isinstance(other, NamedNode) or isinstance(other, str): | ||
| return str(self.subject) == str(other) | ||
| return NotImplemented | ||
| def __ne__(self, other): | ||
| eq = self.__eq__(other) | ||
| if eq is NotImplemented: | ||
| return NotImplemented | ||
| else: | ||
| return not eq | ||
| def __hash__(self): | ||
| return hash(self.subject) | ||
| def bare_literals(self, predicate): | ||
| """Objects for a predicate that are language-less, datatype-less Literals.""" | ||
| return [ | ||
| t.object | ||
| for t in self.graph.match(self.subject, predicate, None) | ||
| if hasattr(t.object, "language") | ||
| and t.object.language is None | ||
| and hasattr(t.object, "datatype") | ||
| and t.object.datatype is None | ||
| ] | ||
| def objects_by_lang(self, predicate, lang=None): | ||
| """Objects for a predicate that match a specified language or, if | ||
| language is None, have a language specified.""" | ||
| if lang: | ||
| return [ | ||
| t.object | ||
| for t in self.graph.match(self.subject, predicate, None) | ||
| if hasattr(t.object, "language") and lang_match(lang, t.object.language) | ||
| ] | ||
| elif lang == "": | ||
| return self.bare_literals(predicate) | ||
| else: | ||
| return [ | ||
| t.object | ||
| for t in self.graph.match(self.subject, predicate, None) | ||
| if hasattr(t.object, "language") and t.object.language is not None | ||
| ] | ||
| def objects_by_datatype(self, predicate, datatype=None): | ||
| """Objects for a predicate that match a specified datatype or, if | ||
| datatype is None, have a datatype specified.""" | ||
| if datatype: | ||
| return [ | ||
| t.object | ||
| for t in self.graph.match(self.subject, predicate, None) | ||
| if hasattr(t.object, "datatype") and t.object.datatype == datatype | ||
| ] | ||
| elif datatype == "": | ||
| return self.bare_literals(predicate) | ||
| else: | ||
| return [ | ||
| t.object | ||
| for t in self.graph.match(self.subject, predicate, None) | ||
| if hasattr(t.object, "datatype") and t.object.datatype is not None | ||
| ] | ||
| def objects_by_type(self, predicate, resource_class=None): | ||
| """Objects for a predicate that are instances of a particular Resource | ||
| subclass or, if resource_class is none, are Resources.""" | ||
| selected_objects = [] | ||
| for t in self.graph.match(self.subject, predicate, None): | ||
| obj = t.object | ||
| if isinstance(obj, BlankNode) or isinstance(obj, NamedNode): | ||
| if resource_class is None or isinstance( | ||
| self.classify(self.graph, obj), resource_class | ||
| ): | ||
| selected_objects.append(obj) | ||
| return selected_objects | ||
| def objects(self, predicate): | ||
| """All objects for a predicate.""" | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None)] | ||
| def object_of(self, predicate=None): | ||
| """All subjects for which this resource is an object for the given | ||
| predicate.""" | ||
| if predicate is None: | ||
| for triple in self.graph.match(None, None, self.subject): | ||
| yield (self.classify(self.graph, triple.subject), triple.predicate) | ||
| else: | ||
| predicate = self.resolve(predicate) | ||
| for triple in self.graph.match(None, predicate, self.subject): | ||
| yield self.classify(self.graph, triple.subject) | ||
| def __getitem__(self, key): | ||
| """Fetch predicates off this subject by key dictionary-style. | ||
| This is the primary mechanism for predicate access. You can either | ||
| provide a predicate name, as a complete URL or CURIE: | ||
| resource['rdfs:label'] | ||
| resource['http://www.w3.org/2000/01/rdf-schema#label'] | ||
| Or a predicate name and a datatype or language: | ||
| resource['rdfs:label', 'en'] | ||
| Passing in a value of None will result in all values for the predicate | ||
| in question being returned.""" | ||
| predicate, objects = self._objects_for_key(key) | ||
| if predicate not in self.scalars or (isinstance(key, tuple) and key[1] is None): | ||
| def getitem_iter_results(): | ||
| for obj in objects: | ||
| yield self.classify(self.graph, obj) | ||
| return getitem_iter_results() | ||
| else: | ||
| return self.classify(self.graph, util.one_or_none(objects)) | ||
| def get_scalar(self, key): | ||
| """As __getitem__ access, but pretend the key is a scalar even if it isn't. | ||
| Expect random exceptions if using this carelessly.""" | ||
| predicate, objects = self._objects_for_key(key) | ||
| return self.classify(self.graph, util.one_or_none(objects)) | ||
| # Set item | ||
| def __setitem__(self, key, value): | ||
| """Sets objects for predicates for this subject by key dictionary-style. | ||
| Returns 'self', for easy chaining. | ||
| 1) Setting a predicate without a filter replaces the set of all objects | ||
| for that predicate. The exception is assigning a Literal object with | ||
| a language to a scalar predicate. This will only replace objects that | ||
| share its language, though any resources or datatyped literals will | ||
| be removed. | ||
| 2) Setting a predicate with a filter will only replace objects that | ||
| match the specified filter, including all resource references for | ||
| language or datatype filters. The exception is scalars, where | ||
| datatyped literals and objects will replace everything else, and | ||
| language literals can co-exist but will replace all datatyped | ||
| literals. | ||
| 3) Attempting to set a literal that doesn't make sense will raise a | ||
| ValueError. For example, including an english or dateTime literal | ||
| when setting a predicate's objects using a French language filter | ||
| will result in a ValueError. Object references are always acceptable | ||
| to include.""" | ||
| predicate, lang, datatype, rdf_class = self._interpret_key(key) | ||
| value = literalize(self.graph, value, lang, datatype) | ||
| if not isinstance(key, tuple): | ||
| # Implicit specification. | ||
| objects = self._objects_for_implicit_set(predicate, value) | ||
| else: | ||
| # Explicit specification. | ||
| objects = self._objects_for_explicit_set( | ||
| predicate, value, lang, datatype, rdf_class | ||
| ) | ||
| for obj in objects: | ||
| self.graph.remove(Triple(self.subject, predicate, obj)) | ||
| if isinstance(value, frozenset): | ||
| for obj in value: | ||
| if isinstance(obj, Resource): | ||
| self.graph.add(Triple(self.subject, predicate, obj.subject)) | ||
| else: | ||
| self.graph.add(Triple(self.subject, predicate, obj)) | ||
| else: | ||
| if isinstance(value, Resource): | ||
| self.graph.add(Triple(self.subject, predicate, value.subject)) | ||
| else: | ||
| self.graph.add(Triple(self.subject, predicate, value)) | ||
| return self | ||
| # Delete item | ||
| def __delitem__(self, key): | ||
| """Deletes predicates for this subject by key dictionary-style. | ||
| del resource[key] will always remove the same things from the graph as | ||
| resource[key] returns.""" | ||
| predicate, objects = self._objects_for_key(key) | ||
| for obj in objects: | ||
| self.graph.remove(Triple(self.subject, predicate, obj)) | ||
| # Membership test | ||
| def __contains__(self, predicate): | ||
| """Uses the same logic as __getitem__ to determine if a predicate or | ||
| filtered predicate is present for this object.""" | ||
| predicate, objects = self._objects_for_key(predicate) | ||
| if objects: | ||
| return True | ||
| return False | ||
| def __iter__(self): | ||
| for s, p, o in self.graph.match(self.subject, None, None): | ||
| yield p, o | ||
| @classmethod | ||
| def in_graph(cls, graph): | ||
| """Iterate through all instances of this Resource in the graph.""" | ||
| subjects = set() | ||
| for rdf_class in cls.rdf_classes: | ||
| if not subjects: | ||
| subjects.update( | ||
| [ | ||
| t.subject | ||
| for t in graph.match(None, cls.resolve("rdf:type"), rdf_class) | ||
| ] | ||
| ) | ||
| else: | ||
| subjects.intersection_update( | ||
| [ | ||
| t.subject | ||
| for t in graph.match(None, cls.resolve("rdf:type"), rdf_class) | ||
| ] | ||
| ) | ||
| return set(cls(graph, subject) for subject in subjects) | ||
| def __repr__(self): | ||
| return "<%r: %s>" % (type(self), self.subject) | ||
| def __str__(self): | ||
| if self["rdfs:label"]: | ||
| return self["rdfs:label"].value | ||
| else: | ||
| return str(self.subject) | ||
| @classmethod | ||
| def classify(cls, graph, obj): | ||
| """Classify an object into an appropriate registered class, or Resource. | ||
| May create a new class if necessary that is a subclass of two or more | ||
| registered Resource classes.""" | ||
| if obj is None: | ||
| return None | ||
| if isinstance(obj, Literal): | ||
| return obj | ||
| if any(graph.match(obj, cls.resolve("rdf:type"), None)): | ||
| # retrieve_resource(graph, obj) | ||
| if not any(graph.match(obj, cls.resolve("rdf:type"), None)): | ||
| return Resource(graph, obj) | ||
| types = frozenset( | ||
| [t.object for t in graph.match(obj, cls.resolve("rdf:type"), None)] | ||
| ) | ||
| python_classes = tuple( | ||
| cls._meta_resource._classes[t] | ||
| for t in types | ||
| if t in cls._meta_resource._classes | ||
| ) | ||
| if len(python_classes) == 0: | ||
| return Resource(graph, obj) | ||
| elif len(python_classes) == 1: | ||
| return python_classes[0](graph, obj) | ||
| else: | ||
| if types not in cls._meta_resource._classes: | ||
| the_class = cls._meta_resource.__new__( | ||
| cls._meta_resource, | ||
| "".join(python_class.__name__ for python_class in python_classes), | ||
| python_classes, | ||
| {"_autocreate": True}, | ||
| ) | ||
| cls._meta_resource._classes[types] = the_class | ||
| the_class.rdf_classes = frozenset(types) | ||
| return cls._meta_resource._classes[types](graph, obj) | ||
| def _interpret_key(self, key): | ||
| """Break up a key into a predicate name and optional language or | ||
| datatype specifier.""" | ||
| lang = None | ||
| datatype = None | ||
| rdf_class = None | ||
| if isinstance(key, tuple) and len(key) >= 2: | ||
| if key[1] is None: | ||
| pass # All values are already None, do nothing. | ||
| elif isinstance(key[1], MetaResource): | ||
| rdf_class = key[1] | ||
| elif is_language(key[1]): | ||
| lang = key[1] | ||
| else: | ||
| datatype = self._interpret_datatype(key[1]) | ||
| predicate = self.resolve(key[0]) | ||
| else: | ||
| predicate = self.resolve(key) | ||
| if not isinstance(key, tuple) and predicate in self.scalars: | ||
| lang = self.lang | ||
| return predicate, lang, datatype, rdf_class | ||
| def _interpret_datatype(self, datatype): | ||
| """Deal with xsd:string vs. plain literal""" | ||
| if datatype == "": | ||
| return "" | ||
| elif datatype == "http://www.w3.org/2001/XMLSchema#string": | ||
| return "" | ||
| else: | ||
| return datatype | ||
| def _objects_for_key(self, key): | ||
| """Find objects that are potentially interesting when doing normal | ||
| dictionary key-style access - IE, __getitem__, __delitem__, __contains__, | ||
| and pretty much everything but __setitem__.""" | ||
| predicate, lang, datatype, rdf_class = self._interpret_key(key) | ||
| # log.debug("predicate: %r lang: %r datatype: %r rdf_class: %r", predicate, lang, datatype, rdf_class) | ||
| if lang is None and datatype is None and rdf_class is None: | ||
| objects = self.objects(predicate) | ||
| elif lang: | ||
| objects = self.objects_by_lang(predicate, lang) | ||
| if not isinstance(key, tuple) and predicate in self.scalars and not objects: | ||
| objects += self.objects_by_type(predicate) | ||
| if not objects: | ||
| objects += self.objects_by_datatype(predicate) | ||
| if not objects: | ||
| objects += self.bare_literals(predicate) | ||
| if predicate not in self.scalars: | ||
| objects += self.objects_by_type(predicate) | ||
| elif datatype: | ||
| objects = self.objects_by_datatype(predicate, datatype) | ||
| if predicate not in self.scalars: | ||
| objects += self.objects_by_type(predicate) | ||
| elif rdf_class: | ||
| objects = self.objects_by_type(predicate, rdf_class) | ||
| elif lang == "" or datatype == "": | ||
| objects = self.bare_literals(predicate) | ||
| else: | ||
| raise KeyError("Invalid key: " + repr(key)) | ||
| return predicate, objects | ||
| def _objects_for_implicit_set(self, predicate, value): | ||
| """Find the objects that should be removed from the graph when doing a | ||
| dictionary-style set with implicit type information.""" | ||
| if ( | ||
| isinstance(value, frozenset) | ||
| or (isinstance(value, tuple) and not isinstance(value, Literal)) | ||
| ) and predicate in self.scalars: | ||
| raise ValueError("Cannot store sequences in scalars") | ||
| elif ( | ||
| predicate in self.scalars and isinstance(value, Literal) and value.language | ||
| ): | ||
| return ( | ||
| self.objects_by_lang(predicate, value.language) | ||
| + self.objects_by_datatype(predicate) | ||
| + self.objects_by_type(predicate) | ||
| + self.bare_literals(predicate) | ||
| ) | ||
| else: | ||
| return self.objects(predicate) | ||
| def _objects_for_explicit_set(self, predicate, value, lang, datatype, rdf_class): | ||
| """Find the objects that should be removed from the graph when doing a | ||
| dictionary-style set with explicit type information.""" | ||
| if not check_objects(self.graph, value, lang, datatype, rdf_class): | ||
| raise ValueError("Improper value provided.") | ||
| if lang and predicate in self.scalars: | ||
| return ( | ||
| self.objects_by_lang(predicate, lang) | ||
| + self.objects_by_datatype(predicate) | ||
| + self.objects_by_type(predicate) | ||
| ) | ||
| elif lang and predicate not in self.scalars: | ||
| return self.objects_by_lang(predicate, lang) + self.objects_by_type( | ||
| predicate | ||
| ) | ||
| elif predicate in self.scalars: | ||
| return self.objects(predicate) | ||
| elif datatype: | ||
| return self.objects_by_datatype(predicate, datatype) + self.objects_by_type( | ||
| predicate | ||
| ) | ||
| elif rdf_class: | ||
| return self.objects_by_type(predicate, rdf_class) | ||
| def copy(self, target_subject): | ||
| """Create copies of all triples with this resource as their subject | ||
| with the target subject as their subject. Returns a classified version | ||
| of the target subject.""" | ||
| if not isinstance(target_subject, NamedNode) and not isinstance( | ||
| target_subject, BlankNode | ||
| ): | ||
| target_subject = NamedNode(target_subject) | ||
| for t in self.graph.match(self.subject, None, None): | ||
| self.graph.add((target_subject, t.predicate, t.object)) | ||
| return self.classify(self.graph, target_subject) | ||
| def as_(self, target_class): | ||
| return target_class(self.graph, self.subject) | ||
| class List(Resource): | ||
| """Convenience class for dealing with RDF lists. | ||
| Requires considerable use of ``as_``, due to the utter lack of type | ||
| information on said lists.""" | ||
| scalars = frozenset(("rdf:first", "rdf:rest")) | ||
| def __iter__(self): | ||
| """Iterating over lists works differently from normal Resources.""" | ||
| current = self | ||
| while current.subject != self.resolve("rdf:nil"): | ||
| yield current["rdf:first"] | ||
| current = current["rdf:rest"] | ||
| if current.subject != self.resolve("rdf:nil"): | ||
| current = current.as_(type(self)) | ||
| @classmethod | ||
| def is_list(cls, node, graph): | ||
| """Determine if a given node is plausibly the subject of a list element.""" | ||
| return bool(list(graph.match(subject=node, predicate=cls.resolve("rdf:rest")))) | ||
| def literalize(graph, value, lang, datatype): | ||
| """Convert either a value or a sequence of values to either a Literal or | ||
| a Resource.""" | ||
| if ( | ||
| isinstance(value, set) | ||
| or isinstance(value, frozenset) | ||
| or isinstance(value, list) | ||
| or (isinstance(value, tuple) and not isinstance(value, Literal)) | ||
| ): | ||
| return frozenset(objectify_value(graph, v, lang, datatype) for v in value) | ||
| else: | ||
| return objectify_value(graph, value, lang, datatype) | ||
| def objectify_value(graph, value, lang=None, datatype=None): | ||
| """Convert a single value into either a Literal or a Resource.""" | ||
| if isinstance(value, BlankNode) or isinstance(value, NamedNode): | ||
| return Resource.classify(graph, value) | ||
| elif isinstance(value, Literal) or isinstance(value, Resource): | ||
| return value | ||
| elif isinstance(value, str): | ||
| return Literal(value, language=lang, datatype=datatype) | ||
| else: | ||
| return Literal(value) | ||
| def check_objects(graph, value, lang, datatype, rdf_class): | ||
| """Determine that value or the things in values are appropriate for the | ||
| specified explicit object access key.""" | ||
| if isinstance(value, frozenset) or ( | ||
| isinstance(value, tuple) and not isinstance(value, Literal) | ||
| ): | ||
| for v in value: | ||
| if ( | ||
| ( | ||
| lang | ||
| and (not hasattr(v, "language") or not lang_match(v.language, lang)) | ||
| ) | ||
| or (datatype and v.datatype != datatype) | ||
| or (rdf_class and not isinstance(v, rdf_class)) | ||
| ): | ||
| return False | ||
| return True | ||
| else: | ||
| return ( | ||
| (lang and lang_match(value.language, lang)) | ||
| or (datatype and value.datatype == datatype) | ||
| or (rdf_class and isinstance(value, rdf_class)) | ||
| ) |
| from collections import OrderedDict | ||
| def nt_escape(node_string): | ||
| """Properly escape strings for n-triples and n-quads serialization.""" | ||
| output_string = "" | ||
| for char in node_string: | ||
| if char == "\u0009": | ||
| output_string += "\\t" | ||
| elif char == "\u000A": | ||
| output_string += "\\n" | ||
| elif char == "\u000D": | ||
| output_string += "\\r" | ||
| elif char == "\u0022": | ||
| output_string += '\\"' | ||
| elif char == "\u005C": | ||
| output_string += "\\\\" | ||
| elif ( | ||
| char >= "\u0020" | ||
| and char <= "\u0021" | ||
| or char >= "\u0023" | ||
| and char <= "\u005B" | ||
| or char >= "\u005D" | ||
| and char <= "\u007E" | ||
| ): | ||
| output_string += char | ||
| elif char >= "\u007F" and char <= "\uFFFF": | ||
| output_string += "\\u%04X" % ord(char) | ||
| elif char >= "\U00010000" and char <= "\U0010FFFF": | ||
| output_string += "\\U%08X" % ord(char) | ||
| return output_string | ||
| def serialize_ntriples(graph, f): | ||
| """Serialize some graph to f as ntriples.""" | ||
| for triple in graph: | ||
| f.write(str(triple)) | ||
| def serialize_nquads(dataset, f): | ||
| """Serialize some graph to f as nquads.""" | ||
| for quad in dataset: | ||
| f.write(str(quad)) | ||
| def default_bnode_name_generator(): | ||
| i = 0 | ||
| while True: | ||
| yield "_b" + str(i) | ||
| i += 1 | ||
| def escape_prefix_local(prefix): | ||
| prefix, colon, local = prefix.partition(":") | ||
| for esc_char in "~.-!$&'()*+,;=:/?#@%_": | ||
| local = local.replace(esc_char, "\\" + esc_char) | ||
| return "".join((prefix, colon, local)) | ||
| def turtle_string_escape(string): | ||
| """Escape a string appropriately for output in turtle form.""" | ||
| from pymantic.util import ECHAR_MAP | ||
| for escaped, value in ECHAR_MAP.items(): | ||
| string = string.replace(value, "\\" + escaped) | ||
| return '"' + string + '"' | ||
| def turtle_repr(node, profile, name_map, bnode_name_maker, base=None): | ||
| """Turn a node in an RDF graph into its turtle representation.""" | ||
| if node.interfaceName == "NamedNode": | ||
| name = profile.prefixes.shrink(node) | ||
| if base and name.startswith(base): | ||
| if base.endswith("#"): | ||
| name = f"<{str(name.replace(base, '#'))}>" | ||
| else: | ||
| name = f"<{str(name.replace(base, ''))}>" | ||
| if name == node: | ||
| name = f"<{str(name)}>" | ||
| else: | ||
| escape_prefix_local(name) | ||
| elif node.interfaceName == "BlankNode": | ||
| if node in name_map: | ||
| name = name_map[node] | ||
| else: | ||
| name = bnode_name_maker.next() | ||
| name_map[node] = name | ||
| elif node.interfaceName == "Literal": | ||
| if node.datatype == profile.resolve("xsd:string"): | ||
| # Simple string. | ||
| name = turtle_string_escape(node.value) | ||
| elif node.datatype is None: | ||
| # String with language? | ||
| name = turtle_string_escape(node.value) | ||
| if node.language: | ||
| name += "@" + node.language | ||
| elif node.datatype == profile.resolve("xsd:integer"): | ||
| name = node.value | ||
| elif node.datatype == profile.resolve("xsd:decimal"): | ||
| name = node.value | ||
| elif node.datatype == profile.resolve("xsd:double"): | ||
| name = node.value | ||
| elif node.datatype == profile.resolve("xsd:boolean"): | ||
| name = node.value | ||
| else: | ||
| # Unrecognized data-type. | ||
| name = turtle_string_escape(node.value) | ||
| name += "^" + turtle_repr(node.datatype, profile, None, None) | ||
| return name | ||
| def turtle_sorted_names(nodes, name_maker): | ||
| """Sort a list of nodes in a graph by turtle name.""" | ||
| return sorted((name_maker(node), node) for node in nodes) | ||
| def serialize_turtle( | ||
| graph, f, base=None, profile=None, bnode_name_generator=default_bnode_name_generator | ||
| ): | ||
| """Serialize a graph to f as turtle, optionally using base IRI base | ||
| and prefix map from profile. If provided, subject_key will be used to order | ||
| subjects, and predicate_key predicates within a subject.""" | ||
| if base is not None: | ||
| f.write("@base <" + base + "> .\n") | ||
| if profile is None: | ||
| from pymantic.primitives import Profile | ||
| profile = Profile() | ||
| for prefix, iri in profile.prefixes.items(): | ||
| if prefix != "rdf": | ||
| f.write("@prefix " + prefix + ": <" + iri + "> .\n") | ||
| name_map = OrderedDict() | ||
| bnode_name_maker = bnode_name_generator() | ||
| def name_maker(n): | ||
| return turtle_repr(n, profile, name_map, bnode_name_maker, base) | ||
| from pymantic.rdf import List | ||
| subjects = [subj for subj in graph.subjects() if not List.is_list(subj, graph)] | ||
| for subject_name, subject in turtle_sorted_names(subjects, name_maker): | ||
| subj_indent_size = len(subject_name) + 1 | ||
| f.write(subject_name + " ") | ||
| predicates = set(t.predicate for t in graph.match(subject=subject)) | ||
| sorted_predicates = turtle_sorted_names(predicates, name_maker) | ||
| for i, (predicate_name, predicate) in enumerate(sorted_predicates): | ||
| if i != 0: | ||
| f.write(" " * subj_indent_size) | ||
| pred_indent_size = subj_indent_size + len(predicate_name) + 1 | ||
| f.write(predicate_name + " ") | ||
| for j, triple in enumerate( | ||
| graph.match(subject=subject, predicate=predicate) | ||
| ): | ||
| if j != 0: | ||
| f.write(",\n" + " " * pred_indent_size) | ||
| if List.is_list(triple.object, graph): | ||
| f.write("(") | ||
| for k, o in enumerate(List(graph, triple.object)): | ||
| if k != 0: | ||
| f.write(" ") | ||
| f.write(name_maker(o)) | ||
| f.write(")") | ||
| else: | ||
| f.write(name_maker(triple.object)) | ||
| f.write(" ;\n") | ||
| f.write(" " * subj_indent_size + ".\n\n") |
| """Provide an interface to SPARQL query endpoints.""" | ||
| import datetime | ||
| from io import StringIO | ||
| import json | ||
| import logging | ||
| from lxml import objectify | ||
| import pytz | ||
| import rdflib | ||
| import requests | ||
| import urllib | ||
| from urllib.parse import urlparse | ||
| log = logging.getLogger(__name__) | ||
| class SPARQLQueryException(Exception): | ||
| """Raised when the SPARQL store returns an HTTP status code other than 200 OK.""" | ||
| pass | ||
| class UnknownSPARQLReturnTypeException(Exception): | ||
| """Raised when the SPARQL store provides a response with an unrecognized content-type.""" | ||
| pass | ||
| class _SelectOrUpdate: | ||
| """A server that can run SPARQL queries.""" | ||
| def __init__( | ||
| self, server, sparql, default_graph=None, named_graph=None, *args, **kwargs | ||
| ): | ||
| self.server = server | ||
| self.sparql = sparql | ||
| self.default_graphs = default_graph | ||
| self.named_graphs = named_graph | ||
| self.headers = dict() | ||
| self.params = dict() | ||
| # abstract methods, see Select for the idea | ||
| def default_graph_uri(self): | ||
| pass | ||
| def named_graph_uri(self): | ||
| pass | ||
| def query_or_update(self): | ||
| pass | ||
| def directContentType(self): | ||
| pass | ||
| def postQueries(self): | ||
| pass | ||
| def execute(self): | ||
| log.debug("Querying: %s with: %r", self.server.query_url, self.sparql) | ||
| sparql = self.sparql.encode("utf-8") | ||
| if self.default_graphs: | ||
| self.params[self.default_graph_uri()] = self.default_graphs | ||
| if self.named_graphs: | ||
| self.params[self.named_graph_uri()] = self.named_graphs | ||
| if self.server.post_directly: | ||
| self.headers["Content-Type"] = self.directContentType() + "; charset=utf-8" | ||
| uri_params = self.params | ||
| data = sparql | ||
| method = "post" | ||
| elif self.postQueries(): | ||
| uri_params = None | ||
| self.params[self.query_or_update()] = sparql | ||
| data = self.params | ||
| method = "post" | ||
| else: | ||
| # select only | ||
| self.params[self.query_or_update()] = sparql | ||
| uri_params = self.params | ||
| data = None | ||
| method = "get" | ||
| response = self.server.s.request( | ||
| method, | ||
| self.server.query_url, | ||
| params=uri_params, | ||
| headers=self.headers, | ||
| data=data, | ||
| **self.server.requests_kwargs | ||
| ) | ||
| if response.status_code == 204: | ||
| return True | ||
| if response.status_code != 200: | ||
| raise SPARQLQueryException( | ||
| "%s: %s\nQuery: %s" % (response.headers, response.content, self.sparql) | ||
| ) | ||
| return response | ||
| class _Select(_SelectOrUpdate): | ||
| acceptable_xml_responses = [ | ||
| "application/rdf+xml", | ||
| "application/sparql-results+xml", | ||
| ] | ||
| acceptable_json_responses = [ | ||
| "application/sparql-results+json", | ||
| "text/turtle", | ||
| ] | ||
| def __init__(self, server, query, output="json", *args, **kwargs): | ||
| super(_Select, self).__init__(server, query, *args, **kwargs) | ||
| if output == "xml": | ||
| self.headers["Accept"] = ",".join(self.acceptable_xml_responses) | ||
| else: | ||
| self.headers["Accept"] = ",".join(self.acceptable_json_responses) | ||
| def default_graph_uri(self): | ||
| return "default-graph-uri" | ||
| def named_graph_uri(self): | ||
| return "named-graph-uri" | ||
| def query_or_update(self): | ||
| return "query" | ||
| def directContentType(self): | ||
| return "application/sparql-query" | ||
| def postQueries(self): | ||
| return self.server.post_queries | ||
| def execute(self): | ||
| response = super(_Select, self).execute() | ||
| format = None | ||
| if response.headers["content-type"].startswith("application/rdf+xml"): | ||
| format = "xml" | ||
| elif response.headers["content-type"].startswith("text/turtle"): | ||
| format = "turtle" | ||
| if format: | ||
| graph = rdflib.ConjunctiveGraph() | ||
| graph.parse(StringIO(response.content), self.query_url, format=format) | ||
| return graph | ||
| elif response.headers["content-type"].startswith( | ||
| "application/sparql-results+json" | ||
| ): | ||
| return json.loads(response.content.decode("utf-8")) | ||
| elif response.headers["content-type"].startswith( | ||
| "application/sparql-results+xml" | ||
| ): | ||
| return objectify.parse(StringIO(response.content)) | ||
| else: | ||
| raise UnknownSPARQLReturnTypeException( | ||
| "Got content of type: %s" % response.headers["content-type"] | ||
| ) | ||
| class _Update(_SelectOrUpdate): | ||
| def default_graph_uri(self): | ||
| return "using-graph-uri" | ||
| def named_graph_uri(self): | ||
| return "using-named-graph-uri" | ||
| def query_or_update(self): | ||
| return "update" | ||
| def directContentType(self): | ||
| return "application/sparql-update" | ||
| def postQueries(self): | ||
| return True | ||
| class SPARQLServer: | ||
| """A server that can run SPARQL queries.""" | ||
| def __init__(self, query_url, post_queries=False, post_directly=False, verify=None): | ||
| self.query_url = query_url | ||
| self.post_queries = post_queries | ||
| self.post_directly = post_directly | ||
| self.requests_kwargs = {} | ||
| if verify is not None: | ||
| self.requests_kwargs = {"verify": verify} | ||
| self.s = requests.Session() | ||
| acceptable_sparql_responses = [ | ||
| "application/sparql-results+json", | ||
| "application/rdf+xml", | ||
| "application/sparql-results+xml", | ||
| ] | ||
| def query(self, sparql, *args, **kwargs): | ||
| """Execute a SPARQL query. | ||
| The return type varies based on what the SPARQL store responds with: | ||
| * application/rdf+xml: an rdflib.ConjunctiveGraph | ||
| * application/sparql-results+json: A dictionary from json | ||
| * application/sparql-results+xml: An lxml.objectify structure | ||
| :param sparql: The SPARQL to execute. | ||
| :returns: The results of the query from the SPARQL store. | ||
| """ | ||
| return _Select(self, sparql, *args, **kwargs).execute() | ||
| def update(self, sparql, **kwargs): | ||
| """Execute a SPARQL update. | ||
| :param sparql: The SPARQL Update request to execute. | ||
| """ | ||
| return _Update(self, sparql, **kwargs).execute() | ||
| class UpdateableGraphStore(SPARQLServer): | ||
| """SPARQL server class that is capable of interacting with SPARQL 1.1 graph stores.""" | ||
| def __init__(self, query_url, dataset_url, param_style=True, **kwargs): | ||
| super(UpdateableGraphStore, self).__init__(query_url, **kwargs) | ||
| self.dataset_url = dataset_url | ||
| self.param_style = param_style | ||
| acceptable_graph_responses = [ | ||
| "text/plain", | ||
| "application/rdf+xml", | ||
| "text/turtle", | ||
| "text/rdf+n3", | ||
| ] | ||
| def request_url(self, graph_uri): | ||
| if self.param_style: | ||
| return self.dataset_url + "?" + urllib.urlencode({"graph": graph_uri}) | ||
| else: | ||
| return urlparse.urljoin(self.dataset_url, urllib.quote_plus(graph_uri)) | ||
| def get(self, graph_uri): | ||
| response = self.s.get( | ||
| self.request_url(graph_uri), | ||
| headers={"Accept": ",".join(self.acceptable_graph_responses)}, | ||
| **self.server.requests_kwargs | ||
| ) | ||
| if response.status_code != 200: | ||
| raise Exception( | ||
| "Error from Graph Store (%s): %s" | ||
| % (response.status_code, response.content) | ||
| ) | ||
| graph = rdflib.ConjunctiveGraph() | ||
| if response.headers["content-type"].startswith("text/plain"): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format="nt") | ||
| elif response.headers["content-type"].startswith("application/rdf+xml"): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format="xml") | ||
| elif response.headers["content-type"].startswith("text/turtle"): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format="turtle") | ||
| elif response.headers["content-type"].startswith("text/rdf+n3"): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format="n3") | ||
| return graph | ||
| def delete(self, graph_uri): | ||
| response = self.s.delete( | ||
| self.request_url(graph_uri), **self.server.request_kwargs | ||
| ) | ||
| if response.status_code not in (200, 202): | ||
| raise Exception( | ||
| "Error from Graph Store (%s): %s" | ||
| % (response.status_code, response.content) | ||
| ) | ||
| def put(self, graph_uri, graph): | ||
| graph_triples = graph.serialize(format="nt") | ||
| response = self.s.put( | ||
| self.request_url(graph_uri), | ||
| data=graph_triples, | ||
| headers={"content-type": "text/plain"}, | ||
| **self.server.requests_kwargs | ||
| ) | ||
| if response.status_code not in (200, 201, 204): | ||
| raise Exception( | ||
| "Error from Graph Store (%s): %s" | ||
| % (response.status_code, response.content) | ||
| ) | ||
| def post(self, graph_uri, graph): | ||
| graph_triples = graph.serialize(format="nt") | ||
| if graph_uri is not None: | ||
| response = self.s.post( | ||
| self.request_url(graph_uri), | ||
| data=graph_triples, | ||
| headers={"content-type": "text/plain"}, | ||
| **self.server.requests_kwargs | ||
| ) | ||
| if response.status_code not in (200, 201, 204): | ||
| raise Exception( | ||
| "Error from Graph Store (%s): %s" | ||
| % (response.status_code, response.content) | ||
| ) | ||
| else: | ||
| response = self.s.post( | ||
| self.dataset_url, | ||
| data=graph_triples, | ||
| headers={"content-type": "text/plain"}, | ||
| **self.server.requests_kwargs | ||
| ) | ||
| if response.status_code != 201: | ||
| raise Exception( | ||
| "Error from Graph Store (%s): %s" | ||
| % (response.status_code, response.content) | ||
| ) | ||
| class PatchableGraphStore(UpdateableGraphStore): | ||
| """A graph store that supports the optional PATCH method of updating RDF graphs.""" | ||
| def patch(self, graph_uri, changeset): | ||
| graph_xml = changeset.serialize(format="xml", encoding="utf-8") | ||
| response = self.s.patch( | ||
| self.request_url(graph_uri), | ||
| data=graph_xml, | ||
| headers={"content-type": "application/vnd.talis.changeset+xml"}, | ||
| **self.server.requests_kwargs | ||
| ) | ||
| if response.status_code not in (200, 201, 204): | ||
| raise Exception( | ||
| "Error from Graph Store (%s): %s" | ||
| % (response.status_code, response.content) | ||
| ) | ||
| return True | ||
| def changeset(a, b, graph_uri): | ||
| """Create an RDF graph with the changeset between graphs a and b.""" | ||
| cs = rdflib.Namespace("http://purl.org/vocab/changeset/schema#") | ||
| graph = rdflib.Graph() | ||
| graph.namespace_manager.bind("cs", cs) | ||
| removal, addition = differences(a, b) | ||
| change_set = rdflib.BNode() | ||
| graph.add((change_set, rdflib.RDF.type, cs["ChangeSet"])) | ||
| graph.add( | ||
| ( | ||
| change_set, | ||
| cs["createdDate"], | ||
| rdflib.Literal(datetime.datetime.now(pytz.UTC).isoformat()), | ||
| ) | ||
| ) | ||
| graph.add((change_set, cs["subjectOfChange"], rdflib.URIRef(graph_uri))) | ||
| for stmt in removal: | ||
| statement = reify(graph, stmt) | ||
| graph.add((change_set, cs["removal"], statement)) | ||
| for stmt in addition: | ||
| statement = reify(graph, stmt) | ||
| graph.add((change_set, cs["addition"], statement)) | ||
| return graph | ||
| def reify(graph, statement): | ||
| """Add reifed statement to graph.""" | ||
| s, p, o = statement | ||
| statement_node = rdflib.BNode() | ||
| graph.add((statement_node, rdflib.RDF.type, rdflib.RDF.Statement)) | ||
| graph.add((statement_node, rdflib.RDF.subject, s)) | ||
| graph.add((statement_node, rdflib.RDF.predicate, p)) | ||
| graph.add((statement_node, rdflib.RDF.object, o)) | ||
| return statement_node | ||
| def differences(a, b, exclude=[]): | ||
| """Return (removes,adds) excluding statements with a predicate in exclude.""" | ||
| exclude = [rdflib.URIRef(excluded) for excluded in exclude] | ||
| return ( | ||
| [s for s in a if s not in b and s[1] not in exclude], | ||
| [s for s in b if s not in a and s[1] not in exclude], | ||
| ) |
| """A complete list of URI schemes registered as of Sept 26th, 2008, used when | ||
| parsing CURIEs to differentiate explicit URIs from CURIEs.""" | ||
| schemes = [ | ||
| "aaa", | ||
| "aaas", | ||
| "acap", | ||
| "cap", | ||
| "cid", | ||
| "crid", | ||
| "data", | ||
| "dav", | ||
| "dict", | ||
| "dns", | ||
| "fax", | ||
| "file", | ||
| "ftp", | ||
| "go", | ||
| "gopher", | ||
| "h323", | ||
| "http", | ||
| "https", | ||
| "icap", | ||
| "im", | ||
| "imap", | ||
| "info", | ||
| "ipp", | ||
| "iris", | ||
| "iris.beep", | ||
| "iris.xpc", | ||
| "iris.xpcs", | ||
| "iris.lwz", | ||
| "ldap", | ||
| "mailto", | ||
| "mid", | ||
| "modem", | ||
| "msrp", | ||
| "msrps", | ||
| "mtqp", | ||
| "mupdate", | ||
| "news", | ||
| "nfs", | ||
| "nntp", | ||
| "opaquelocktoken", | ||
| "pop", | ||
| "pres", | ||
| "rtsp", | ||
| "service", | ||
| "shttp", | ||
| "sip", | ||
| "sips", | ||
| "snmp", | ||
| "soap.beep", | ||
| "soap.beeps", | ||
| "tag", | ||
| "tel", | ||
| "telnet", | ||
| "tftp", | ||
| "thismessage", | ||
| "tip", | ||
| "tv", | ||
| "urn", | ||
| "vemmi", | ||
| "xmlrpc.beep", | ||
| "xmlrpc.beeps", | ||
| "xmpp", | ||
| "z39.50r", | ||
| "z39.50s", | ||
| "afs", | ||
| "dtn", | ||
| "iax", | ||
| "mailserver", | ||
| "pack", | ||
| "tn3270", | ||
| "prospero", | ||
| "snews", | ||
| "videotex", | ||
| "wais", | ||
| ] |
| """Utility functions used throughout pymantic.""" | ||
| __all__ = ["en", "de", "one_or_none", "normalize_iri", "quote_normalized_iri"] | ||
| import re | ||
| from urllib.parse import quote | ||
| def en(value): | ||
| """Returns an RDF literal from the en language for the given value.""" | ||
| from pymantic.primitives import Literal | ||
| return Literal(value, language="en") | ||
| def de(value): | ||
| """Returns an RDF literal from the de language for the given value.""" | ||
| from pymantic.primitives import Literal | ||
| return Literal(value, language="de") | ||
| def one_or_none(values): | ||
| """Fetch the first value from values, or None if values is empty. Raises | ||
| ValueError if values has more than one thing in it.""" | ||
| if not values: | ||
| return None | ||
| if len(values) > 1: | ||
| raise ValueError("Got more than one value.") | ||
| return values[0] | ||
| percent_encoding_re = re.compile( | ||
| r"(?:%(?![01][0-9a-fA-F])(?!20)[a-fA-F0-9][a-fA-F0-9])+" | ||
| ) | ||
| reserved_in_iri = [ | ||
| "%", | ||
| ":", | ||
| "/", | ||
| "?", | ||
| "#", | ||
| "[", | ||
| "]", | ||
| "@", | ||
| "!", | ||
| "$", | ||
| "&", | ||
| "'", | ||
| "(", | ||
| ")", | ||
| "*", | ||
| "+", | ||
| ",", | ||
| ";", | ||
| "=", | ||
| ] | ||
| def percent_decode(regmatch): | ||
| encoded = b"" | ||
| for group in regmatch.group(0)[1:].split("%"): | ||
| encoded += int(group, 16).to_bytes(1, "big") | ||
| uni = encoded.decode("utf-8") | ||
| for res in reserved_in_iri: | ||
| uni = uni.replace(res, "%%%02X" % ord(res)) | ||
| return uni | ||
| def normalize_iri(iri): | ||
| """Normalize an IRI using the Case Normalization (5.3.2.1) and | ||
| Percent-Encoding Normalization (5.3.2.3) from RFC 3987. The IRI should be a | ||
| unicode object.""" | ||
| return percent_encoding_re.sub(percent_decode, iri) | ||
| def percent_encode(char): | ||
| return "".join("%%%02X" % char for char in char.encode("utf-8")) | ||
| def quote_normalized_iri(normalized_iri): | ||
| """Percent-encode a normalized IRI; IE, all reserved characters are presumed | ||
| to be themselves and not percent encoded. All other unsafe characters are | ||
| percent-encoded.""" | ||
| normalized_uri = "".join( | ||
| percent_encode(char) if ord(char) > 127 else char for char in normalized_iri | ||
| ) | ||
| return quote(normalized_uri, safe="".join(reserved_in_iri)) | ||
| def smart_urljoin(base, url): | ||
| """urljoin, only an empty fragment from the relative(?) URL will be | ||
| preserved. | ||
| """ | ||
| from urllib.parse import urljoin | ||
| joined = urljoin(base, url) | ||
| if url.endswith("#") and not joined.endswith("#"): | ||
| joined += "#" | ||
| return joined | ||
| def grouper(iterable, n, fillvalue=None): | ||
| "Collect data into fixed-length chunks or blocks" | ||
| # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" | ||
| from itertools import zip_longest | ||
| args = [iter(iterable)] * n | ||
| return zip_longest(*args, fillvalue=fillvalue) | ||
| ESCAPE_MAP = { | ||
| "t": "\t", | ||
| "b": "\b", | ||
| "n": "\n", | ||
| "r": "\r", | ||
| "f": "\f", | ||
| '"': '"', | ||
| "'": "'", | ||
| "\\": "\\", | ||
| } | ||
| ECHAR_MAP = {v: "\\" + k for k, v in ESCAPE_MAP.items()} | ||
| def process_escape(escape): | ||
| escape = escape.group(0)[1:] | ||
| if escape[0] in ("u", "U"): | ||
| return chr(int(escape[1:], 16)) | ||
| else: | ||
| return ESCAPE_MAP.get(escape[0], escape[0]) | ||
| def decode_literal(literal): | ||
| return re.sub( | ||
| r"\\u[a-fA-F0-9]{4}|\\U[a-fA-F0-9]{8}|\\[^uU]", | ||
| process_escape, | ||
| literal, | ||
| ) |
| from pymantic.rdf import Resource, register_class | ||
| SKOS_NS = "http://www.w3.org/2004/02/skos/core#" | ||
| NS_DICT = dict(skos=SKOS_NS) | ||
| class SKOSResource(Resource): | ||
| namespaces = NS_DICT | ||
| scaler = ["skos:prefLabel"] | ||
| @register_class("skos:Concept") | ||
| class Concept(SKOSResource): | ||
| pass | ||
| @register_class("skos:ConceptScheme") | ||
| class ConceptScheme(SKOSResource): | ||
| pass |
| from io import StringIO | ||
| from pymantic.parsers import ( | ||
| jsonld_parser, | ||
| nquads_parser, | ||
| ntriples_parser, | ||
| turtle_parser, | ||
| ) | ||
| from pymantic.primitives import Graph, Literal, NamedNode, Quad, Triple | ||
| def test_parse_ntriples_named_nodes(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> <http://example.com/objects/2> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> <http://example.com/objects/1> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| NamedNode("http://example.com/objects/2"), | ||
| ) | ||
| in g | ||
| ) | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| NamedNode("http://example.com/objects/1"), | ||
| ) | ||
| in g | ||
| ) | ||
| def test_parse_ntriples_bare_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo" . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "Bar" . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| Literal("Foo"), | ||
| ) | ||
| in g | ||
| ) | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| Literal("Bar"), | ||
| ) | ||
| in g | ||
| ) | ||
| def test_parse_ntriples_language_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo"@en-US . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "Bar"@fr . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| Literal("Foo", language="en-US"), | ||
| ) | ||
| in g | ||
| ) | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| Literal("Bar", language="fr"), | ||
| ) | ||
| in g | ||
| ) | ||
| def test_parse_ntriples_datatyped_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo"^^<http://www.w3.org/2001/XMLSchema#string> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "9.99"^^<http://www.w3.org/2001/XMLSchema#decimal> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| Literal( | ||
| "Foo", datatype=NamedNode("http://www.w3.org/2001/XMLSchema#string") | ||
| ), | ||
| ) | ||
| in g | ||
| ) | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| Literal( | ||
| "9.99", datatype=NamedNode("http://www.w3.org/2001/XMLSchema#decimal") | ||
| ), | ||
| ) | ||
| in g | ||
| ) | ||
| def test_parse_ntriples_mixed_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo"@en-US . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "9.99"^^<http://www.w3.org/2001/XMLSchema#decimal> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| Literal("Foo", language="en-US"), | ||
| ) | ||
| in g | ||
| ) | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| Literal( | ||
| "9.99", datatype=NamedNode("http://www.w3.org/2001/XMLSchema#decimal") | ||
| ), | ||
| ) | ||
| in g | ||
| ) | ||
| def test_parse_ntriples_bnodes(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> _:A1 . | ||
| _:A1 <http://example.com/predicates/2> <http://example.com/objects/1> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| # assert Triple(NamedNode('http://example.com/objects/1'), | ||
| # NamedNode('http://example.com/predicates/1'), | ||
| # NamedNode('http://example.com/objects/2')) in g | ||
| # assert Triple(NamedNode('http://example.com/objects/2'), | ||
| # NamedNode('http://example.com/predicates/2'), | ||
| # NamedNode('http://example.com/objects/1')) in g | ||
| def test_parse_nquads_named_nodes(): | ||
| test_nquads = """<http://example.com/objects/1> <http://example.com/predicates/1> <http://example.com/objects/2> <http://example.com/graphs/1> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> <http://example.com/objects/1> <http://example.com/graphs/1> . | ||
| """ | ||
| g = Graph() | ||
| nquads_parser.parse(StringIO(test_nquads), g) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Quad( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/graphs/1"), | ||
| ) | ||
| in g | ||
| ) | ||
| assert ( | ||
| Quad( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/graphs/1"), | ||
| ) | ||
| in g | ||
| ) | ||
| def test_parse_turtle_example_1(): | ||
| ttl = """@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . | ||
| @prefix dc: <http://purl.org/dc/elements/1.1/> . | ||
| @prefix ex: <http://example.org/stuff/1.0/> . | ||
| <http://www.w3.org/TR/rdf-syntax-grammar> | ||
| dc:title "RDF/XML Syntax Specification (Revised)" ; | ||
| ex:editor [ | ||
| ex:fullname "Dave Beckett"; | ||
| ex:homePage <http://purl.org/net/dajobe/> | ||
| ] .""" | ||
| g = Graph() | ||
| turtle_parser.parse(ttl, g) | ||
| assert len(g) == 4 | ||
| def test_jsonld_basic(): | ||
| import json | ||
| jsonld = """[{ | ||
| "@id": "http://example.com/id1", | ||
| "@type": ["http://example.com/t1"], | ||
| "http://example.com/term1": ["v1"], | ||
| "http://example.com/term2": [{"@value": "v2", "@type": "http://example.com/t2"}], | ||
| "http://example.com/term3": [{"@value": "v3", "@language": "en"}], | ||
| "http://example.com/term4": [4], | ||
| "http://example.com/term5": [50, 51] | ||
| }] | ||
| """ | ||
| g = Graph() | ||
| jsonld_parser.parse_json(json.loads(jsonld), g) | ||
| assert len(g) == 7 |
| import random | ||
| from pymantic.primitives import ( | ||
| BlankNode, | ||
| Dataset, | ||
| Graph, | ||
| Literal, | ||
| NamedNode, | ||
| Quad, | ||
| Triple, | ||
| to_curie, | ||
| ) | ||
| def en(s): | ||
| return Literal(s, "en") | ||
| def test_to_curie_multi_match(): | ||
| """Test that the longest match for prefix is used""" | ||
| namespaces = {"short": "aa", "long": "aaa"} | ||
| curie = to_curie("aaab", namespaces) | ||
| assert curie == "long:b" | ||
| def test_simple_add(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| assert t in g | ||
| def test_simple_remove(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| g.remove(t) | ||
| assert t not in g | ||
| def test_match_VVV_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, None, None) | ||
| assert t in matches | ||
| def test_match_sVV_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(NamedNode("http://example.com"), None, None) | ||
| assert t in matches | ||
| def test_match_sVo_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(NamedNode("http://example.com"), None, en("Never!")) | ||
| assert t in matches | ||
| def test_match_spV_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| None, | ||
| ) | ||
| assert t in matches | ||
| def test_match_Vpo_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, NamedNode("http://purl.org/dc/terms/issued"), en("Never!")) | ||
| assert t in matches | ||
| def test_match_VVo_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, None, en("Never!")) | ||
| assert t in matches | ||
| def test_match_VpV_pattern(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| en("Never!"), | ||
| ) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, NamedNode("http://purl.org/dc/terms/issued"), None) | ||
| assert t in matches | ||
| def generate_triples(n=10): | ||
| for i in range(1, n): | ||
| yield Triple( | ||
| NamedNode("http://example/" + str(random.randint(1, 1000))), | ||
| NamedNode("http://example/terms/" + str(random.randint(1, 1000))), | ||
| Literal(random.randint(1, 1000)), | ||
| ) | ||
| def test_10000_triples(): | ||
| n = 10000 | ||
| g = Graph() | ||
| for t in generate_triples(n): | ||
| g.add(t) | ||
| assert len(g) > n * 0.9 | ||
| g.match(NamedNode("http://example.com/42"), None, None) | ||
| g.match(None, NamedNode("http://example/terms/42"), None) | ||
| g.match(None, None, Literal(42)) | ||
| def test_iter_10000_triples(): | ||
| n = 10000 | ||
| g = Graph() | ||
| triples = set() | ||
| for t in generate_triples(n): | ||
| g.add(t) | ||
| triples.add(t) | ||
| assert len(g) > n * 0.9 | ||
| for t in g: | ||
| triples.remove(t) | ||
| assert len(triples) == 0 | ||
| # Dataset Tests | ||
| def test_add_quad(): | ||
| q = Quad( | ||
| NamedNode("http://example.com/graph"), | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| Literal("Never!"), | ||
| ) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| assert q in ds | ||
| def test_remove_quad(): | ||
| q = Quad( | ||
| NamedNode("http://example.com/graph"), | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| Literal("Never!"), | ||
| ) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| ds.remove(q) | ||
| assert q not in ds | ||
| def test_ds_len(): | ||
| n = 10 | ||
| ds = Dataset() | ||
| for q in generate_quads(n): | ||
| ds.add(q) | ||
| assert len(ds) == 10 | ||
| def test_match_ds_sVV_pattern(): | ||
| q = Quad( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| Literal("Never!"), | ||
| NamedNode("http://example.com/graph"), | ||
| ) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| matches = ds.match(subject=NamedNode("http://example.com")) | ||
| assert q in matches | ||
| def test_match_ds_quad_pattern(): | ||
| q = Quad( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| Literal("Never!"), | ||
| NamedNode("http://example.com/graph"), | ||
| ) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| matches = ds.match(graph="http://example.com/graph") | ||
| assert q in matches | ||
| def test_add_graph(): | ||
| t = Triple( | ||
| NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"), | ||
| Literal("Never!"), | ||
| ) | ||
| g = Graph("http://example.com/graph") | ||
| g.add(t) | ||
| ds = Dataset() | ||
| ds.add_graph(g) | ||
| assert t in ds | ||
| def generate_quads(n): | ||
| for i in range(n): | ||
| yield Quad( | ||
| NamedNode("http://example/" + str(random.randint(1, 1000))), | ||
| NamedNode("http://purl.org/dc/terms/" + str(random.randint(1, 100))), | ||
| Literal(random.randint(1, 1000)), | ||
| NamedNode("http://example/graph/" + str(random.randint(1, 1000))), | ||
| ) | ||
| def test_10000_quads(): | ||
| n = 10000 | ||
| ds = Dataset() | ||
| for q in generate_quads(n): | ||
| ds.add(q) | ||
| assert len(ds) > n * 0.9 | ||
| ds.match( | ||
| subject=NamedNode("http://example.com/42"), | ||
| graph=NamedNode("http://example/graph/42"), | ||
| ) | ||
| def test_iter_10000_quads(): | ||
| n = 10000 | ||
| ds = Dataset() | ||
| quads = set() | ||
| for q in generate_quads(n): | ||
| ds.add(q) | ||
| quads.add(q) | ||
| assert len(ds) > n * 0.9 | ||
| for quad in ds: | ||
| quads.remove(quad) | ||
| assert len(quads) == 0 | ||
| def test_interfaceName(): | ||
| assert Literal("Bob", "en").interfaceName == "Literal" | ||
| assert NamedNode().interfaceName == "NamedNode" | ||
| def test_BlankNode_id(): | ||
| b1 = BlankNode() | ||
| b2 = BlankNode() | ||
| assert b1.value != b2.value |
| import datetime | ||
| import pytest | ||
| from pymantic.primitives import Graph, Literal, NamedNode, Prefix, Triple | ||
| import pymantic.rdf | ||
| import pymantic.util | ||
| XSD = Prefix("http://www.w3.org/2001/XMLSchema#") | ||
| RDF = Prefix("http://www.w3.org/1999/02/22-rdf-syntax-ns#") | ||
| @pytest.fixture() | ||
| def reset_metaresource(): | ||
| yield | ||
| pymantic.rdf.MetaResource._classes = {} | ||
| def testCurieURI(reset_metaresource): | ||
| """Test CURIE parsing of explicit URIs.""" | ||
| test_ns = { | ||
| "http": Prefix("WRONG!"), | ||
| "urn": Prefix("WRONG!"), | ||
| } | ||
| assert pymantic.rdf.parse_curie("http://example.com", test_ns) == NamedNode( | ||
| "http://example.com" | ||
| ) | ||
| assert pymantic.rdf.parse_curie("urn:isbn:1234567890123", test_ns) == NamedNode( | ||
| "urn:isbn:1234567890123" | ||
| ) | ||
| def testCurieDefaultPrefix(reset_metaresource): | ||
| """Test CURIE parsing of CURIEs in the default Prefix.""" | ||
| test_ns = {"": Prefix("foo/"), "wrong": Prefix("WRONG!")} | ||
| assert pymantic.rdf.parse_curie("bar", test_ns) == NamedNode("foo/bar") | ||
| assert pymantic.rdf.parse_curie("[bar]", test_ns) == NamedNode("foo/bar") | ||
| assert pymantic.rdf.parse_curie("baz", test_ns) == NamedNode("foo/baz") | ||
| assert pymantic.rdf.parse_curie("[aap]", test_ns) == NamedNode("foo/aap") | ||
| def testCurieprefixes(reset_metaresource): | ||
| """Test CURIE parsing of CURIEs in non-default prefixes.""" | ||
| test_ns = { | ||
| "": Prefix("WRONG!"), | ||
| "foo": Prefix("foobly/"), | ||
| "bar": Prefix("bardle/"), | ||
| "http": Prefix("reallybadidea/"), | ||
| } | ||
| assert pymantic.rdf.parse_curie("foo:aap", test_ns) == NamedNode("foobly/aap") | ||
| assert pymantic.rdf.parse_curie("[bar:aap]", test_ns) == NamedNode("bardle/aap") | ||
| assert pymantic.rdf.parse_curie("[foo:baz]", test_ns) == NamedNode("foobly/baz") | ||
| assert pymantic.rdf.parse_curie("bar:baz", test_ns) == NamedNode("bardle/baz") | ||
| assert pymantic.rdf.parse_curie("[http://example.com]", test_ns) == NamedNode( | ||
| "reallybadidea///example.com" | ||
| ) | ||
| def testUnparseableCuries(reset_metaresource): | ||
| """Test some CURIEs that shouldn't parse.""" | ||
| test_ns = { | ||
| "foo": Prefix("WRONG!"), | ||
| } | ||
| with pytest.raises(ValueError): | ||
| pymantic.rdf.parse_curie("[bar]", test_ns) | ||
| with pytest.raises(ValueError): | ||
| pymantic.rdf.parse_curie("bar", test_ns) | ||
| with pytest.raises(ValueError): | ||
| pymantic.rdf.parse_curie("bar:baz", test_ns) | ||
| with pytest.raises(ValueError): | ||
| pymantic.rdf.parse_curie("[bar:baz]", test_ns) | ||
| def testMetaResourceNothingUseful(reset_metaresource): | ||
| """Test applying a MetaResource to a class without anything it uses.""" | ||
| class Foo(metaclass=pymantic.rdf.MetaResource): | ||
| pass | ||
| def testMetaResourceprefixes(reset_metaresource): | ||
| """Test the handling of prefixes by MetaResource.""" | ||
| class Foo(metaclass=pymantic.rdf.MetaResource): | ||
| prefixes = { | ||
| "foo": "bar", | ||
| "baz": "garply", | ||
| "meme": "lolcatz!", | ||
| } | ||
| assert Foo.prefixes == { | ||
| "foo": Prefix("bar"), | ||
| "baz": Prefix("garply"), | ||
| "meme": Prefix("lolcatz!"), | ||
| } | ||
| def testMetaResourcePrefixInheritance(reset_metaresource): | ||
| """Test the composition of Prefix dictionaries by MetaResource.""" | ||
| class Foo(metaclass=pymantic.rdf.MetaResource): | ||
| prefixes = { | ||
| "foo": "bar", | ||
| "baz": "garply", | ||
| "meme": "lolcatz!", | ||
| } | ||
| class Bar(Foo): | ||
| prefixes = { | ||
| "allyourbase": "arebelongtous!", | ||
| "bunny": "pancake", | ||
| } | ||
| assert Foo.prefixes == { | ||
| "foo": Prefix("bar"), | ||
| "baz": Prefix("garply"), | ||
| "meme": Prefix("lolcatz!"), | ||
| } | ||
| assert Bar.prefixes == { | ||
| "foo": Prefix("bar"), | ||
| "baz": Prefix("garply"), | ||
| "meme": Prefix("lolcatz!"), | ||
| "allyourbase": Prefix("arebelongtous!"), | ||
| "bunny": Prefix("pancake"), | ||
| } | ||
| def testMetaResourcePrefixInheritanceReplacement(reset_metaresource): | ||
| """Test the composition of Prefix dictionaries by MetaResource where | ||
| some prefixes on the parent get replaced.""" | ||
| class Foo(metaclass=pymantic.rdf.MetaResource): | ||
| prefixes = { | ||
| "foo": "bar", | ||
| "baz": "garply", | ||
| "meme": "lolcatz!", | ||
| } | ||
| class Bar(Foo): | ||
| prefixes = { | ||
| "allyourbase": "arebelongtous!", | ||
| "bunny": "pancake", | ||
| "foo": "notbar", | ||
| "baz": "notgarply", | ||
| } | ||
| assert Foo.prefixes == { | ||
| "foo": Prefix("bar"), | ||
| "baz": Prefix("garply"), | ||
| "meme": Prefix("lolcatz!"), | ||
| } | ||
| assert Bar.prefixes == { | ||
| "foo": Prefix("notbar"), | ||
| "baz": Prefix("notgarply"), | ||
| "meme": Prefix("lolcatz!"), | ||
| "allyourbase": Prefix("arebelongtous!"), | ||
| "bunny": Prefix("pancake"), | ||
| } | ||
| def testResourceEquality(reset_metaresource): | ||
| graph = Graph() | ||
| otherGraph = Graph() | ||
| testResource = pymantic.rdf.Resource(graph, "foo") | ||
| assert testResource == pymantic.rdf.Resource(graph, "foo") | ||
| assert testResource == NamedNode("foo") | ||
| assert testResource == "foo" | ||
| assert testResource != pymantic.rdf.Resource(graph, "bar") | ||
| assert testResource == pymantic.rdf.Resource(otherGraph, "foo") | ||
| assert testResource != NamedNode("bar") | ||
| assert testResource != "bar" | ||
| assert testResource != 42 | ||
| def testClassification(reset_metaresource): | ||
| """Test classification of a resource.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| test_subject = NamedNode("http://example.com/athing") | ||
| graph = Graph() | ||
| graph.add( | ||
| Triple( | ||
| test_subject, | ||
| Offering.resolve("rdf:type"), | ||
| Offering.resolve("gr:Offering"), | ||
| ) | ||
| ) | ||
| offering = pymantic.rdf.Resource.classify(graph, test_subject) | ||
| assert isinstance(offering, Offering) | ||
| def testMulticlassClassification(reset_metaresource): | ||
| """Test classification of a resource that matches multiple registered | ||
| classes.""" | ||
| @pymantic.rdf.register_class("foaf:Organization") | ||
| class Organization(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "foaf": "http://xmlns.com/foaf/0.1/", | ||
| } | ||
| @pymantic.rdf.register_class("foaf:Group") | ||
| class Group(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "foaf": "http://xmlns.com/foaf/0.1/", | ||
| } | ||
| test_subject1 = NamedNode("http://example.com/aorganization") | ||
| test_subject2 = NamedNode("http://example.com/agroup") | ||
| test_subject3 = NamedNode("http://example.com/aorgandgroup") | ||
| graph = Graph() | ||
| graph.add( | ||
| Triple( | ||
| test_subject1, | ||
| Organization.resolve("rdf:type"), | ||
| Organization.resolve("foaf:Organization"), | ||
| ) | ||
| ) | ||
| graph.add( | ||
| Triple(test_subject2, Group.resolve("rdf:type"), Group.resolve("foaf:Group")) | ||
| ) | ||
| graph.add( | ||
| Triple( | ||
| test_subject3, | ||
| Organization.resolve("rdf:type"), | ||
| Organization.resolve("foaf:Organization"), | ||
| ) | ||
| ) | ||
| graph.add( | ||
| Triple( | ||
| test_subject3, | ||
| Organization.resolve("rdf:type"), | ||
| Organization.resolve("foaf:Group"), | ||
| ) | ||
| ) | ||
| organization = pymantic.rdf.Resource.classify(graph, test_subject1) | ||
| group = pymantic.rdf.Resource.classify(graph, test_subject2) | ||
| both = pymantic.rdf.Resource.classify(graph, test_subject3) | ||
| assert isinstance(organization, Organization) | ||
| assert not isinstance(group, Organization) | ||
| assert not isinstance(organization, Group) | ||
| assert isinstance(group, Group) | ||
| assert isinstance(both, Organization) | ||
| assert isinstance(both, Group) | ||
| def testStr(reset_metaresource): | ||
| """Test str-y serialization of Resources.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/aorganization") | ||
| test_label = Literal("Test Label", language="en") | ||
| graph.add( | ||
| Triple(test_subject1, pymantic.rdf.Resource.resolve("rdfs:label"), test_label) | ||
| ) | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| assert r["rdfs:label"] == test_label | ||
| assert str(r) == test_label.value | ||
| def testGetSetDelPredicate(reset_metaresource): | ||
| """Test getting, setting, and deleting a multi-value predicate.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r["rdfs:example"] = set(("foo", "bar")) | ||
| example_values = set(r["rdfs:example"]) | ||
| assert Literal("foo") in example_values | ||
| assert Literal("bar") in example_values | ||
| assert len(example_values) == 2 | ||
| del r["rdfs:example"] | ||
| example_values = set(r["rdfs:example"]) | ||
| assert len(example_values) == 0 | ||
| def testGetSetDelScalarPredicate(reset_metaresource): | ||
| """Test getting, setting, and deleting a scalar predicate.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r["rdfs:label"] = "foo" | ||
| assert r["rdfs:label"] == Literal("foo", language="en") | ||
| del r["rdfs:label"] | ||
| assert r["rdfs:label"] is None | ||
| def testGetSetDelPredicateLanguage(reset_metaresource): | ||
| """Test getting, setting and deleting a multi-value predicate with an explicit language.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r["rdfs:example", "en"] = set(("baz",)) | ||
| r["rdfs:example", "fr"] = set(("foo", "bar")) | ||
| example_values = set(r["rdfs:example", "fr"]) | ||
| assert Literal("foo", language="fr") in example_values | ||
| assert Literal("bar", language="fr") in example_values | ||
| assert Literal("baz", language="en") not in example_values | ||
| assert len(example_values) == 2 | ||
| example_values = set(r["rdfs:example", "en"]) | ||
| assert Literal("foo", language="fr") not in example_values | ||
| assert Literal("bar", language="fr") not in example_values | ||
| assert Literal("baz", language="en") in example_values | ||
| assert len(example_values) == 1 | ||
| del r["rdfs:example", "fr"] | ||
| example_values = set(r["rdfs:example", "fr"]) | ||
| assert len(example_values) == 0 | ||
| example_values = set(r["rdfs:example", "en"]) | ||
| assert Literal("foo", language="fr") not in example_values | ||
| assert Literal("bar", language="fr") not in example_values | ||
| assert Literal("baz", language="en") in example_values | ||
| assert len(example_values) == 1 | ||
| def testGetSetDelScalarPredicateLanguage(reset_metaresource): | ||
| """Test getting, setting, and deleting a scalar predicate with an explicit language.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r["rdfs:label"] = "foo" | ||
| r["rdfs:label", "fr"] = "bar" | ||
| assert r["rdfs:label"] == Literal("foo", language="en") | ||
| assert r["rdfs:label", "en"] == Literal("foo", language="en") | ||
| assert r["rdfs:label", "fr"] == Literal("bar", language="fr") | ||
| del r["rdfs:label"] | ||
| assert r["rdfs:label"] is None | ||
| assert r["rdfs:label", "en"] is None | ||
| assert r["rdfs:label", "fr"] == Literal("bar", language="fr") | ||
| def testGetSetDelPredicateDatatype(reset_metaresource): | ||
| """Test getting, setting and deleting a multi-value predicate with an explicit datatype.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| now = datetime.datetime.now() | ||
| then = datetime.datetime.now() - datetime.timedelta(days=1) | ||
| number = 42 | ||
| r["rdfs:example", XSD("integer")] = set((number,)) | ||
| r["rdfs:example", XSD("dateTime")] = set( | ||
| ( | ||
| now, | ||
| then, | ||
| ) | ||
| ) | ||
| example_values = set(r["rdfs:example", XSD("dateTime")]) | ||
| assert Literal(now) in example_values | ||
| assert Literal(then) in example_values | ||
| assert Literal(number) not in example_values | ||
| assert len(example_values) == 2 | ||
| example_values = set(r["rdfs:example", XSD("integer")]) | ||
| assert Literal(now) not in example_values | ||
| assert Literal(then) not in example_values | ||
| assert Literal(number) in example_values | ||
| assert len(example_values) == 1 | ||
| del r["rdfs:example", XSD("dateTime")] | ||
| example_values = set(r["rdfs:example", XSD("dateTime")]) | ||
| assert len(example_values) == 0 | ||
| example_values = set(r["rdfs:example", XSD("integer")]) | ||
| assert Literal(now) not in example_values | ||
| assert Literal(then) not in example_values | ||
| assert Literal(number) in example_values | ||
| assert len(example_values) == 1 | ||
| def testGetSetDelScalarPredicateDatatype(reset_metaresource): | ||
| """Test getting, setting, and deleting a scalar predicate with an explicit datatype.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| now = datetime.datetime.now() | ||
| number = 42 | ||
| r["rdfs:label", XSD("integer")] = number | ||
| assert r["rdfs:label", XSD("integer")] == Literal(number, datatype=XSD("integer")) | ||
| assert r["rdfs:label", XSD("dateTime")] is None | ||
| assert r["rdfs:label"] == Literal(number, datatype=XSD("integer")) | ||
| r["rdfs:label", XSD("dateTime")] = now | ||
| assert r["rdfs:label", XSD("dateTime")] == Literal(now) | ||
| assert r["rdfs:label", XSD("integer")] is None | ||
| assert r["rdfs:label"] == Literal(now) | ||
| del r["rdfs:label", XSD("integer")] | ||
| assert r["rdfs:label", XSD("dateTime")] == Literal(now) | ||
| assert r["rdfs:label", XSD("integer")] is None | ||
| assert r["rdfs:label"] == Literal(now) | ||
| del r["rdfs:label", XSD("dateTime")] | ||
| assert r["rdfs:label"] is None | ||
| r["rdfs:label", XSD("integer")] = number | ||
| assert r["rdfs:label", XSD("integer")] == Literal(number, datatype=XSD("integer")) | ||
| assert r["rdfs:label", XSD("dateTime")] is None | ||
| assert r["rdfs:label"] == Literal(number, datatype=XSD("integer")) | ||
| del r["rdfs:label"] | ||
| assert r["rdfs:label"] is None | ||
| def testGetSetDelPredicateType(reset_metaresource): | ||
| """Test getting, setting and deleting a multi-value predicate with an explicit expected RDF Class.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/offering") | ||
| test_subject2 = NamedNode("http://example.com/aposi1") | ||
| test_subject3 = NamedNode("http://example.com/aposi2") | ||
| test_subject4 = NamedNode("http://example.com/possip1") | ||
| shared_prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| @pymantic.rdf.register_class("gr:ActualProductOrServiceInstance") | ||
| class ActualProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| @pymantic.rdf.register_class("gr:ProductOrServicesSomeInstancesPlaceholder") | ||
| class PlaceholderProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| offering = Offering.new(graph, test_subject1) | ||
| aposi1 = ActualProduct.new(graph, test_subject2) | ||
| aposi2 = ActualProduct.new(graph, test_subject3) | ||
| possip1 = PlaceholderProduct.new(graph, test_subject4) | ||
| offering["gr:includes", ActualProduct] = set( | ||
| ( | ||
| aposi1, | ||
| aposi2, | ||
| ) | ||
| ) | ||
| offering["gr:includes", PlaceholderProduct] = set((possip1,)) | ||
| example_values = set(offering["gr:includes", ActualProduct]) | ||
| assert aposi1 in example_values | ||
| assert aposi2 in example_values | ||
| assert possip1 not in example_values | ||
| assert len(example_values) == 2 | ||
| example_values = set(offering["gr:includes", PlaceholderProduct]) | ||
| assert aposi1 not in example_values | ||
| assert aposi2 not in example_values | ||
| assert possip1 in example_values | ||
| assert len(example_values) == 1 | ||
| del offering["gr:includes", ActualProduct] | ||
| example_values = set(offering["gr:includes", ActualProduct]) | ||
| assert len(example_values) == 0 | ||
| example_values = set(offering["gr:includes", PlaceholderProduct]) | ||
| assert aposi1 not in example_values | ||
| assert aposi2 not in example_values | ||
| assert possip1 in example_values | ||
| assert len(example_values) == 1 | ||
| def testGetSetDelScalarPredicateType(reset_metaresource): | ||
| """Test getting, setting, and deleting a scalar predicate with an explicit language.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/offering") | ||
| test_subject2 = NamedNode("http://example.com/aposi") | ||
| test_subject4 = NamedNode("http://example.com/possip") | ||
| shared_prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| scalars = frozenset(("gr:includes",)) | ||
| @pymantic.rdf.register_class("gr:ActualProductOrServiceInstance") | ||
| class ActualProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| @pymantic.rdf.register_class("gr:ProductOrServicesSomeInstancesPlaceholder") | ||
| class PlaceholderProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| offering = Offering.new(graph, test_subject1) | ||
| aposi1 = ActualProduct.new(graph, test_subject2) | ||
| possip1 = PlaceholderProduct.new(graph, test_subject4) | ||
| offering["gr:includes", ActualProduct] = aposi1 | ||
| assert aposi1 == offering["gr:includes", ActualProduct] | ||
| assert offering["gr:includes", PlaceholderProduct] is None | ||
| assert aposi1 == offering["gr:includes"] | ||
| offering["gr:includes", PlaceholderProduct] = possip1 | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| assert possip1 == offering["gr:includes", PlaceholderProduct] | ||
| assert possip1 == offering["gr:includes"] | ||
| del offering["gr:includes", ActualProduct] | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| assert possip1 == offering["gr:includes", PlaceholderProduct] | ||
| del offering["gr:includes", PlaceholderProduct] | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| assert offering["gr:includes", PlaceholderProduct] is None | ||
| offering["gr:includes", ActualProduct] = aposi1 | ||
| assert aposi1 == offering["gr:includes", ActualProduct] | ||
| assert offering["gr:includes", PlaceholderProduct] is None | ||
| assert aposi1 == offering["gr:includes"] | ||
| del offering["gr:includes"] | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| assert offering["gr:includes", PlaceholderProduct] is None | ||
| assert offering["gr:includes"] is None | ||
| def testSetMixedScalarPredicate(reset_metaresource): | ||
| """Test getting and setting a scalar predicate with mixed typing.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/offering") | ||
| test_subject2 = NamedNode("http://example.com/aposi") | ||
| shared_prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| scalars = frozenset(("gr:includes",)) | ||
| @pymantic.rdf.register_class("gr:ActualProductOrServiceInstance") | ||
| class ActualProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| offering = Offering.new(graph, test_subject1) | ||
| aposi1 = ActualProduct.new(graph, test_subject2) | ||
| test_en = Literal("foo", language="en") | ||
| test_fr = Literal("le foo", language="fr") | ||
| test_dt = Literal("42", datatype=XSD("integer")) | ||
| offering["gr:includes"] = aposi1 | ||
| assert offering["gr:includes"] == aposi1 | ||
| offering["gr:includes"] = test_dt | ||
| assert offering["gr:includes"] == test_dt | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| offering["gr:includes"] = test_en | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| assert offering["gr:includes", XSD("integer")] is None | ||
| assert offering["gr:includes"] == test_en | ||
| assert offering["gr:includes", "en"] == test_en | ||
| assert offering["gr:includes", "fr"] is None | ||
| offering["gr:includes"] = test_fr | ||
| assert offering["gr:includes", ActualProduct] is None | ||
| assert offering["gr:includes", XSD("integer")] is None | ||
| assert offering["gr:includes"] == test_en | ||
| assert offering["gr:includes", "en"] == test_en | ||
| assert offering["gr:includes", "fr"] == test_fr | ||
| offering["gr:includes"] = aposi1 | ||
| assert offering["gr:includes"] == aposi1 | ||
| assert offering["gr:includes", XSD("integer")] is None | ||
| assert offering["gr:includes", "en"] is None | ||
| assert offering["gr:includes", "fr"] is None | ||
| def testResourcePredicate(reset_metaresource): | ||
| """Test instantiating a class when accessing a predicate.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| @pymantic.rdf.register_class("gr:PriceSpecification") | ||
| class PriceSpecification(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| test_subject1 = NamedNode("http://example.com/offering") | ||
| test_subject2 = NamedNode("http://example.com/price") | ||
| graph = Graph() | ||
| graph.add( | ||
| Triple( | ||
| test_subject1, | ||
| Offering.resolve("rdf:type"), | ||
| Offering.resolve("gr:Offering"), | ||
| ) | ||
| ) | ||
| graph.add( | ||
| Triple( | ||
| test_subject1, | ||
| Offering.resolve("gr:hasPriceSpecification"), | ||
| test_subject2, | ||
| ) | ||
| ) | ||
| graph.add( | ||
| Triple( | ||
| test_subject2, | ||
| PriceSpecification.resolve("rdf:type"), | ||
| PriceSpecification.resolve("gr:PriceSpecification"), | ||
| ) | ||
| ) | ||
| offering = Offering(graph, test_subject1) | ||
| price_specification = PriceSpecification(graph, test_subject2) | ||
| prices = set(offering["gr:hasPriceSpecification"]) | ||
| assert len(prices) == 1 | ||
| assert price_specification in prices | ||
| def testResourcePredicateAssignment(reset_metaresource): | ||
| """Test assigning an instance of a resource to a predicate.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| @pymantic.rdf.register_class("gr:PriceSpecification") | ||
| class PriceSpecification(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| test_subject1 = NamedNode("http://example.com/offering") | ||
| test_subject2 = NamedNode("http://example.com/price") | ||
| graph = Graph() | ||
| graph.add( | ||
| Triple( | ||
| test_subject1, | ||
| Offering.resolve("rdf:type"), | ||
| Offering.resolve("gr:Offering"), | ||
| ) | ||
| ) | ||
| graph.add( | ||
| Triple( | ||
| test_subject2, | ||
| PriceSpecification.resolve("rdf:type"), | ||
| PriceSpecification.resolve("gr:PriceSpecification"), | ||
| ) | ||
| ) | ||
| offering = Offering(graph, test_subject1) | ||
| price_specification = PriceSpecification(graph, test_subject2) | ||
| before_prices = set(offering["gr:hasPriceSpecification"]) | ||
| assert len(before_prices) == 0 | ||
| offering["gr:hasPriceSpecification"] = price_specification | ||
| after_prices = set(offering["gr:hasPriceSpecification"]) | ||
| assert len(after_prices) == 1 | ||
| assert price_specification in after_prices | ||
| def testNewResource(reset_metaresource): | ||
| """Test creating a new resource.""" | ||
| graph = Graph() | ||
| @pymantic.rdf.register_class("foaf:Person") | ||
| class Person(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "foaf": "http://xmlns.com/foaf/0.1/", | ||
| } | ||
| test_subject = NamedNode("http://example.com/") | ||
| Person.new(graph, test_subject) | ||
| def testGetAllResourcesInGraph(reset_metaresource): | ||
| """Test iterating over all of the resources in a graph with a | ||
| particular RDF type.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| graph = Graph() | ||
| test_subject_base = NamedNode("http://example.com/") | ||
| for i in range(10): | ||
| graph.add( | ||
| Triple( | ||
| NamedNode(test_subject_base + str(i)), | ||
| Offering.resolve("rdf:type"), | ||
| Offering.resolve("gr:Offering"), | ||
| ) | ||
| ) | ||
| offerings = Offering.in_graph(graph) | ||
| assert len(offerings) == 10 | ||
| for i in range(10): | ||
| this_subject = NamedNode(test_subject_base + str(i)) | ||
| offering = Offering(graph, this_subject) | ||
| assert offering in offerings | ||
| def testContained(reset_metaresource): | ||
| """Test in against a multi-value predicate.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode("http://example.com/") | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r["rdfs:example"] = set(("foo", "bar")) | ||
| assert "rdfs:example" in r | ||
| assert ("rdfs:example", "en") not in r | ||
| ("rdfs:example", "fr") not in r | ||
| assert "rdfs:examplefoo" not in r | ||
| del r["rdfs:example"] | ||
| assert "rdfs:example" not in r | ||
| assert ("rdfs:example", "en") not in r | ||
| assert ("rdfs:example", "fr") not in r | ||
| assert "rdfs:examplefoo" not in r | ||
| r["rdfs:example", "fr"] = "le foo" | ||
| def testBack(reset_metaresource): | ||
| """Test following a predicate backwards.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| @pymantic.rdf.register_class("gr:PriceSpecification") | ||
| class PriceSpecification(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| graph = Graph() | ||
| offering1 = Offering.new(graph, "http://example.com/offering1") | ||
| offering2 = Offering.new(graph, "http://example.com/offering2") | ||
| Offering.new(graph, "http://example.com/offering3") | ||
| price1 = PriceSpecification.new(graph, "http://example.com/price1") | ||
| price2 = PriceSpecification.new(graph, "http://example.com/price2") | ||
| price3 = PriceSpecification.new(graph, "http://example.com/price3") | ||
| offering1["gr:hasPriceSpecification"] = set( | ||
| ( | ||
| price1, | ||
| price2, | ||
| price3, | ||
| ) | ||
| ) | ||
| offering2["gr:hasPriceSpecification"] = set( | ||
| ( | ||
| price2, | ||
| price3, | ||
| ) | ||
| ) | ||
| assert set(price1.object_of(predicate="gr:hasPriceSpecification")) == set( | ||
| (offering1,) | ||
| ) | ||
| assert set(price2.object_of(predicate="gr:hasPriceSpecification")) == set( | ||
| ( | ||
| offering1, | ||
| offering2, | ||
| ) | ||
| ) | ||
| assert set(price3.object_of(predicate="gr:hasPriceSpecification")) == set( | ||
| ( | ||
| offering1, | ||
| offering2, | ||
| ) | ||
| ) | ||
| def testGetAllValues(reset_metaresource): | ||
| """Test getting all values for a predicate.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| en = Literal("foo", language="en") | ||
| fr = Literal("bar", language="fr") | ||
| es = Literal("baz", language="es") | ||
| xsdstring = Literal("aap") | ||
| xsddecimal = Literal("9.95", datatype=XSD("decimal")) | ||
| graph = Graph() | ||
| offering = Offering.new(graph, "http://example.com/offering") | ||
| offering["gr:description"] = set( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| ) | ||
| ) | ||
| assert frozenset(offering["gr:description"]) == frozenset( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| ) | ||
| ) | ||
| assert frozenset(offering["gr:description", "en"]) == frozenset((en,)) | ||
| assert frozenset(offering["gr:description", "fr"]) == frozenset((fr,)) | ||
| assert frozenset(offering["gr:description", "es"]) == frozenset((es,)) | ||
| assert frozenset(offering["gr:description", None]) == frozenset( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| ) | ||
| ) | ||
| offering["gr:description"] = set( | ||
| ( | ||
| xsdstring, | ||
| xsddecimal, | ||
| ) | ||
| ) | ||
| assert frozenset(offering["gr:description", ""]), frozenset((xsdstring,)) | ||
| assert frozenset(offering["gr:description", XSD("string")]) == frozenset( | ||
| (xsdstring,) | ||
| ) | ||
| assert frozenset(offering["gr:description", XSD("decimal")]) == frozenset( | ||
| (xsddecimal,) | ||
| ) | ||
| assert frozenset(offering["gr:description", None]) == frozenset( | ||
| ( | ||
| xsdstring, | ||
| xsddecimal, | ||
| ) | ||
| ) | ||
| offering["gr:description"] = set( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| xsdstring, | ||
| xsddecimal, | ||
| ) | ||
| ) | ||
| assert frozenset(offering["gr:description"]) == frozenset( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| xsdstring, | ||
| xsddecimal, | ||
| ) | ||
| ) | ||
| assert frozenset(offering["gr:description", "en"]) == frozenset((en,)) | ||
| assert frozenset(offering["gr:description", "fr"]) == frozenset((fr,)) | ||
| assert frozenset(offering["gr:description", "es"]) == frozenset((es,)) | ||
| assert frozenset(offering["gr:description", ""]) == frozenset((xsdstring,)) | ||
| assert frozenset(offering["gr:description", XSD("string")]) == frozenset( | ||
| (xsdstring,) | ||
| ) | ||
| assert frozenset(offering["gr:description", XSD("decimal")]) == frozenset( | ||
| (xsddecimal,) | ||
| ) | ||
| assert frozenset(offering["gr:description", None]) == frozenset( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| xsdstring, | ||
| xsddecimal, | ||
| ) | ||
| ) | ||
| def testGetAllValuesScalar(reset_metaresource): | ||
| """Test getting all values for a predicate.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| scalars = frozenset(("gr:description",)) | ||
| en = Literal("foo", language="en") | ||
| fr = Literal("bar", language="fr") | ||
| es = Literal("baz", language="es") | ||
| graph = Graph() | ||
| offering = Offering.new(graph, "http://example.com/offering") | ||
| offering["gr:description"] = en | ||
| offering["gr:description"] = fr | ||
| offering["gr:description"] = es | ||
| assert offering["gr:description"] == en | ||
| assert offering["gr:description", "en"] == en | ||
| assert offering["gr:description", "fr"] == fr | ||
| assert offering["gr:description", "es"] == es | ||
| assert frozenset(offering["gr:description", None]) == frozenset( | ||
| ( | ||
| en, | ||
| fr, | ||
| es, | ||
| ) | ||
| ) | ||
| def testErase(reset_metaresource): | ||
| """Test erasing an object from the graph.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| scalars = frozenset(("gr:name",)) | ||
| graph = Graph() | ||
| offering1 = Offering.new(graph, "http://example.com/offering1") | ||
| offering2 = Offering.new(graph, "http://example.com/offering2") | ||
| offering1["gr:name"] = "Foo" | ||
| offering1["gr:description"] = set( | ||
| ( | ||
| "Baz", | ||
| "Garply", | ||
| ) | ||
| ) | ||
| offering2["gr:name"] = "Bar" | ||
| offering2["gr:description"] = set( | ||
| ( | ||
| "Aap", | ||
| "Mies", | ||
| ) | ||
| ) | ||
| assert offering1.is_a() | ||
| assert offering2.is_a() | ||
| offering1.erase() | ||
| assert not offering1.is_a() | ||
| assert offering2.is_a() | ||
| assert not offering1["gr:name"] | ||
| assert not frozenset(offering1["gr:description"]) | ||
| assert offering2["gr:name"] == Literal("Bar", language="en") | ||
| def testUnboundClass(reset_metaresource): | ||
| """Test classifying objects with one or more unbound classes.""" | ||
| @pymantic.rdf.register_class("gr:Offering") | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| "gr": "http://purl.org/goodrelations/", | ||
| } | ||
| graph = Graph() | ||
| funky_class = NamedNode("http://example.com/AFunkyClass") | ||
| funky_subject = NamedNode("http://example.com/aFunkyResource") | ||
| offering1 = Offering.new(graph, "http://example.com/offering1") | ||
| graph.add(Triple(offering1.subject, RDF("type"), funky_class)) | ||
| assert isinstance( | ||
| pymantic.rdf.Resource.classify(graph, offering1.subject), Offering | ||
| ) | ||
| graph.add(Triple(funky_subject, RDF("type"), funky_class)) | ||
| assert isinstance( | ||
| pymantic.rdf.Resource.classify(graph, funky_subject), | ||
| pymantic.rdf.Resource, | ||
| ) |
| from io import StringIO | ||
| import pytest | ||
| from pymantic.parsers import ntriples_parser | ||
| from pymantic.primitives import Graph, NamedNode, Triple | ||
| from pymantic.serializers import serialize_ntriples | ||
| def test_parse_ntriples_named_nodes(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> <http://example.com/objects/2> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> <http://example.com/objects/1> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| f = StringIO() | ||
| serialize_ntriples(g, f) | ||
| f.seek(0) | ||
| g2 = Graph() | ||
| ntriples_parser.parse(f, g2) | ||
| assert len(g) == 2 | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/1"), | ||
| NamedNode("http://example.com/predicates/1"), | ||
| NamedNode("http://example.com/objects/2"), | ||
| ) | ||
| in g2 | ||
| ) | ||
| assert ( | ||
| Triple( | ||
| NamedNode("http://example.com/objects/2"), | ||
| NamedNode("http://example.com/predicates/2"), | ||
| NamedNode("http://example.com/objects/1"), | ||
| ) | ||
| in g2 | ||
| ) | ||
| @pytest.fixture() | ||
| def turtle_repr(): | ||
| from pymantic.serializers import turtle_repr | ||
| return turtle_repr | ||
| @pytest.fixture() | ||
| def primitives(): | ||
| import pymantic.primitives | ||
| return pymantic.primitives | ||
| @pytest.fixture() | ||
| def profile(primitives): | ||
| return primitives.Profile() | ||
| def test_integer(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal(value="42", datatype=profile.resolve("xsd:integer")) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == "42" | ||
| def test_decimal(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal(value="4.2", datatype=profile.resolve("xsd:decimal")) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == "4.2" | ||
| def test_double(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal(value="4.2e1", datatype=profile.resolve("xsd:double")) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == "4.2e1" | ||
| def test_boolean(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal(value="true", datatype=profile.resolve("xsd:boolean")) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == "true" | ||
| def test_bare_string(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal(value="Foo", datatype=profile.resolve("xsd:string")) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == '"Foo"' | ||
| def test_language_string(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal(value="Foo", language="en") | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == '"Foo"@en' | ||
| def test_random_datatype_bare_url(primitives, profile, turtle_repr): | ||
| lit = primitives.Literal( | ||
| value="Foo", datatype=primitives.NamedNode("http://example.com/garply") | ||
| ) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == '"Foo"^<http://example.com/garply>' | ||
| def test_random_datatype_prefixed(primitives, profile, turtle_repr): | ||
| profile.setPrefix("ex", primitives.NamedNode("http://example.com/")) | ||
| lit = primitives.Literal( | ||
| value="Foo", datatype=primitives.NamedNode("http://example.com/garply") | ||
| ) | ||
| name = turtle_repr(node=lit, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == '"Foo"^ex:garply' | ||
| def test_named_node_bare(primitives, profile, turtle_repr): | ||
| node = primitives.NamedNode("http://example.com/foo") | ||
| name = turtle_repr(node=node, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == "<http://example.com/foo>" | ||
| def test_named_node_prefixed(primitives, profile, turtle_repr): | ||
| profile.setPrefix("ex", primitives.NamedNode("http://example.com/")) | ||
| node = primitives.NamedNode("http://example.com/foo") | ||
| name = turtle_repr(node=node, profile=profile, name_map=None, bnode_name_maker=None) | ||
| assert name == "ex:foo" | ||
| def test_named_node_with_hash_base(primitives, profile, turtle_repr): | ||
| node = primitives.NamedNode("https://example.com/foo#bar") | ||
| name = turtle_repr( | ||
| node=node, | ||
| profile=profile, | ||
| name_map=None, | ||
| bnode_name_maker=None, | ||
| base="https://example.com/foo#", | ||
| ) | ||
| assert name == "<#bar>" | ||
| def test_named_node_with_path_base(primitives, profile, turtle_repr): | ||
| node = primitives.NamedNode("https://example.com/foo") | ||
| name = turtle_repr( | ||
| node=node, | ||
| profile=profile, | ||
| name_map=None, | ||
| bnode_name_maker=None, | ||
| base="https://example.com/", | ||
| ) | ||
| assert name == "<foo>" | ||
| def test_named_node_with_multi_path_base(primitives, profile, turtle_repr): | ||
| node = primitives.NamedNode("https://example.com/foo/bar") | ||
| name = turtle_repr( | ||
| node=node, | ||
| profile=profile, | ||
| name_map=None, | ||
| bnode_name_maker=None, | ||
| base="https://example.com/", | ||
| ) | ||
| assert name == "<foo/bar>" | ||
| @pytest.fixture() | ||
| def turtle_parser(): | ||
| from pymantic.parsers import turtle_parser | ||
| return turtle_parser | ||
| @pytest.fixture() | ||
| def serialize_turtle(): | ||
| from pymantic.serializers import serialize_turtle | ||
| return serialize_turtle | ||
| def testSimpleSerialization(primitives, profile, turtle_parser, serialize_turtle): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" . | ||
| example:bar dc:title "Bar" . | ||
| example:baz dc:subject example:foo .""" | ||
| graph = turtle_parser.parse(basic_turtle) | ||
| f = StringIO() | ||
| profile.setPrefix("ex", primitives.NamedNode("http://example.com/")) | ||
| profile.setPrefix("dc", primitives.NamedNode("http://purl.org/dc/terms/")) | ||
| serialize_turtle(graph=graph, f=f, profile=profile) | ||
| f.seek(0) | ||
| turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| assert ( | ||
| f.read().strip() | ||
| == """@prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:bar dc:title "Bar" ; | ||
| . | ||
| ex:baz dc:subject ex:foo ; | ||
| . | ||
| ex:foo dc:title "Foo" ; | ||
| . | ||
| """.strip() | ||
| ) | ||
| def testBaseSerialization(primitives, profile, turtle_parser, serialize_turtle): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" . | ||
| example:bar dc:title "Bar" . | ||
| example:baz dc:subject example:foo .""" | ||
| graph = turtle_parser.parse(basic_turtle) | ||
| f = StringIO() | ||
| profile.setPrefix("dc", primitives.NamedNode("http://purl.org/dc/terms/")) | ||
| serialize_turtle( | ||
| graph=graph, | ||
| f=f, | ||
| profile=profile, | ||
| base="http://example.com/", | ||
| ) | ||
| f.seek(0) | ||
| turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| assert ( | ||
| f.read().strip() | ||
| == """@base <http://example.com/> . | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| <bar> dc:title "Bar" ; | ||
| . | ||
| <baz> dc:subject <foo> ; | ||
| . | ||
| <foo> dc:title "Foo" ; | ||
| . | ||
| """.strip() | ||
| ) | ||
| def testBaseAndPrefixSerialization( | ||
| primitives, profile, turtle_parser, serialize_turtle | ||
| ): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" . | ||
| example:bar dc:title "Bar" . | ||
| example:baz dc:subject example:foo .""" | ||
| graph = turtle_parser.parse(basic_turtle) | ||
| f = StringIO() | ||
| profile.setPrefix("ex", primitives.NamedNode("http://example.com/")) | ||
| profile.setPrefix("dc", primitives.NamedNode("http://purl.org/dc/terms/")) | ||
| serialize_turtle( | ||
| graph=graph, | ||
| f=f, | ||
| profile=profile, | ||
| base="http://example.com/", | ||
| ) | ||
| f.seek(0) | ||
| turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| assert ( | ||
| f.read().strip() | ||
| == """@base <http://example.com/> . | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:bar dc:title "Bar" ; | ||
| . | ||
| ex:baz dc:subject ex:foo ; | ||
| . | ||
| ex:foo dc:title "Foo" ; | ||
| . | ||
| """.strip() | ||
| ) | ||
| def testMultiplePredicates(primitives, profile, turtle_parser, serialize_turtle): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" ; | ||
| dc:author "Bar" ; | ||
| dc:subject example:yesfootoo . | ||
| example:garply dc:title "Garply" ; | ||
| dc:author "Baz" ; | ||
| dc:subject example:thegarply .""" | ||
| graph = turtle_parser.parse(basic_turtle) | ||
| f = StringIO() | ||
| profile.setPrefix("ex", primitives.NamedNode("http://example.com/")) | ||
| profile.setPrefix("dc", primitives.NamedNode("http://purl.org/dc/terms/")) | ||
| serialize_turtle(graph=graph, f=f, profile=profile) | ||
| f.seek(0) | ||
| turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| assert ( | ||
| f.read().strip() | ||
| == """ | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:foo dc:author "Bar" ; | ||
| dc:subject ex:yesfootoo ; | ||
| dc:title "Foo" ; | ||
| . | ||
| ex:garply dc:author "Baz" ; | ||
| dc:subject ex:thegarply ; | ||
| dc:title "Garply" ; | ||
| .""".strip() | ||
| ) | ||
| def testListSerialization(primitives, profile, turtle_parser, serialize_turtle): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:author ("Foo" "Bar" "Baz") .""" | ||
| graph = turtle_parser.parse(basic_turtle) | ||
| f = StringIO() | ||
| profile.setPrefix("ex", primitives.NamedNode("http://example.com/")) | ||
| profile.setPrefix("dc", primitives.NamedNode("http://purl.org/dc/terms/")) | ||
| serialize_turtle(graph=graph, f=f, profile=profile) | ||
| f.seek(0) | ||
| turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| assert ( | ||
| f.read().strip() | ||
| == """ | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:foo dc:author ("Foo" "Bar" "Baz") ; | ||
| .""".strip() | ||
| ) |
| from betamax import Betamax | ||
| import os.path | ||
| import pytest | ||
| from pymantic.sparql import SPARQLQueryException, SPARQLServer | ||
| with Betamax.configure() as config: | ||
| config.cassette_library_dir = os.path.join( | ||
| os.path.dirname(__file__), | ||
| "playbacks", | ||
| ) | ||
| def testMockSPARQL(): | ||
| """Test a SPARQL query against a mocked-up endpoint.""" | ||
| test_query = """PREFIX dc: <http://purl.org/dc/terms/> | ||
| SELECT ?product ?title WHERE { ?product dc:title ?title } LIMIT 10""" | ||
| sparql = SPARQLServer( | ||
| "http://localhost/tenuki/sparql", | ||
| post_queries=True, | ||
| ) | ||
| with Betamax(sparql.s).use_cassette("mock_sparql", record="none"): | ||
| results = sparql.query(test_query) | ||
| assert results["results"]["bindings"][0]["product"]["value"] == "test_product" | ||
| assert results["results"]["bindings"][0]["title"]["value"] == "Test Title" | ||
| def testMockSPARQLError(): | ||
| """Test a SPARQL query against a mocked-up endpoint.""" | ||
| test_query = """PREFIX dc: <http://purl.org/dc/terms/> | ||
| SELECT ?product ?title WHERE { ?product dc:title ?title } LIMIT 10""" | ||
| sparql = SPARQLServer( | ||
| "http://localhost/tenuki/sparql", | ||
| post_queries=True, | ||
| ) | ||
| with Betamax(sparql.s).use_cassette( | ||
| "mock_sparql_error", | ||
| record="none", | ||
| ), pytest.raises(SPARQLQueryException): | ||
| sparql.query(test_query) |
| import os.path | ||
| import pytest | ||
| from urllib.parse import urljoin | ||
| from pymantic.parsers import ntriples_parser, turtle_parser | ||
| import pymantic.rdf as rdf | ||
| turtle_tests_url = "http://www.w3.org/2013/TurtleTests/" | ||
| prefixes = { | ||
| "mf": "http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#", | ||
| "qt": "http://www.w3.org/2001/sw/DataAccess/tests/test-query#", | ||
| "rdft": "http://www.w3.org/ns/rdftest#", | ||
| } | ||
| def isomorph_triple(triple): | ||
| from pymantic.primitives import BlankNode, Literal, NamedNode | ||
| if isinstance(triple.subject, BlankNode): | ||
| triple = triple._replace(subject=None) | ||
| if isinstance(triple.object, BlankNode): | ||
| triple = triple._replace(object=None) | ||
| if isinstance(triple.object, Literal) and triple.object.datatype is None: | ||
| triple = triple._replace( | ||
| object=triple.object._replace( | ||
| datatype=NamedNode("http://www.w3.org/2001/XMLSchema#string") | ||
| ) | ||
| ) | ||
| return triple | ||
| def isomorph(graph): | ||
| return {isomorph_triple(t) for t in graph._triples} | ||
| @rdf.register_class("mf:Manifest") | ||
| class Manifest(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(("rdfs:comment", "mf:entries")) | ||
| @rdf.register_class("rdft:TestTurtleEval") | ||
| class TurtleEvalTest(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(("mf:name", "rdfs:comment", "mf:action", "mf:result")) | ||
| def execute(self): | ||
| with open(str(self["mf:action"]), "rb") as f: | ||
| in_data = f.read() | ||
| with open(str(self["mf:result"]), "rb") as f: | ||
| compare_data = f.read() | ||
| base = urljoin(turtle_tests_url, os.path.basename(str(self["mf:action"]))) | ||
| test_graph = turtle_parser.parse(in_data, base=base) | ||
| compare_graph = ntriples_parser.parse_string(compare_data) | ||
| assert isomorph(test_graph) == isomorph(compare_graph), self[ | ||
| "rdfs:comment" | ||
| ].value | ||
| @rdf.register_class("rdft:TestTurtlePositiveSyntax") | ||
| class TurtlePositiveSyntaxTest(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(("mf:name", "rdfs:comment", "mf:action")) | ||
| def execute(self): | ||
| with open(str(self["mf:action"]), "rb") as f: | ||
| in_data = f.read() | ||
| base = urljoin(turtle_tests_url, os.path.basename(str(self["mf:action"]))) | ||
| turtle_parser.parse(in_data, base=base) | ||
| @rdf.register_class("rdft:TestTurtleNegativeSyntax") | ||
| class TurtleNegativeSyntaxTest(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(("mf:name", "rdfs:comment", "mf:action")) | ||
| def execute(self): | ||
| with open(str(self["mf:action"]), "rb") as f: | ||
| in_data = f.read() | ||
| base = urljoin(turtle_tests_url, os.path.basename(str(self["mf:action"]))) | ||
| with pytest.raises(Exception): | ||
| turtle_parser.parse(in_data, base=base) | ||
| @rdf.register_class("rdft:TestTurtleNegativeEval") | ||
| class TurtleNegativeEvalTest(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(("mf:name", "rdfs:comment", "mf:action")) | ||
| def execute(self): | ||
| with open(str(self["mf:action"]), "rb") as f: | ||
| in_data = f.read() | ||
| base = urljoin(turtle_tests_url, os.path.basename(str(self["mf:action"]))) | ||
| with pytest.raises(Exception): | ||
| turtle_parser.parse(in_data, base=base) | ||
| base = os.path.join(os.path.dirname(__file__), "TurtleTests/") | ||
| manifest_name = os.path.join(base, "manifest.ttl") | ||
| with open(manifest_name, "rb") as f: | ||
| manifest_turtle = f.read() | ||
| manifest_graph = turtle_parser.parse(manifest_turtle, base=base) | ||
| manifest = Manifest(manifest_graph, base) | ||
| turtle_test_cases = { | ||
| test_case["mf:name"].value: test_case | ||
| for test_case in manifest["mf:entries"].as_(rdf.List) | ||
| } | ||
| @pytest.mark.parametrize(["turtle_test_case_name"], zip(turtle_test_cases.keys())) | ||
| def test_turtle(turtle_test_case_name): | ||
| turtle_test_cases[turtle_test_case_name].execute() |
| from pymantic.util import normalize_iri, quote_normalized_iri | ||
| def test_normalize_iri_no_escapes(): | ||
| uri = "http://example.com/foo/bar?garply=aap&maz=bies" | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == "http://example.com/foo/bar?garply=aap&maz=bies" | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri | ||
| def test_normalize_iri_escaped_slash(): | ||
| uri = "http://example.com/foo%2Fbar?garply=aap&maz=bies" | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == "http://example.com/foo%2Fbar?garply=aap&maz=bies" | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri | ||
| def test_normalize_iri_escaped_ampersand(): | ||
| uri = "http://example.com/foo/bar?garply=aap%26yak&maz=bies" | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == "http://example.com/foo/bar?garply=aap%26yak&maz=bies" | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri | ||
| def test_normalize_iri_escaped_international(): | ||
| uri = "http://example.com/foo/bar?garply=aap&maz=bi%C3%89s" | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == "http://example.com/foo/bar?garply=aap&maz=bi\u00C9s" | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri |
+61
-61
| Metadata-Version: 2.1 | ||
| Name: pymantic | ||
| Version: 0.3.0 | ||
| Version: 1.0.0 | ||
| Summary: Semantic Web and RDF library for Python | ||
@@ -9,59 +9,4 @@ Home-page: https://github.com/norcalrdf/pymantic/ | ||
| License: BSD | ||
| Description: ======== | ||
| Pymantic | ||
| ======== | ||
| --------------------------------------- | ||
| Semantic Web and RDF library for Python | ||
| --------------------------------------- | ||
| Quick Start | ||
| =========== | ||
| :: | ||
| >>> from pymantic.rdf import * | ||
| >>> from pymantic.parsers import turtle_parser | ||
| >>> import requests | ||
| >>> Resource.prefixes['foaf'] = Prefix('http://xmlns.com/foaf/0.1/') | ||
| >>> graph = turtle_parser.parse(requests.get('https://raw.github.com/norcalrdf/pymantic/master/examples/foaf-bond.ttl').text) | ||
| >>> bond_james = Resource(graph, 'http://example.org/stuff/Bond') | ||
| >>> print("%s knows:" % (bond_james.get_scalar('foaf:name'),)) | ||
| >>> for person in bond_james['foaf:knows']: | ||
| print(person.get_scalar('foaf:name')) | ||
| Requirements | ||
| ============ | ||
| ``pymantic`` requires Python 3.6 or higher. | ||
| ``lark`` is used for the Turtle and NTriples parser. | ||
| The ``requests`` library is used for HTTP requests and the SPARQL client. | ||
| ``lxml`` and ``rdflib`` are required by the SPARQL client as well. | ||
| Install | ||
| ======= | ||
| :: | ||
| $ pip install pymantic | ||
| This will install ``pymantic`` and all its dependencies. | ||
| Documentation | ||
| ============= | ||
| Generating a local copy of the documentation requires Sphinx: | ||
| :: | ||
| $ pip install Sphinx | ||
| Keywords: RDF N3 Turtle Semantics Web3.0 | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 3 - Alpha | ||
| Keywords: RDF N3 Turtle Semantics | ||
| Classifier: Development Status :: 5 - Production/Stable | ||
| Classifier: Intended Audience :: Developers | ||
@@ -72,6 +17,61 @@ Classifier: License :: OSI Approved :: BSD License | ||
| Classifier: Topic :: Text Processing :: Markup | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Classifier: Programming Language :: Python :: 3.10 | ||
| Classifier: Programming Language :: Python :: 3.11 | ||
| Description-Content-Type: text/x-rst | ||
| Provides-Extra: testing | ||
| License-File: LICENSE | ||
| ======== | ||
| Pymantic | ||
| ======== | ||
| --------------------------------------- | ||
| Semantic Web and RDF library for Python | ||
| --------------------------------------- | ||
| Quick Start | ||
| =========== | ||
| :: | ||
| >>> from pymantic.rdf import * | ||
| >>> from pymantic.parsers import turtle_parser | ||
| >>> import requests | ||
| >>> Resource.prefixes['foaf'] = Prefix('http://xmlns.com/foaf/0.1/') | ||
| >>> graph = turtle_parser.parse(requests.get('https://raw.github.com/norcalrdf/pymantic/master/examples/foaf-bond.ttl').text) | ||
| >>> bond_james = Resource(graph, 'http://example.org/stuff/Bond') | ||
| >>> print("%s knows:" % (bond_james.get_scalar('foaf:name'),)) | ||
| >>> for person in bond_james['foaf:knows']: | ||
| print(person.get_scalar('foaf:name')) | ||
| Requirements | ||
| ============ | ||
| ``pymantic`` requires Python 3.9 or higher. | ||
| ``lark`` is used for the Turtle and NTriples parser. | ||
| The ``requests`` library is used for HTTP requests and the SPARQL client. | ||
| ``lxml`` and ``rdflib`` are required by the SPARQL client as well. | ||
| Install | ||
| ======= | ||
| :: | ||
| $ pip install pymantic | ||
| This will install ``pymantic`` and all its dependencies. | ||
| Documentation | ||
| ============= | ||
| Generating a local copy of the documentation requires Sphinx: | ||
| :: | ||
| $ pip install Sphinx | ||
+1
-1
@@ -28,3 +28,3 @@ ======== | ||
| ``pymantic`` requires Python 3.6 or higher. | ||
| ``pymantic`` requires Python 3.9 or higher. | ||
| ``lark`` is used for the Turtle and NTriples parser. | ||
@@ -31,0 +31,0 @@ The ``requests`` library is used for HTTP requests and the SPARQL client. |
+48
-1
@@ -0,5 +1,52 @@ | ||
| [metadata] | ||
| name = pymantic | ||
| version = attr: pymantic.version | ||
| author = Gavin Carothers, Nick Pilon | ||
| author_email = gavin@carothers.name, npilon@gmail.com | ||
| description = Semantic Web and RDF library for Python | ||
| long_description = file: README.rst | ||
| long_description_content_type = text/x-rst | ||
| keywords = RDF N3 Turtle Semantics | ||
| url = https://github.com/norcalrdf/pymantic/ | ||
| license = BSD | ||
| classifiers = | ||
| Development Status :: 5 - Production/Stable | ||
| Intended Audience :: Developers | ||
| License :: OSI Approved :: BSD License | ||
| Topic :: Internet :: WWW/HTTP | ||
| Topic :: Scientific/Engineering :: Information Analysis | ||
| Topic :: Text Processing :: Markup | ||
| Programming Language :: Python :: 3.9 | ||
| Programming Language :: Python :: 3.10 | ||
| Programming Language :: Python :: 3.11 | ||
| [options] | ||
| package_dir = | ||
| =src | ||
| zip_safe = True | ||
| include_package_data = True | ||
| install_requires = | ||
| requests | ||
| lxml | ||
| pytz | ||
| rdflib | ||
| lark>=1.1.0,<1.2.0 | ||
| pyld | ||
| [options.packages.find] | ||
| where = src | ||
| [options.extras_require] | ||
| testing = | ||
| pytest | ||
| coverage | ||
| betamax | ||
| [flake8] | ||
| enable-extensions = G | ||
| ignore = E203,E501,W503 | ||
| [egg_info] | ||
| tag_svn_revision = true | ||
| tag_build = | ||
| tag_date = 0 | ||
| # -*- Entry points: -*- | ||
Sorry, the diff of this file is not supported yet
| Metadata-Version: 2.1 | ||
| Name: pymantic | ||
| Version: 0.3.0 | ||
| Summary: Semantic Web and RDF library for Python | ||
| Home-page: https://github.com/norcalrdf/pymantic/ | ||
| Author: Gavin Carothers, Nick Pilon | ||
| Author-email: gavin@carothers.name, npilon@gmail.com | ||
| License: BSD | ||
| Description: ======== | ||
| Pymantic | ||
| ======== | ||
| --------------------------------------- | ||
| Semantic Web and RDF library for Python | ||
| --------------------------------------- | ||
| Quick Start | ||
| =========== | ||
| :: | ||
| >>> from pymantic.rdf import * | ||
| >>> from pymantic.parsers import turtle_parser | ||
| >>> import requests | ||
| >>> Resource.prefixes['foaf'] = Prefix('http://xmlns.com/foaf/0.1/') | ||
| >>> graph = turtle_parser.parse(requests.get('https://raw.github.com/norcalrdf/pymantic/master/examples/foaf-bond.ttl').text) | ||
| >>> bond_james = Resource(graph, 'http://example.org/stuff/Bond') | ||
| >>> print("%s knows:" % (bond_james.get_scalar('foaf:name'),)) | ||
| >>> for person in bond_james['foaf:knows']: | ||
| print(person.get_scalar('foaf:name')) | ||
| Requirements | ||
| ============ | ||
| ``pymantic`` requires Python 3.6 or higher. | ||
| ``lark`` is used for the Turtle and NTriples parser. | ||
| The ``requests`` library is used for HTTP requests and the SPARQL client. | ||
| ``lxml`` and ``rdflib`` are required by the SPARQL client as well. | ||
| Install | ||
| ======= | ||
| :: | ||
| $ pip install pymantic | ||
| This will install ``pymantic`` and all its dependencies. | ||
| Documentation | ||
| ============= | ||
| Generating a local copy of the documentation requires Sphinx: | ||
| :: | ||
| $ pip install Sphinx | ||
| Keywords: RDF N3 Turtle Semantics Web3.0 | ||
| Platform: UNKNOWN | ||
| Classifier: Development Status :: 3 - Alpha | ||
| Classifier: Intended Audience :: Developers | ||
| Classifier: License :: OSI Approved :: BSD License | ||
| Classifier: Topic :: Internet :: WWW/HTTP | ||
| Classifier: Topic :: Scientific/Engineering :: Information Analysis | ||
| Classifier: Topic :: Text Processing :: Markup | ||
| Classifier: Programming Language :: Python :: 3.6 | ||
| Classifier: Programming Language :: Python :: 3.7 | ||
| Classifier: Programming Language :: Python :: 3.8 | ||
| Classifier: Programming Language :: Python :: 3.9 | ||
| Provides-Extra: testing |
| requests | ||
| lxml | ||
| pytz | ||
| rdflib | ||
| lark-parser<0.12.0,>=0.11.1 | ||
| pyld | ||
| [testing] | ||
| betamax | ||
| nose | ||
| coverage |
| README.rst | ||
| setup.cfg | ||
| setup.py | ||
| pymantic/__init__.py | ||
| pymantic/change_tracking.py | ||
| pymantic/compat.py | ||
| pymantic/primitives.py | ||
| pymantic/rdf.py | ||
| pymantic/serializers.py | ||
| pymantic/sparql.py | ||
| pymantic/uri_schemes.py | ||
| pymantic/util.py | ||
| pymantic.egg-info/PKG-INFO | ||
| pymantic.egg-info/SOURCES.txt | ||
| pymantic.egg-info/dependency_links.txt | ||
| pymantic.egg-info/entry_points.txt | ||
| pymantic.egg-info/not-zip-safe | ||
| pymantic.egg-info/requires.txt | ||
| pymantic.egg-info/top_level.txt | ||
| pymantic/parsers/__init__.py | ||
| pymantic/parsers/base.py | ||
| pymantic/parsers/jsonld.py | ||
| pymantic/parsers/rdfxml.py | ||
| pymantic/parsers/lark/__init__.py | ||
| pymantic/parsers/lark/base.py | ||
| pymantic/parsers/lark/nquads.py | ||
| pymantic/parsers/lark/ntriples.py | ||
| pymantic/parsers/lark/turtle.py | ||
| pymantic/scripts/__init__.py | ||
| pymantic/scripts/bnf2html | ||
| pymantic/scripts/named_graph_to_nquads | ||
| pymantic/tests/__init__.py | ||
| pymantic/tests/test_RDF.py | ||
| pymantic/tests/test_SPARQL.py | ||
| pymantic/tests/test_parsers.py | ||
| pymantic/tests/test_primitives.py | ||
| pymantic/tests/test_serializers.py | ||
| pymantic/tests/test_turtle.py | ||
| pymantic/tests/test_util.py | ||
| pymantic/vocab/__init__.py | ||
| pymantic/vocab/skos.py |
| pymantic |
| # | ||
| version = '0.3.0' | ||
| release = version |
| from primitives import Graph, BlankNode, Triple | ||
| from rdf import Resource, register_class | ||
| class ChangeTrackingGraph(Graph): | ||
| def __init__(self, graph_uri=None): | ||
| super(Graph, self).__init__() | ||
| self._added = set() | ||
| self._removed = set() | ||
| def add(self, triple): | ||
| if not self.contains(triple): | ||
| super(Graph, self).add(triple) | ||
| self._added.add(triple) | ||
| def remove(self, triple): | ||
| if self.contains(triple): | ||
| super(Graph, self).remove(triple) | ||
| self._removed.add(triple) | ||
| def changes(self, cls=Changes): | ||
| return cls(added=frozenset(self._added), | ||
| removed=frozenset(self._removed)) | ||
| class ChangeSet(object): | ||
| def __init__(self, added, removed): | ||
| self.added = added | ||
| self.removed = removed | ||
| CHANGESET_NS = "http://purl.org/vocab/changeset/schema#" | ||
| NS_DICT = dict(cs=CHANGESET_NS) | ||
| class CSR(Resource): | ||
| namespaces = NS_DICT | ||
| @register_class("cs:ChangeSet") | ||
| class CS(CSR): | ||
| scalars = ["cs:subjectOfChange"] | ||
| @register_class("rdf:Statement") | ||
| class Statement(CSR): | ||
| scalars = ["rdf:subject", "rdf:predicate", "rdf:object"] | ||
| @classmethod | ||
| def from_triple(cls, graph, triple): | ||
| statement = Statement.new(graph) | ||
| statement["rdf:subject"] = triple.subject | ||
| statement["rdf:predicate"] = triple.predicate | ||
| statement["rdf:object"] = triple.object | ||
| return statement | ||
| class ChangeSetGraph(ChangeSet): | ||
| def as_resource(self): | ||
| change_graph = Graph() | ||
| cs = CS.new(change_graph) | ||
| addition_statements = set() | ||
| for triple in self.added: | ||
| addition_statements.add(Statement.from_triple(change_graph, triple)) | ||
| cs["cs:addition"] = addition_statements | ||
| removal_statements = set() | ||
| for triple in self.removed: | ||
| removal_statements.add(Statement.from_triple(change_graph, triple)) | ||
| cs["cs:removal"] = removal_statements | ||
| return cs |
| # Copyright (c) 2010-2018 Benjamin Peterson | ||
| # | ||
| # Permission is hereby granted, free of charge, to any person obtaining a copy | ||
| # of this software and associated documentation files (the "Software"), to deal | ||
| # in the Software without restriction, including without limitation the rights | ||
| # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
| # copies of the Software, and to permit persons to whom the Software is | ||
| # furnished to do so, subject to the following conditions: | ||
| # | ||
| # The above copyright notice and this permission notice shall be included in all | ||
| # copies or substantial portions of the Software. | ||
| # | ||
| # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
| # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
| # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
| # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
| # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
| # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
| # SOFTWARE. | ||
| """Utilities for writing code that runs on Python 2 and 3""" | ||
| from __future__ import absolute_import | ||
| import functools | ||
| import itertools | ||
| import operator | ||
| import sys | ||
| import types | ||
| __author__ = "Benjamin Peterson <benjamin@python.org>" | ||
| __version__ = "1.11.0" | ||
| # Useful for very coarse version differentiation. | ||
| PY2 = sys.version_info[0] == 2 | ||
| PY3 = sys.version_info[0] == 3 | ||
| PY34 = sys.version_info[0:2] >= (3, 4) | ||
| if PY3: | ||
| string_types = str, | ||
| integer_types = int, | ||
| class_types = type, | ||
| text_type = str | ||
| binary_type = bytes | ||
| MAXSIZE = sys.maxsize | ||
| else: | ||
| string_types = basestring, | ||
| integer_types = (int, long) | ||
| class_types = (type, types.ClassType) | ||
| text_type = unicode | ||
| binary_type = str | ||
| if sys.platform.startswith("java"): | ||
| # Jython always uses 32 bits. | ||
| MAXSIZE = int((1 << 31) - 1) | ||
| else: | ||
| # It's possible to have sizeof(long) != sizeof(Py_ssize_t). | ||
| class X(object): | ||
| def __len__(self): | ||
| return 1 << 31 | ||
| try: | ||
| len(X()) | ||
| except OverflowError: | ||
| # 32-bit | ||
| MAXSIZE = int((1 << 31) - 1) | ||
| else: | ||
| # 64-bit | ||
| MAXSIZE = int((1 << 63) - 1) | ||
| del X | ||
| def _add_doc(func, doc): | ||
| """Add documentation to a function.""" | ||
| func.__doc__ = doc | ||
| def _import_module(name): | ||
| """Import module, returning the module after the last dot.""" | ||
| __import__(name) | ||
| return sys.modules[name] | ||
| class _LazyDescr(object): | ||
| def __init__(self, name): | ||
| self.name = name | ||
| def __get__(self, obj, tp): | ||
| result = self._resolve() | ||
| setattr(obj, self.name, result) # Invokes __set__. | ||
| try: | ||
| # This is a bit ugly, but it avoids running this again by | ||
| # removing this descriptor. | ||
| delattr(obj.__class__, self.name) | ||
| except AttributeError: | ||
| pass | ||
| return result | ||
| class MovedModule(_LazyDescr): | ||
| def __init__(self, name, old, new=None): | ||
| super(MovedModule, self).__init__(name) | ||
| if PY3: | ||
| if new is None: | ||
| new = name | ||
| self.mod = new | ||
| else: | ||
| self.mod = old | ||
| def _resolve(self): | ||
| return _import_module(self.mod) | ||
| def __getattr__(self, attr): | ||
| _module = self._resolve() | ||
| value = getattr(_module, attr) | ||
| setattr(self, attr, value) | ||
| return value | ||
| class _LazyModule(types.ModuleType): | ||
| def __init__(self, name): | ||
| super(_LazyModule, self).__init__(name) | ||
| self.__doc__ = self.__class__.__doc__ | ||
| def __dir__(self): | ||
| attrs = ["__doc__", "__name__"] | ||
| attrs += [attr.name for attr in self._moved_attributes] | ||
| return attrs | ||
| # Subclasses should override this | ||
| _moved_attributes = [] | ||
| class MovedAttribute(_LazyDescr): | ||
| def __init__(self, name, old_mod, new_mod, old_attr=None, new_attr=None): | ||
| super(MovedAttribute, self).__init__(name) | ||
| if PY3: | ||
| if new_mod is None: | ||
| new_mod = name | ||
| self.mod = new_mod | ||
| if new_attr is None: | ||
| if old_attr is None: | ||
| new_attr = name | ||
| else: | ||
| new_attr = old_attr | ||
| self.attr = new_attr | ||
| else: | ||
| self.mod = old_mod | ||
| if old_attr is None: | ||
| old_attr = name | ||
| self.attr = old_attr | ||
| def _resolve(self): | ||
| module = _import_module(self.mod) | ||
| return getattr(module, self.attr) | ||
| class _SixMetaPathImporter(object): | ||
| """ | ||
| A meta path importer to import six.moves and its submodules. | ||
| This class implements a PEP302 finder and loader. It should be compatible | ||
| with Python 2.5 and all existing versions of Python3 | ||
| """ | ||
| def __init__(self, six_module_name): | ||
| self.name = six_module_name | ||
| self.known_modules = {} | ||
| def _add_module(self, mod, *fullnames): | ||
| for fullname in fullnames: | ||
| self.known_modules[self.name + "." + fullname] = mod | ||
| def _get_module(self, fullname): | ||
| return self.known_modules[self.name + "." + fullname] | ||
| def find_module(self, fullname, path=None): | ||
| if fullname in self.known_modules: | ||
| return self | ||
| return None | ||
| def __get_module(self, fullname): | ||
| try: | ||
| return self.known_modules[fullname] | ||
| except KeyError: | ||
| raise ImportError("This loader does not know module " + fullname) | ||
| def load_module(self, fullname): | ||
| try: | ||
| # in case of a reload | ||
| return sys.modules[fullname] | ||
| except KeyError: | ||
| pass | ||
| mod = self.__get_module(fullname) | ||
| if isinstance(mod, MovedModule): | ||
| mod = mod._resolve() | ||
| else: | ||
| mod.__loader__ = self | ||
| sys.modules[fullname] = mod | ||
| return mod | ||
| def is_package(self, fullname): | ||
| """ | ||
| Return true, if the named module is a package. | ||
| We need this method to get correct spec objects with | ||
| Python 3.4 (see PEP451) | ||
| """ | ||
| return hasattr(self.__get_module(fullname), "__path__") | ||
| def get_code(self, fullname): | ||
| """Return None | ||
| Required, if is_package is implemented""" | ||
| self.__get_module(fullname) # eventually raises ImportError | ||
| return None | ||
| get_source = get_code # same as get_code | ||
| _importer = _SixMetaPathImporter(__name__) | ||
| class _MovedItems(_LazyModule): | ||
| """Lazy loading of moved objects""" | ||
| __path__ = [] # mark as package | ||
| _moved_attributes = [ | ||
| MovedAttribute("cStringIO", "cStringIO", "io", "StringIO"), | ||
| MovedAttribute("filter", "itertools", "builtins", "ifilter", "filter"), | ||
| MovedAttribute("filterfalse", "itertools", "itertools", "ifilterfalse", "filterfalse"), | ||
| MovedAttribute("input", "__builtin__", "builtins", "raw_input", "input"), | ||
| MovedAttribute("intern", "__builtin__", "sys"), | ||
| MovedAttribute("map", "itertools", "builtins", "imap", "map"), | ||
| MovedAttribute("getcwd", "os", "os", "getcwdu", "getcwd"), | ||
| MovedAttribute("getcwdb", "os", "os", "getcwd", "getcwdb"), | ||
| MovedAttribute("getoutput", "commands", "subprocess"), | ||
| MovedAttribute("range", "__builtin__", "builtins", "xrange", "range"), | ||
| MovedAttribute("reload_module", "__builtin__", "importlib" if PY34 else "imp", "reload"), | ||
| MovedAttribute("reduce", "__builtin__", "functools"), | ||
| MovedAttribute("shlex_quote", "pipes", "shlex", "quote"), | ||
| MovedAttribute("StringIO", "StringIO", "io"), | ||
| MovedAttribute("UserDict", "UserDict", "collections"), | ||
| MovedAttribute("UserList", "UserList", "collections"), | ||
| MovedAttribute("UserString", "UserString", "collections"), | ||
| MovedAttribute("xrange", "__builtin__", "builtins", "xrange", "range"), | ||
| MovedAttribute("zip", "itertools", "builtins", "izip", "zip"), | ||
| MovedAttribute("zip_longest", "itertools", "itertools", "izip_longest", "zip_longest"), | ||
| MovedModule("builtins", "__builtin__"), | ||
| MovedModule("configparser", "ConfigParser"), | ||
| MovedModule("copyreg", "copy_reg"), | ||
| MovedModule("dbm_gnu", "gdbm", "dbm.gnu"), | ||
| MovedModule("_dummy_thread", "dummy_thread", "_dummy_thread"), | ||
| MovedModule("http_cookiejar", "cookielib", "http.cookiejar"), | ||
| MovedModule("http_cookies", "Cookie", "http.cookies"), | ||
| MovedModule("html_entities", "htmlentitydefs", "html.entities"), | ||
| MovedModule("html_parser", "HTMLParser", "html.parser"), | ||
| MovedModule("http_client", "httplib", "http.client"), | ||
| MovedModule("email_mime_base", "email.MIMEBase", "email.mime.base"), | ||
| MovedModule("email_mime_image", "email.MIMEImage", "email.mime.image"), | ||
| MovedModule("email_mime_multipart", "email.MIMEMultipart", "email.mime.multipart"), | ||
| MovedModule("email_mime_nonmultipart", "email.MIMENonMultipart", "email.mime.nonmultipart"), | ||
| MovedModule("email_mime_text", "email.MIMEText", "email.mime.text"), | ||
| MovedModule("BaseHTTPServer", "BaseHTTPServer", "http.server"), | ||
| MovedModule("CGIHTTPServer", "CGIHTTPServer", "http.server"), | ||
| MovedModule("SimpleHTTPServer", "SimpleHTTPServer", "http.server"), | ||
| MovedModule("cPickle", "cPickle", "pickle"), | ||
| MovedModule("queue", "Queue"), | ||
| MovedModule("reprlib", "repr"), | ||
| MovedModule("socketserver", "SocketServer"), | ||
| MovedModule("_thread", "thread", "_thread"), | ||
| MovedModule("tkinter", "Tkinter"), | ||
| MovedModule("tkinter_dialog", "Dialog", "tkinter.dialog"), | ||
| MovedModule("tkinter_filedialog", "FileDialog", "tkinter.filedialog"), | ||
| MovedModule("tkinter_scrolledtext", "ScrolledText", "tkinter.scrolledtext"), | ||
| MovedModule("tkinter_simpledialog", "SimpleDialog", "tkinter.simpledialog"), | ||
| MovedModule("tkinter_tix", "Tix", "tkinter.tix"), | ||
| MovedModule("tkinter_ttk", "ttk", "tkinter.ttk"), | ||
| MovedModule("tkinter_constants", "Tkconstants", "tkinter.constants"), | ||
| MovedModule("tkinter_dnd", "Tkdnd", "tkinter.dnd"), | ||
| MovedModule("tkinter_colorchooser", "tkColorChooser", | ||
| "tkinter.colorchooser"), | ||
| MovedModule("tkinter_commondialog", "tkCommonDialog", | ||
| "tkinter.commondialog"), | ||
| MovedModule("tkinter_tkfiledialog", "tkFileDialog", "tkinter.filedialog"), | ||
| MovedModule("tkinter_font", "tkFont", "tkinter.font"), | ||
| MovedModule("tkinter_messagebox", "tkMessageBox", "tkinter.messagebox"), | ||
| MovedModule("tkinter_tksimpledialog", "tkSimpleDialog", | ||
| "tkinter.simpledialog"), | ||
| MovedModule("urllib_parse", __name__ + ".moves.urllib_parse", "urllib.parse"), | ||
| MovedModule("urllib_error", __name__ + ".moves.urllib_error", "urllib.error"), | ||
| MovedModule("urllib", __name__ + ".moves.urllib", __name__ + ".moves.urllib"), | ||
| MovedModule("urllib_robotparser", "robotparser", "urllib.robotparser"), | ||
| MovedModule("xmlrpc_client", "xmlrpclib", "xmlrpc.client"), | ||
| MovedModule("xmlrpc_server", "SimpleXMLRPCServer", "xmlrpc.server"), | ||
| ] | ||
| # Add windows specific modules. | ||
| if sys.platform == "win32": | ||
| _moved_attributes += [ | ||
| MovedModule("winreg", "_winreg"), | ||
| ] | ||
| for attr in _moved_attributes: | ||
| setattr(_MovedItems, attr.name, attr) | ||
| if isinstance(attr, MovedModule): | ||
| _importer._add_module(attr, "moves." + attr.name) | ||
| del attr | ||
| _MovedItems._moved_attributes = _moved_attributes | ||
| moves = _MovedItems(__name__ + ".moves") | ||
| _importer._add_module(moves, "moves") | ||
| class Module_six_moves_urllib_parse(_LazyModule): | ||
| """Lazy loading of moved objects in six.moves.urllib_parse""" | ||
| _urllib_parse_moved_attributes = [ | ||
| MovedAttribute("ParseResult", "urlparse", "urllib.parse"), | ||
| MovedAttribute("SplitResult", "urlparse", "urllib.parse"), | ||
| MovedAttribute("parse_qs", "urlparse", "urllib.parse"), | ||
| MovedAttribute("parse_qsl", "urlparse", "urllib.parse"), | ||
| MovedAttribute("urldefrag", "urlparse", "urllib.parse"), | ||
| MovedAttribute("urljoin", "urlparse", "urllib.parse"), | ||
| MovedAttribute("urlparse", "urlparse", "urllib.parse"), | ||
| MovedAttribute("urlsplit", "urlparse", "urllib.parse"), | ||
| MovedAttribute("urlunparse", "urlparse", "urllib.parse"), | ||
| MovedAttribute("urlunsplit", "urlparse", "urllib.parse"), | ||
| MovedAttribute("quote", "urllib", "urllib.parse"), | ||
| MovedAttribute("quote_plus", "urllib", "urllib.parse"), | ||
| MovedAttribute("unquote", "urllib", "urllib.parse"), | ||
| MovedAttribute("unquote_plus", "urllib", "urllib.parse"), | ||
| MovedAttribute("unquote_to_bytes", "urllib", "urllib.parse", "unquote", "unquote_to_bytes"), | ||
| MovedAttribute("urlencode", "urllib", "urllib.parse"), | ||
| MovedAttribute("splitquery", "urllib", "urllib.parse"), | ||
| MovedAttribute("splittag", "urllib", "urllib.parse"), | ||
| MovedAttribute("splituser", "urllib", "urllib.parse"), | ||
| MovedAttribute("splitvalue", "urllib", "urllib.parse"), | ||
| MovedAttribute("uses_fragment", "urlparse", "urllib.parse"), | ||
| MovedAttribute("uses_netloc", "urlparse", "urllib.parse"), | ||
| MovedAttribute("uses_params", "urlparse", "urllib.parse"), | ||
| MovedAttribute("uses_query", "urlparse", "urllib.parse"), | ||
| MovedAttribute("uses_relative", "urlparse", "urllib.parse"), | ||
| ] | ||
| for attr in _urllib_parse_moved_attributes: | ||
| setattr(Module_six_moves_urllib_parse, attr.name, attr) | ||
| del attr | ||
| Module_six_moves_urllib_parse._moved_attributes = _urllib_parse_moved_attributes | ||
| _importer._add_module(Module_six_moves_urllib_parse(__name__ + ".moves.urllib_parse"), | ||
| "moves.urllib_parse", "moves.urllib.parse") | ||
| class Module_six_moves_urllib_error(_LazyModule): | ||
| """Lazy loading of moved objects in six.moves.urllib_error""" | ||
| _urllib_error_moved_attributes = [ | ||
| MovedAttribute("URLError", "urllib2", "urllib.error"), | ||
| MovedAttribute("HTTPError", "urllib2", "urllib.error"), | ||
| MovedAttribute("ContentTooShortError", "urllib", "urllib.error"), | ||
| ] | ||
| for attr in _urllib_error_moved_attributes: | ||
| setattr(Module_six_moves_urllib_error, attr.name, attr) | ||
| del attr | ||
| Module_six_moves_urllib_error._moved_attributes = _urllib_error_moved_attributes | ||
| _importer._add_module(Module_six_moves_urllib_error(__name__ + ".moves.urllib.error"), | ||
| "moves.urllib_error", "moves.urllib.error") | ||
| class Module_six_moves_urllib_request(_LazyModule): | ||
| """Lazy loading of moved objects in six.moves.urllib_request""" | ||
| _urllib_request_moved_attributes = [ | ||
| MovedAttribute("urlopen", "urllib2", "urllib.request"), | ||
| MovedAttribute("install_opener", "urllib2", "urllib.request"), | ||
| MovedAttribute("build_opener", "urllib2", "urllib.request"), | ||
| MovedAttribute("pathname2url", "urllib", "urllib.request"), | ||
| MovedAttribute("url2pathname", "urllib", "urllib.request"), | ||
| MovedAttribute("getproxies", "urllib", "urllib.request"), | ||
| MovedAttribute("Request", "urllib2", "urllib.request"), | ||
| MovedAttribute("OpenerDirector", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPDefaultErrorHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPRedirectHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPCookieProcessor", "urllib2", "urllib.request"), | ||
| MovedAttribute("ProxyHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("BaseHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPPasswordMgr", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPPasswordMgrWithDefaultRealm", "urllib2", "urllib.request"), | ||
| MovedAttribute("AbstractBasicAuthHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPBasicAuthHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("ProxyBasicAuthHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("AbstractDigestAuthHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPDigestAuthHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("ProxyDigestAuthHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPSHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("FileHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("FTPHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("CacheFTPHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("UnknownHandler", "urllib2", "urllib.request"), | ||
| MovedAttribute("HTTPErrorProcessor", "urllib2", "urllib.request"), | ||
| MovedAttribute("urlretrieve", "urllib", "urllib.request"), | ||
| MovedAttribute("urlcleanup", "urllib", "urllib.request"), | ||
| MovedAttribute("URLopener", "urllib", "urllib.request"), | ||
| MovedAttribute("FancyURLopener", "urllib", "urllib.request"), | ||
| MovedAttribute("proxy_bypass", "urllib", "urllib.request"), | ||
| MovedAttribute("parse_http_list", "urllib2", "urllib.request"), | ||
| MovedAttribute("parse_keqv_list", "urllib2", "urllib.request"), | ||
| ] | ||
| for attr in _urllib_request_moved_attributes: | ||
| setattr(Module_six_moves_urllib_request, attr.name, attr) | ||
| del attr | ||
| Module_six_moves_urllib_request._moved_attributes = _urllib_request_moved_attributes | ||
| _importer._add_module(Module_six_moves_urllib_request(__name__ + ".moves.urllib.request"), | ||
| "moves.urllib_request", "moves.urllib.request") | ||
| class Module_six_moves_urllib_response(_LazyModule): | ||
| """Lazy loading of moved objects in six.moves.urllib_response""" | ||
| _urllib_response_moved_attributes = [ | ||
| MovedAttribute("addbase", "urllib", "urllib.response"), | ||
| MovedAttribute("addclosehook", "urllib", "urllib.response"), | ||
| MovedAttribute("addinfo", "urllib", "urllib.response"), | ||
| MovedAttribute("addinfourl", "urllib", "urllib.response"), | ||
| ] | ||
| for attr in _urllib_response_moved_attributes: | ||
| setattr(Module_six_moves_urllib_response, attr.name, attr) | ||
| del attr | ||
| Module_six_moves_urllib_response._moved_attributes = _urllib_response_moved_attributes | ||
| _importer._add_module(Module_six_moves_urllib_response(__name__ + ".moves.urllib.response"), | ||
| "moves.urllib_response", "moves.urllib.response") | ||
| class Module_six_moves_urllib_robotparser(_LazyModule): | ||
| """Lazy loading of moved objects in six.moves.urllib_robotparser""" | ||
| _urllib_robotparser_moved_attributes = [ | ||
| MovedAttribute("RobotFileParser", "robotparser", "urllib.robotparser"), | ||
| ] | ||
| for attr in _urllib_robotparser_moved_attributes: | ||
| setattr(Module_six_moves_urllib_robotparser, attr.name, attr) | ||
| del attr | ||
| Module_six_moves_urllib_robotparser._moved_attributes = _urllib_robotparser_moved_attributes | ||
| _importer._add_module(Module_six_moves_urllib_robotparser(__name__ + ".moves.urllib.robotparser"), | ||
| "moves.urllib_robotparser", "moves.urllib.robotparser") | ||
| class Module_six_moves_urllib(types.ModuleType): | ||
| """Create a six.moves.urllib namespace that resembles the Python 3 namespace""" | ||
| __path__ = [] # mark as package | ||
| parse = _importer._get_module("moves.urllib_parse") | ||
| error = _importer._get_module("moves.urllib_error") | ||
| request = _importer._get_module("moves.urllib_request") | ||
| response = _importer._get_module("moves.urllib_response") | ||
| robotparser = _importer._get_module("moves.urllib_robotparser") | ||
| def __dir__(self): | ||
| return ['parse', 'error', 'request', 'response', 'robotparser'] | ||
| _importer._add_module(Module_six_moves_urllib(__name__ + ".moves.urllib"), | ||
| "moves.urllib") | ||
| def add_move(move): | ||
| """Add an item to six.moves.""" | ||
| setattr(_MovedItems, move.name, move) | ||
| def remove_move(name): | ||
| """Remove item from six.moves.""" | ||
| try: | ||
| delattr(_MovedItems, name) | ||
| except AttributeError: | ||
| try: | ||
| del moves.__dict__[name] | ||
| except KeyError: | ||
| raise AttributeError("no such move, %r" % (name,)) | ||
| if PY3: | ||
| _meth_func = "__func__" | ||
| _meth_self = "__self__" | ||
| _func_closure = "__closure__" | ||
| _func_code = "__code__" | ||
| _func_defaults = "__defaults__" | ||
| _func_globals = "__globals__" | ||
| else: | ||
| _meth_func = "im_func" | ||
| _meth_self = "im_self" | ||
| _func_closure = "func_closure" | ||
| _func_code = "func_code" | ||
| _func_defaults = "func_defaults" | ||
| _func_globals = "func_globals" | ||
| try: | ||
| advance_iterator = next | ||
| except NameError: | ||
| def advance_iterator(it): | ||
| return it.next() | ||
| next = advance_iterator | ||
| try: | ||
| callable = callable | ||
| except NameError: | ||
| def callable(obj): | ||
| return any("__call__" in klass.__dict__ for klass in type(obj).__mro__) | ||
| if PY3: | ||
| def get_unbound_function(unbound): | ||
| return unbound | ||
| create_bound_method = types.MethodType | ||
| def create_unbound_method(func, cls): | ||
| return func | ||
| Iterator = object | ||
| else: | ||
| def get_unbound_function(unbound): | ||
| return unbound.im_func | ||
| def create_bound_method(func, obj): | ||
| return types.MethodType(func, obj, obj.__class__) | ||
| def create_unbound_method(func, cls): | ||
| return types.MethodType(func, None, cls) | ||
| class Iterator(object): | ||
| def next(self): | ||
| return type(self).__next__(self) | ||
| callable = callable | ||
| _add_doc(get_unbound_function, | ||
| """Get the function out of a possibly unbound function""") | ||
| get_method_function = operator.attrgetter(_meth_func) | ||
| get_method_self = operator.attrgetter(_meth_self) | ||
| get_function_closure = operator.attrgetter(_func_closure) | ||
| get_function_code = operator.attrgetter(_func_code) | ||
| get_function_defaults = operator.attrgetter(_func_defaults) | ||
| get_function_globals = operator.attrgetter(_func_globals) | ||
| if PY3: | ||
| def iterkeys(d, **kw): | ||
| return iter(d.keys(**kw)) | ||
| def itervalues(d, **kw): | ||
| return iter(d.values(**kw)) | ||
| def iteritems(d, **kw): | ||
| return iter(d.items(**kw)) | ||
| def iterlists(d, **kw): | ||
| return iter(d.lists(**kw)) | ||
| viewkeys = operator.methodcaller("keys") | ||
| viewvalues = operator.methodcaller("values") | ||
| viewitems = operator.methodcaller("items") | ||
| else: | ||
| def iterkeys(d, **kw): | ||
| return d.iterkeys(**kw) | ||
| def itervalues(d, **kw): | ||
| return d.itervalues(**kw) | ||
| def iteritems(d, **kw): | ||
| return d.iteritems(**kw) | ||
| def iterlists(d, **kw): | ||
| return d.iterlists(**kw) | ||
| viewkeys = operator.methodcaller("viewkeys") | ||
| viewvalues = operator.methodcaller("viewvalues") | ||
| viewitems = operator.methodcaller("viewitems") | ||
| _add_doc(iterkeys, "Return an iterator over the keys of a dictionary.") | ||
| _add_doc(itervalues, "Return an iterator over the values of a dictionary.") | ||
| _add_doc(iteritems, | ||
| "Return an iterator over the (key, value) pairs of a dictionary.") | ||
| _add_doc(iterlists, | ||
| "Return an iterator over the (key, [values]) pairs of a dictionary.") | ||
| if PY3: | ||
| def b(s): | ||
| return s.encode("latin-1") | ||
| def u(s): | ||
| return s | ||
| unichr = chr | ||
| import struct | ||
| int2byte = struct.Struct(">B").pack | ||
| del struct | ||
| byte2int = operator.itemgetter(0) | ||
| ordbyte = int | ||
| indexbytes = operator.getitem | ||
| iterbytes = iter | ||
| import io | ||
| StringIO = io.StringIO | ||
| BytesIO = io.BytesIO | ||
| _assertCountEqual = "assertCountEqual" | ||
| if sys.version_info[1] <= 1: | ||
| _assertRaisesRegex = "assertRaisesRegexp" | ||
| _assertRegex = "assertRegexpMatches" | ||
| else: | ||
| _assertRaisesRegex = "assertRaisesRegex" | ||
| _assertRegex = "assertRegex" | ||
| else: | ||
| def b(s): | ||
| return s | ||
| # Workaround for standalone backslash | ||
| def u(s): | ||
| return unicode(s.replace(r'\\', r'\\\\'), "unicode_escape") | ||
| unichr = unichr | ||
| int2byte = chr | ||
| def byte2int(bs): | ||
| return ord(bs[0]) | ||
| ordbyte = byte2int | ||
| def indexbytes(buf, i): | ||
| return ord(buf[i]) | ||
| iterbytes = functools.partial(itertools.imap, ord) | ||
| import StringIO | ||
| StringIO = BytesIO = StringIO.StringIO | ||
| _assertCountEqual = "assertItemsEqual" | ||
| _assertRaisesRegex = "assertRaisesRegexp" | ||
| _assertRegex = "assertRegexpMatches" | ||
| _add_doc(b, """Byte literal""") | ||
| _add_doc(u, """Text literal""") | ||
| def assertCountEqual(self, *args, **kwargs): | ||
| return getattr(self, _assertCountEqual)(*args, **kwargs) | ||
| def assertRaisesRegex(self, *args, **kwargs): | ||
| return getattr(self, _assertRaisesRegex)(*args, **kwargs) | ||
| def assertRegex(self, *args, **kwargs): | ||
| return getattr(self, _assertRegex)(*args, **kwargs) | ||
| if PY3: | ||
| exec_ = getattr(moves.builtins, "exec") | ||
| def reraise(tp, value, tb=None): | ||
| try: | ||
| if value is None: | ||
| value = tp() | ||
| if value.__traceback__ is not tb: | ||
| raise value.with_traceback(tb) | ||
| raise value | ||
| finally: | ||
| value = None | ||
| tb = None | ||
| else: | ||
| def exec_(_code_, _globs_=None, _locs_=None): | ||
| """Execute code in a namespace.""" | ||
| if _globs_ is None: | ||
| frame = sys._getframe(1) | ||
| _globs_ = frame.f_globals | ||
| if _locs_ is None: | ||
| _locs_ = frame.f_locals | ||
| del frame | ||
| elif _locs_ is None: | ||
| _locs_ = _globs_ | ||
| exec("""exec _code_ in _globs_, _locs_""") | ||
| exec_("""def reraise(tp, value, tb=None): | ||
| try: | ||
| raise tp, value, tb | ||
| finally: | ||
| tb = None | ||
| """) | ||
| if sys.version_info[:2] == (3, 2): | ||
| exec_("""def raise_from(value, from_value): | ||
| try: | ||
| if from_value is None: | ||
| raise value | ||
| raise value from from_value | ||
| finally: | ||
| value = None | ||
| """) | ||
| elif sys.version_info[:2] > (3, 2): | ||
| exec_("""def raise_from(value, from_value): | ||
| try: | ||
| raise value from from_value | ||
| finally: | ||
| value = None | ||
| """) | ||
| else: | ||
| def raise_from(value, from_value): | ||
| raise value | ||
| print_ = getattr(moves.builtins, "print", None) | ||
| if print_ is None: | ||
| def print_(*args, **kwargs): | ||
| """The new-style print function for Python 2.4 and 2.5.""" | ||
| fp = kwargs.pop("file", sys.stdout) | ||
| if fp is None: | ||
| return | ||
| def write(data): | ||
| if not isinstance(data, basestring): | ||
| data = str(data) | ||
| # If the file has an encoding, encode unicode with it. | ||
| if (isinstance(fp, file) and | ||
| isinstance(data, unicode) and | ||
| fp.encoding is not None): | ||
| errors = getattr(fp, "errors", None) | ||
| if errors is None: | ||
| errors = "strict" | ||
| data = data.encode(fp.encoding, errors) | ||
| fp.write(data) | ||
| want_unicode = False | ||
| sep = kwargs.pop("sep", None) | ||
| if sep is not None: | ||
| if isinstance(sep, unicode): | ||
| want_unicode = True | ||
| elif not isinstance(sep, str): | ||
| raise TypeError("sep must be None or a string") | ||
| end = kwargs.pop("end", None) | ||
| if end is not None: | ||
| if isinstance(end, unicode): | ||
| want_unicode = True | ||
| elif not isinstance(end, str): | ||
| raise TypeError("end must be None or a string") | ||
| if kwargs: | ||
| raise TypeError("invalid keyword arguments to print()") | ||
| if not want_unicode: | ||
| for arg in args: | ||
| if isinstance(arg, unicode): | ||
| want_unicode = True | ||
| break | ||
| if want_unicode: | ||
| newline = unicode("\n") | ||
| space = unicode(" ") | ||
| else: | ||
| newline = "\n" | ||
| space = " " | ||
| if sep is None: | ||
| sep = space | ||
| if end is None: | ||
| end = newline | ||
| for i, arg in enumerate(args): | ||
| if i: | ||
| write(sep) | ||
| write(arg) | ||
| write(end) | ||
| if sys.version_info[:2] < (3, 3): | ||
| _print = print_ | ||
| def print_(*args, **kwargs): | ||
| fp = kwargs.get("file", sys.stdout) | ||
| flush = kwargs.pop("flush", False) | ||
| _print(*args, **kwargs) | ||
| if flush and fp is not None: | ||
| fp.flush() | ||
| _add_doc(reraise, """Reraise an exception.""") | ||
| if sys.version_info[0:2] < (3, 4): | ||
| def wraps(wrapped, assigned=functools.WRAPPER_ASSIGNMENTS, | ||
| updated=functools.WRAPPER_UPDATES): | ||
| def wrapper(f): | ||
| f = functools.wraps(wrapped, assigned, updated)(f) | ||
| f.__wrapped__ = wrapped | ||
| return f | ||
| return wrapper | ||
| else: | ||
| wraps = functools.wraps | ||
| def with_metaclass(meta, *bases): | ||
| """Create a base class with a metaclass.""" | ||
| # This requires a bit of explanation: the basic idea is to make a dummy | ||
| # metaclass for one level of class instantiation that replaces itself with | ||
| # the actual metaclass. | ||
| class metaclass(type): | ||
| def __new__(cls, name, this_bases, d): | ||
| return meta(name, bases, d) | ||
| @classmethod | ||
| def __prepare__(cls, name, this_bases): | ||
| return meta.__prepare__(name, bases) | ||
| return type.__new__(metaclass, 'temporary_class', (), {}) | ||
| def add_metaclass(metaclass): | ||
| """Class decorator for creating a class with a metaclass.""" | ||
| def wrapper(cls): | ||
| orig_vars = cls.__dict__.copy() | ||
| slots = orig_vars.get('__slots__') | ||
| if slots is not None: | ||
| if isinstance(slots, str): | ||
| slots = [slots] | ||
| for slots_var in slots: | ||
| orig_vars.pop(slots_var) | ||
| orig_vars.pop('__dict__', None) | ||
| orig_vars.pop('__weakref__', None) | ||
| return metaclass(cls.__name__, cls.__bases__, orig_vars) | ||
| return wrapper | ||
| def ensure_binary(s, encoding='utf-8', errors='strict'): | ||
| """Coerce **s** to six.binary_type. | ||
| For Python 2: | ||
| - `unicode` -> encoded to `str` | ||
| - `str` -> `str` | ||
| For Python 3: | ||
| - `str` -> encoded to `bytes` | ||
| - `bytes` -> `bytes` | ||
| """ | ||
| if isinstance(s, text_type): | ||
| return s.encode(encoding, errors) | ||
| elif isinstance(s, binary_type): | ||
| return s | ||
| else: | ||
| raise TypeError("not expecting type '%s'" % type(s)) | ||
| def ensure_str(s, encoding='utf-8', errors='strict'): | ||
| """Coerce *s* to `str`. | ||
| For Python 2: | ||
| - `unicode` -> encoded to `str` | ||
| - `str` -> `str` | ||
| For Python 3: | ||
| - `str` -> `str` | ||
| - `bytes` -> decoded to `str` | ||
| """ | ||
| if not isinstance(s, (text_type, binary_type)): | ||
| raise TypeError("not expecting type '%s'" % type(s)) | ||
| if PY2 and isinstance(s, text_type): | ||
| s = s.encode(encoding, errors) | ||
| elif PY3 and isinstance(s, binary_type): | ||
| s = s.decode(encoding, errors) | ||
| return s | ||
| def ensure_text(s, encoding='utf-8', errors='strict'): | ||
| """Coerce *s* to six.text_type. | ||
| For Python 2: | ||
| - `unicode` -> `unicode` | ||
| - `str` -> `unicode` | ||
| For Python 3: | ||
| - `str` -> `str` | ||
| - `bytes` -> decoded to `str` | ||
| """ | ||
| if isinstance(s, binary_type): | ||
| return s.decode(encoding, errors) | ||
| elif isinstance(s, text_type): | ||
| return s | ||
| else: | ||
| raise TypeError("not expecting type '%s'" % type(s)) | ||
| def python_2_unicode_compatible(klass): | ||
| """ | ||
| A decorator that defines __unicode__ and __str__ methods under Python 2. | ||
| Under Python 3 it does nothing. | ||
| To support Python 2 and 3 with a single code base, define a __str__ method | ||
| returning text and apply this decorator to the class. | ||
| """ | ||
| if PY2: | ||
| if '__str__' not in klass.__dict__: | ||
| raise ValueError("@python_2_unicode_compatible cannot be applied " | ||
| "to %s because it doesn't define __str__()." % | ||
| klass.__name__) | ||
| klass.__unicode__ = klass.__str__ | ||
| klass.__str__ = lambda self: self.__unicode__().encode('utf-8') | ||
| return klass | ||
| # Complete the moves implementation. | ||
| # This code is at the end of this module to speed up module loading. | ||
| # Turn this module into a package. | ||
| __path__ = [] # required for PEP 302 and PEP 451 | ||
| __package__ = __name__ # see PEP 366 @ReservedAssignment | ||
| if globals().get("__spec__") is not None: | ||
| __spec__.submodule_search_locations = [] # PEP 451 @UndefinedVariable | ||
| # Remove other six meta path importers, since they cause problems. This can | ||
| # happen if six is removed from sys.modules and then reloaded. (Setuptools does | ||
| # this for some reason.) | ||
| if sys.meta_path: | ||
| for i, importer in enumerate(sys.meta_path): | ||
| # Here's some real nastiness: Another "instance" of the six module might | ||
| # be floating around. Therefore, we can't use isinstance() to check for | ||
| # the six meta path importer, since the other six instance will have | ||
| # inserted an importer with different class. | ||
| if (type(importer).__name__ == "_SixMetaPathImporter" and | ||
| importer.name == __name__): | ||
| del sys.meta_path[i] | ||
| break | ||
| del i, importer | ||
| # Finally, add the importer to the meta path import hook. | ||
| sys.meta_path.append(_importer) |
| __all__ = ['ntriples_parser', 'nquads_parser', 'turtle_parser', 'jsonld_parser'] | ||
| from .lark import ( | ||
| ntriples_parser, | ||
| nquads_parser, | ||
| turtle_parser, | ||
| ) | ||
| from .jsonld import ( | ||
| jsonld_parser, | ||
| ) |
| from collections import defaultdict | ||
| from threading import local | ||
| import pymantic.primitives | ||
| class BaseParser(object): | ||
| """Common base class for all parsers | ||
| Provides shared utilities for creating RDF objects, handling IRIs, and | ||
| tracking parser state. | ||
| """ | ||
| def __init__(self, environment=None): | ||
| super().__init__() | ||
| self.env = environment or pymantic.primitives.RDFEnvironment() | ||
| self.profile = self.env.createProfile() | ||
| self._call_state = local() | ||
| def make_datatype_literal(self, value, datatype): | ||
| return self.env.createLiteral(value=value, datatype=datatype) | ||
| def make_language_literal(self, value, lang=None): | ||
| if lang: | ||
| return self.env.createLiteral(value=value, language=lang) | ||
| else: | ||
| return self.env.createLiteral(value=value) | ||
| def make_named_node(self, iri): | ||
| return self.env.createNamedNode(iri) | ||
| def make_blank_node(self, label=None): | ||
| if label: | ||
| return self._call_state.bnodes[label] | ||
| else: | ||
| return self.env.createBlankNode() | ||
| def make_triple(self, subject, predicate, object): | ||
| return self.env.createTriple(subject, predicate, object) | ||
| def make_quad(self, subject, predicate, object, graph): | ||
| return self.env.createQuad(subject, predicate, object, graph) | ||
| def _prepare_parse(self, graph): | ||
| self._call_state.bnodes = defaultdict(self.env.createBlankNode) | ||
| self._call_state.graph = graph | ||
| def _cleanup_parse(self): | ||
| del self._call_state.bnodes | ||
| del self._call_state.graph | ||
| def _make_graph(self): | ||
| return self.env.createGraph() |
| """Parse RDF serialized as jsonld | ||
| Usage:: | ||
| from pymantic.parsers.jsonld import jsonld_parser | ||
| graph = jsonld_parser.parse_json(json.load(io.open('file.jsonld', mode='rt'))) | ||
| """ | ||
| import json | ||
| from .base import BaseParser | ||
| class PyLDLoader(BaseParser): | ||
| class _Loader(object): | ||
| def __init__(self, pyld_loader): | ||
| self.pyld_loader = pyld_loader | ||
| def parse_file(self, f): | ||
| jobj = json.load(f) | ||
| self.pyld_loader.process_jobj(jobj) | ||
| def parse(self, string): | ||
| jobj = json.loads(string) | ||
| self.pyld_loader.process_jobj(jobj) | ||
| def parse_json(self, jobj, sink=None, options=None): | ||
| if sink is None: | ||
| sink = self._make_graph() | ||
| self._prepare_parse(sink) | ||
| self.process_jobj(jobj, options) | ||
| self._cleanup_parse() | ||
| return sink | ||
| def make_quad(self, values): | ||
| quad = self.env.createQuad(*values) | ||
| self._call_state.graph.add(quad) | ||
| return quad | ||
| def _make_graph(self): | ||
| return self.env.createDataset() | ||
| def __init__(self, *args, **kwargs): | ||
| self.document = self._Loader(self) | ||
| super(PyLDLoader, self).__init__(*args, **kwargs) | ||
| def process_triple_fragment(self, triple_fragment): | ||
| if triple_fragment['type'] == 'IRI': | ||
| return self.env.createNamedNode(triple_fragment['value']) | ||
| elif triple_fragment['type'] == 'blank node': | ||
| return self._call_state.bnodes[triple_fragment['value']] | ||
| elif triple_fragment['type'] == 'literal': | ||
| language = None | ||
| if 'language' in triple_fragment: | ||
| language = triple_fragment['language'] | ||
| return self.env.createLiteral( | ||
| value=triple_fragment['value'], | ||
| datatype=self.env.createNamedNode(triple_fragment['datatype']), | ||
| language=language | ||
| ) | ||
| def process_jobj(self, jobj, options=None): | ||
| from pyld.jsonld import to_rdf | ||
| dataset = to_rdf(jobj, options=options) | ||
| for graph_name, triples in dataset.items(): | ||
| graph_iri = ( | ||
| self.env.createNamedNode(graph_name) if | ||
| graph_name != '@default' else | ||
| None | ||
| ) | ||
| for triple in triples: | ||
| self.make_quad( | ||
| (self.process_triple_fragment(triple['subject']), | ||
| self.process_triple_fragment(triple['predicate']), | ||
| self.process_triple_fragment(triple['object']), | ||
| graph_iri, | ||
| ) | ||
| ) | ||
| jsonld_parser = PyLDLoader() |
| from .ntriples import ntriples_parser | ||
| from . import turtle as turtle_parser | ||
| from .nquads import nquads_parser | ||
| __all__ = [ | ||
| 'ntriples_parser', | ||
| 'turtle_parser', | ||
| 'nquads_parser', | ||
| ] |
| from pymantic.compat import ( | ||
| binary_type, | ||
| ) | ||
| class LarkParser(object): | ||
| """Provide a consistent interface for parsing serialized RDF using one | ||
| of the lark parsers. | ||
| """ | ||
| def __init__(self, lark): | ||
| self.lark = lark | ||
| def line_by_line_parser(self, stream): | ||
| for line in stream: # Equivalent to readline | ||
| if line: | ||
| yield next(self.lark.parse(line)) | ||
| def parse(self, string_or_stream, graph=None): | ||
| """Parse a string or file-like object into RDF primitives and add | ||
| them to either the provided graph or a new graph. | ||
| """ | ||
| tf = self.lark.options.transformer | ||
| try: | ||
| if graph is None: | ||
| graph = tf._make_graph() | ||
| tf._prepare_parse(graph) | ||
| if hasattr(string_or_stream, 'readline'): | ||
| triples = self.line_by_line_parser(string_or_stream) | ||
| else: | ||
| # Presume string. | ||
| triples = self.lark.parse(string_or_stream) | ||
| graph.addAll(triples) | ||
| finally: | ||
| tf._cleanup_parse() | ||
| return graph | ||
| def parse_string(self, string_or_bytes, graph=None): | ||
| """Parse a string, decoding it from bytes to UTF-8 if necessary. | ||
| """ | ||
| if isinstance(string_or_bytes, binary_type): | ||
| string = string_or_bytes.decode('utf-8') | ||
| else: | ||
| string = string_or_bytes | ||
| return self.parse(string, graph) |
| """Parse RDF serialized as nquads files. | ||
| Usage:: | ||
| from pymantic.parsers.lark import nquads_parser | ||
| graph = nquads_parser.parse(io.open('a_file.nq', mode='rt')) | ||
| graph2 = nquads_parser.parse("<http://a.example/s> <http://a.example/p> <http://a.example/o> <http://a.example/g> .") | ||
| If ``.parse()`` is called with a file-like object implementing ``readline``, | ||
| it will efficiently parse line by line rather than parsing the entire file. | ||
| """ | ||
| from lark import Lark | ||
| from pymantic.primitives import ( | ||
| Quad, | ||
| ) | ||
| from .base import ( | ||
| LarkParser, | ||
| ) | ||
| from .ntriples import ( | ||
| grammar, | ||
| NTriplesTransformer, | ||
| ) | ||
| class NQuadsTransformer(NTriplesTransformer): | ||
| """Transform the tokenized nquads into RDF primitives. | ||
| """ | ||
| def quad(self, children): | ||
| subject, predicate, object_, graph = children | ||
| return self.make_quad(subject, predicate, object_, graph) | ||
| def quads_start(self, children): | ||
| for child in children: | ||
| if isinstance(child, Quad): | ||
| yield child | ||
| nq_lark = Lark( | ||
| grammar, start='quads_start', parser='lalr', | ||
| transformer=NQuadsTransformer(), | ||
| ) | ||
| #! A fully-instantiated nquads parser | ||
| nquads_parser = LarkParser(nq_lark) |
| """Parse RDF serialized as ntriples files. | ||
| Usage:: | ||
| from pymantic.parsers.lark import ntriples_parser | ||
| graph = ntriples_parser.parse(io.open('a_file.nt', mode='rt')) | ||
| graph2 = ntriples_parser.parse("<http://a.example/s> <http://a.example/p> <http://a.example/o> .") | ||
| If ``.parse()`` is called with a file-like object implementing ``readline``, | ||
| it will efficiently parse line by line rather than parsing the entire file. | ||
| """ | ||
| from lark import ( | ||
| Lark, | ||
| Transformer, | ||
| ) | ||
| from pymantic.primitives import ( | ||
| NamedNode, | ||
| Triple, | ||
| ) | ||
| from pymantic.util import ( | ||
| decode_literal, | ||
| ) | ||
| from pymantic.parsers.base import ( | ||
| BaseParser, | ||
| ) | ||
| from .base import ( | ||
| LarkParser, | ||
| ) | ||
| grammar = r"""triples_start: triple? (EOL triple)* EOL? | ||
| triple: subject predicate object "." | ||
| quads_start: quad? (EOL quad)* EOL? | ||
| quad: subject predicate object graph "." | ||
| ?subject: iriref | ||
| | BLANK_NODE_LABEL -> blank_node_label | ||
| ?predicate: iriref | ||
| ?object: iriref | ||
| | BLANK_NODE_LABEL -> blank_node_label | ||
| | literal | ||
| ?graph: iriref | ||
| literal: STRING_LITERAL_QUOTE ("^^" iriref | LANGTAG)? | ||
| LANGTAG: "@" /[a-zA-Z]/+ ("-" /[a-zA-Z0_9]/+)* | ||
| EOL: /[\r\n]/+ | ||
| iriref: "<" (/[^\x00-\x20<>"{}|^`\\]/ | UCHAR)* ">" | ||
| STRING_LITERAL_QUOTE: "\"" (/[^\x22\\\x0A\x0D]/ | ECHAR | UCHAR)* "\"" | ||
| BLANK_NODE_LABEL: "_:" (PN_CHARS_U | "0".."9") ((PN_CHARS | ".")* PN_CHARS)? | ||
| UCHAR: "\\u" HEX~4 | "\\U" HEX~8 | ||
| ECHAR: "\\" /[tbnrf"'\\]/ | ||
| PN_CHARS_BASE: /[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\u10000-\uEFFFF]/ | ||
| PN_CHARS_U: PN_CHARS_BASE | "_" | ":" | ||
| PN_CHARS: PN_CHARS_U | /[\-0-9\u00B7\u0300-\u036F\u203F-\u2040]/ | ||
| HEX: /[0-9A-Fa-f]/ | ||
| %ignore /[ \t]/+ | ||
| """ | ||
| class NTriplesTransformer(BaseParser, Transformer): | ||
| """Transform the tokenized ntriples into RDF primitives. | ||
| """ | ||
| def blank_node_label(self, children): | ||
| bn_label, = children | ||
| return self.make_blank_node(bn_label.value) | ||
| def iriref(self, children): | ||
| iri = ''.join(children) | ||
| iri = decode_literal(iri) | ||
| return self.make_named_node(iri) | ||
| def literal(self, children): | ||
| quoted_literal = children[0] | ||
| quoted_literal = quoted_literal[1:-1] # Remove ""s | ||
| literal = decode_literal(quoted_literal) | ||
| if len(children) == 2 and isinstance(children[1], NamedNode): | ||
| type_ = children[1] | ||
| return self.make_datatype_literal(literal, type_) | ||
| elif len(children) == 2 and children[1].type == 'LANGTAG': | ||
| lang = children[1][1:] # Remove @ | ||
| return self.make_language_literal(literal, lang) | ||
| else: | ||
| return self.make_language_literal(literal) | ||
| def triple(self, children): | ||
| subject, predicate, object_ = children | ||
| return self.make_triple(subject, predicate, object_) | ||
| def triples_start(self, children): | ||
| for child in children: | ||
| if isinstance(child, Triple): | ||
| yield child | ||
| nt_lark = Lark( | ||
| grammar, start='triples_start', parser='lalr', | ||
| transformer=NTriplesTransformer(), | ||
| ) | ||
| #! A fully-instantiated ntriples parser | ||
| ntriples_parser = LarkParser(nt_lark) |
| """Parse RDF serialized as turtle files. | ||
| Usage:: | ||
| from pymantic.parsers.lark import turtle_parser | ||
| graph = turtle_parser.parse(io.open('a_file.ttl', mode='rt')) | ||
| graph2 = turtle_parser.parse(\"\"\"@prefix p: <http://a.example/s>. | ||
| p: <http://a.example/p> <http://a.example/o> .\"\"\") | ||
| Unlike :mod:`pymantic.parsers.lark.ntriples`, this parser cannot efficiently | ||
| parse turtle line by line. If a file-like object is provided, the entire file | ||
| will be read into memory and parsed there. | ||
| """ | ||
| from __future__ import unicode_literals | ||
| import re | ||
| from lark import ( | ||
| Lark, | ||
| Transformer, | ||
| Tree, | ||
| ) | ||
| from lark.lexer import ( | ||
| Token, | ||
| ) | ||
| from pymantic.compat import ( | ||
| binary_type, | ||
| ) | ||
| from pymantic.parsers.base import ( | ||
| BaseParser, | ||
| ) | ||
| from pymantic.primitives import ( | ||
| BlankNode, | ||
| Literal, | ||
| NamedNode, | ||
| Triple, | ||
| ) | ||
| from pymantic.util import ( | ||
| grouper, | ||
| smart_urljoin, | ||
| decode_literal, | ||
| ) | ||
| RDF_TYPE = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#type") | ||
| RDF_NIL = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#nil") | ||
| RDF_FIRST = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#first") | ||
| RDF_REST = NamedNode("http://www.w3.org/1999/02/22-rdf-syntax-ns#rest") | ||
| XSD_DECIMAL = NamedNode("http://www.w3.org/2001/XMLSchema#decimal") | ||
| XSD_DOUBLE = NamedNode("http://www.w3.org/2001/XMLSchema#double") | ||
| XSD_INTEGER = NamedNode("http://www.w3.org/2001/XMLSchema#integer") | ||
| XSD_BOOLEAN = NamedNode("http://www.w3.org/2001/XMLSchema#boolean") | ||
| XSD_STRING = NamedNode("http://www.w3.org/2001/XMLSchema#string") | ||
| grammar = r"""turtle_doc: statement* | ||
| ?statement: directive | triples "." | ||
| directive: prefix_id | base | sparql_prefix | sparql_base | ||
| prefix_id: "@prefix" PNAME_NS IRIREF "." | ||
| base: BASE_DIRECTIVE IRIREF "." | ||
| sparql_base: /BASE/i IRIREF | ||
| sparql_prefix: /PREFIX/i PNAME_NS IRIREF | ||
| triples: subject predicate_object_list | ||
| | blank_node_property_list predicate_object_list? | ||
| predicate_object_list: verb object_list (";" (verb object_list)?)* | ||
| ?object_list: object ("," object)* | ||
| ?verb: predicate | /a/ | ||
| ?subject: iri | blank_node | collection | ||
| ?predicate: iri | ||
| ?object: iri | blank_node | collection | blank_node_property_list | literal | ||
| ?literal: rdf_literal | numeric_literal | boolean_literal | ||
| blank_node_property_list: "[" predicate_object_list "]" | ||
| collection: "(" object* ")" | ||
| numeric_literal: INTEGER | DECIMAL | DOUBLE | ||
| rdf_literal: string (LANGTAG | "^^" iri)? | ||
| boolean_literal: /true|false/ | ||
| string: STRING_LITERAL_QUOTE | ||
| | STRING_LITERAL_SINGLE_QUOTE | ||
| | STRING_LITERAL_LONG_SINGLE_QUOTE | ||
| | STRING_LITERAL_LONG_QUOTE | ||
| iri: IRIREF | prefixed_name | ||
| prefixed_name: PNAME_LN | PNAME_NS | ||
| blank_node: BLANK_NODE_LABEL | ANON | ||
| BASE_DIRECTIVE: "@base" | ||
| IRIREF: "<" (/[^\x00-\x20<>"{}|^`\\]/ | UCHAR)* ">" | ||
| PNAME_NS: PN_PREFIX? ":" | ||
| PNAME_LN: PNAME_NS PN_LOCAL | ||
| BLANK_NODE_LABEL: "_:" (PN_CHARS_U | /[0-9]/) ((PN_CHARS | ".")* PN_CHARS)? | ||
| LANGTAG: "@" /[a-zA-Z]+/ ("-" /[a-zA-Z0-9]+/)* | ||
| INTEGER: /[+-]?[0-9]+/ | ||
| DECIMAL: /[+-]?[0-9]*/ "." /[0-9]+/ | ||
| DOUBLE: /[+-]?/ (/[0-9]+/ "." /[0-9]*/ EXPONENT | ||
| | "." /[0-9]+/ EXPONENT | /[0-9]+/ EXPONENT) | ||
| EXPONENT: /[eE][+-]?[0-9]+/ | ||
| STRING_LITERAL_QUOTE: "\"" (/[^\x22\\\x0A\x0D]/ | ECHAR | UCHAR)* "\"" | ||
| STRING_LITERAL_SINGLE_QUOTE: "'" (/[^\x27\\\x0A\x0D]/ | ECHAR | UCHAR)* "'" | ||
| STRING_LITERAL_LONG_SINGLE_QUOTE: "'''" (/'|''/? (/[^'\\]/ | ECHAR | UCHAR))* "'''" | ||
| STRING_LITERAL_LONG_QUOTE: "\"\"\"" (/"|""/? (/[^"\\]/ | ECHAR | UCHAR))* "\"\"\"" | ||
| UCHAR: "\\u" HEX~4 | "\\U" HEX~8 | ||
| ECHAR: "\\" /[tbnrf"'\\]/ | ||
| WS: /[\x20\x09\x0D\x0A]/ | ||
| ANON: "[" WS* "]" | ||
| PN_CHARS_BASE: /[A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\U00010000-\U000EFFFF]/ | ||
| PN_CHARS_U: PN_CHARS_BASE | "_" | ||
| PN_CHARS: PN_CHARS_U | /[\-0-9\u00B7\u0300-\u036F\u203F-\u2040]/ | ||
| PN_PREFIX: PN_CHARS_BASE ((PN_CHARS | ".")* PN_CHARS)? | ||
| PN_LOCAL: (PN_CHARS_U | ":" | /[0-9]/ | PLX) ((PN_CHARS | "." | ":" | PLX)* (PN_CHARS | ":" | PLX))? | ||
| PLX: PERCENT | PN_LOCAL_ESC | ||
| PERCENT: "%" HEX~2 | ||
| HEX: /[0-9A-Fa-f]/ | ||
| PN_LOCAL_ESC: "\\" /[_~\.\-!$&'()*+,;=\/?#@%]/ | ||
| %ignore WS | ||
| COMMENT: "#" /[^\n]/* | ||
| %ignore COMMENT | ||
| """ | ||
| turtle_lark = Lark(grammar, start="turtle_doc", parser="lalr") | ||
| LEGAL_IRI = re.compile(r'^[^\x00-\x20<>"{}|^`\\]*$') | ||
| def validate_iri(iri): | ||
| if not LEGAL_IRI.match(iri): | ||
| raise ValueError("Illegal characters in IRI: " + iri) | ||
| return iri | ||
| def unpack_predicate_object_list(subject, pol): | ||
| if not isinstance(subject, (NamedNode, BlankNode)): | ||
| for triple_or_node in subject: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| else: | ||
| subject = triple_or_node | ||
| break | ||
| for predicate, object_ in grouper(pol, 2): | ||
| if isinstance(predicate, Token): | ||
| if predicate.value != "a": | ||
| raise ValueError(predicate) | ||
| predicate = RDF_TYPE | ||
| if not isinstance(object_, (NamedNode, Literal, BlankNode)): | ||
| if isinstance(object_, Tree): | ||
| object_ = object_.children | ||
| for triple_or_node in object_: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| else: | ||
| object_ = triple_or_node | ||
| yield Triple(subject, predicate, object_) | ||
| else: | ||
| yield Triple(subject, predicate, object_) | ||
| class TurtleTransformer(BaseParser, Transformer): | ||
| def __init__(self, base_iri=""): | ||
| super().__init__() | ||
| self.base_iri = base_iri | ||
| self.prefixes = self.profile.prefixes | ||
| def decode_iriref(self, iriref): | ||
| return validate_iri(decode_literal(iriref[1:-1])) | ||
| def iri(self, children): | ||
| (iriref_or_pname,) = children | ||
| if iriref_or_pname.startswith("<"): | ||
| return self.make_named_node( | ||
| smart_urljoin(self.base_iri, self.decode_iriref(iriref_or_pname)) | ||
| ) | ||
| return iriref_or_pname | ||
| def predicate_object_list(self, children): | ||
| return children | ||
| def triples(self, children): | ||
| if len(children) == 2: | ||
| subject = children[0] | ||
| for triple in unpack_predicate_object_list(subject, children[1]): | ||
| yield triple | ||
| elif len(children) == 1: | ||
| for triple_or_node in children[0]: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| def prefixed_name(self, children): | ||
| (pname,) = children | ||
| ns, _, ln = pname.partition(":") | ||
| return self.make_named_node(self.prefixes[ns] + decode_literal(ln)) | ||
| def prefix_id(self, children): | ||
| ns, iriref = children | ||
| iri = smart_urljoin(self.base_iri, self.decode_iriref(iriref)) | ||
| ns = ns[:-1] # Drop trailing : from namespace | ||
| self.prefixes[ns] = iri | ||
| return [] | ||
| def sparql_prefix(self, children): | ||
| return self.prefix_id(children[1:]) | ||
| def base(self, children): | ||
| base_directive, base_iriref = children | ||
| # Workaround for lalr parser token ambiguity in python 2.7 | ||
| if base_directive.startswith("@") and base_directive != "@base": | ||
| raise ValueError("Unexpected @base: " + base_directive) | ||
| self.base_iri = smart_urljoin(self.base_iri, self.decode_iriref(base_iriref)) | ||
| return [] | ||
| def sparql_base(self, children): | ||
| return self.base(children) | ||
| def blank_node(self, children): | ||
| (bn,) = children | ||
| if bn.type == "ANON": | ||
| return self.make_blank_node() | ||
| elif bn.type == "BLANK_NODE_LABEL": | ||
| return self.make_blank_node(bn.value) | ||
| else: | ||
| raise NotImplementedError() | ||
| def blank_node_property_list(self, children): | ||
| pl_root = self.make_blank_node() | ||
| for pl_item in unpack_predicate_object_list(pl_root, children[0]): | ||
| yield pl_item | ||
| yield pl_root | ||
| def collection(self, children): | ||
| prev_node = RDF_NIL | ||
| for value in reversed(children): | ||
| this_bn = self.make_blank_node() | ||
| if not isinstance(value, (NamedNode, Literal, BlankNode)): | ||
| for triple_or_node in value: | ||
| if isinstance(triple_or_node, Triple): | ||
| yield triple_or_node | ||
| else: | ||
| value = triple_or_node | ||
| break | ||
| yield self.make_triple(this_bn, RDF_FIRST, value) | ||
| yield self.make_triple(this_bn, RDF_REST, prev_node) | ||
| prev_node = this_bn | ||
| yield prev_node | ||
| def numeric_literal(self, children): | ||
| (numeric,) = children | ||
| if numeric.type == "DECIMAL": | ||
| return self.make_datatype_literal(numeric, datatype=XSD_DECIMAL) | ||
| elif numeric.type == "DOUBLE": | ||
| return self.make_datatype_literal(numeric, datatype=XSD_DOUBLE) | ||
| elif numeric.type == "INTEGER": | ||
| return self.make_datatype_literal(numeric, datatype=XSD_INTEGER) | ||
| else: | ||
| raise NotImplementedError() | ||
| def rdf_literal(self, children): | ||
| literal_string = children[0] | ||
| lang = None | ||
| type_ = None | ||
| if len(children) == 2 and isinstance(children[1], NamedNode): | ||
| type_ = children[1] | ||
| return self.make_datatype_literal(literal_string, type_) | ||
| elif len(children) == 2 and children[1].type == "LANGTAG": | ||
| lang = children[1][1:] # Remove @ | ||
| return self.make_language_literal(literal_string, lang) | ||
| else: | ||
| return self.make_datatype_literal(literal_string, datatype=XSD_STRING) | ||
| def boolean_literal(self, children): | ||
| (boolean,) = children | ||
| return self.make_datatype_literal(boolean, datatype=XSD_BOOLEAN) | ||
| def string(self, children): | ||
| (literal,) = children | ||
| if literal.type in ( | ||
| "STRING_LITERAL_QUOTE", | ||
| "STRING_LITERAL_SINGLE_QUOTE", | ||
| ): | ||
| string = decode_literal(literal[1:-1]) | ||
| if literal.type in ( | ||
| "STRING_LITERAL_LONG_SINGLE_QUOTE", | ||
| "STRING_LITERAL_LONG_QUOTE", | ||
| ): | ||
| string = decode_literal(literal[3:-3]) | ||
| return string | ||
| def turtle_doc(self, children): | ||
| for child in children: | ||
| if not isinstance(child, Tree): | ||
| for triple in child: | ||
| yield triple | ||
| def parse(string_or_stream, graph=None, base=""): | ||
| if hasattr(string_or_stream, "readline"): | ||
| string = string_or_stream.read() | ||
| else: | ||
| # Presume string. | ||
| string = string_or_stream | ||
| if isinstance(string_or_stream, binary_type): | ||
| string = string_or_stream.decode("utf-8") | ||
| else: | ||
| string = string_or_stream | ||
| tree = turtle_lark.parse(string) | ||
| tr = TurtleTransformer(base_iri=base) | ||
| if graph is None: | ||
| graph = tr._make_graph() | ||
| tr._prepare_parse(graph) | ||
| graph.addAll(tr.transform(tree)) | ||
| return graph | ||
| def parse_string(string_or_bytes, graph=None, base=""): | ||
| return parse(string_or_bytes, graph, base) |
| import re | ||
| from threading import local | ||
| from urllib.parse import ( | ||
| urljoin, | ||
| ) | ||
| from lxml import etree | ||
| from pymantic.primitives import ( | ||
| BlankNode, | ||
| NamedNode, | ||
| ) | ||
| scheme_re = re.compile(r'[a-zA-Z](?:[a-zA-Z0-9]|\+|-|\.)*') | ||
| class RDFXMLParser(object): | ||
| RDF_TYPE = 'http://www.w3.org/1999/02/22-rdf-syntax-ns#type' | ||
| def __init__(self): | ||
| self.namespaces = { | ||
| 'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', | ||
| } | ||
| self._call_state = local() | ||
| def clark(self, prefix, tag): | ||
| return '{%s}%s' % (self.namespaces[prefix], tag) | ||
| def parse(self, f, sink=None): | ||
| self._call_state.bnodes = {} | ||
| tree = etree.parse(f) | ||
| if tree.getroot() != self.clark('rdf', 'RDF'): | ||
| raise ValueError('Invalid XML document.') | ||
| for element in tree.getroot(): | ||
| self._handle_resource(element, sink) | ||
| def _handle_resource(self, element, sink): | ||
| from pymantic.primitives import NamedNode, Triple | ||
| subject = self._determine_subject(element) | ||
| if element.tag != self.clark('rdf', 'Description'): | ||
| resource_class = self._resolve_tag(element) | ||
| sink.add(Triple(subject, NamedNode(self.RDF_TYPE), resource_class)) | ||
| for property_element in element: | ||
| if property_element.tag == self.clark('rdf', 'li'): | ||
| pass | ||
| else: | ||
| predicate = self._resolve_tag(property_element) | ||
| if self.clark('rdf', 'resource') in property_element.attrib: | ||
| object_ = self._resolve_uri( | ||
| property_element, property_element.attrib[self.clark( | ||
| 'rdf', 'resource')]) | ||
| sink.add( | ||
| Triple(subject, NamedNode(predicate), NamedNode(object_))) | ||
| return subject | ||
| def _resolve_tag(self, element): | ||
| if element.tag[0] == '{': | ||
| tag_bits = element[1:].partition('}') | ||
| return NamedNode(tag_bits[0] + tag_bits[2]) | ||
| else: | ||
| return NamedNode(urljoin(element.base, element.tag)) | ||
| def _determine_subject(self, element): | ||
| if self.clark('rdf', 'about') not in element.attrib and\ | ||
| self.clark('rdf', 'nodeID') not in element.attrib and\ | ||
| self.clark('rdf', 'ID') not in element.attrib: | ||
| return BlankNode() | ||
| elif self.clark('rdf', 'nodeID') in element.attrib: | ||
| node_id = element.attrib[self.clark('rdf', 'nodeID')] | ||
| if node_id not in self._call_state.bnodes: | ||
| self._call_state.bnodes[node_id] = BlankNode() | ||
| return self._call_state.bnodes[node_id] | ||
| elif self.clark('rdf', 'ID') in element.attrib: | ||
| if not element.base: | ||
| raise ValueError('No XML base for %r', element) | ||
| return NamedNode(element.base + '#' + | ||
| element.attrib[self.clark('rdf', 'ID')]) | ||
| elif self.clark('rdf', 'about') in element.attrib: | ||
| return self._resolve_uri(element, element.attrib[ | ||
| self.clark('rdf', 'resource')]) | ||
| def _resolve_uri(self, element, uri): | ||
| if not scheme_re.match(uri): | ||
| return NamedNode(urljoin(element.base, uri)) | ||
| else: | ||
| return NamedNode(uri) |
| __all__ = ['Triple', 'Quad', 'q_as_t', 't_as_q', 'Literal', 'NamedNode', | ||
| 'Prefix', 'BlankNode', 'Graph', 'Dataset', 'PrefixMap', 'TermMap', | ||
| 'parse_curie', 'is_language', 'lang_match', 'to_curie', 'Profile', | ||
| ] | ||
| import collections | ||
| from collections import defaultdict | ||
| import datetime | ||
| from operator import itemgetter | ||
| from .compat import ( | ||
| text_type, | ||
| string_types, | ||
| iteritems, | ||
| itervalues, | ||
| iterkeys, | ||
| ) | ||
| import pymantic.uri_schemes as uri_schemes | ||
| from pymantic.util import quote_normalized_iri | ||
| from pymantic.serializers import nt_escape | ||
| def is_language(lang): | ||
| """Is something a valid XML language?""" | ||
| if isinstance(lang, NamedNode): | ||
| return False | ||
| return True | ||
| def lang_match(lang1, lang2): | ||
| """Determines if two languages are, in fact, the same language. | ||
| Eg: en is the same as en-us and en-uk.""" | ||
| if lang1 is None and lang2 is None: | ||
| return True | ||
| elif lang1 is None or lang2 is None: | ||
| return False | ||
| lang1 = lang1.partition('-') | ||
| lang2 = lang2.partition('-') | ||
| return lang1[0] == lang2[0] and (lang1[2] == '' or lang2[2] == '' or | ||
| lang1[2] == lang2[2]) | ||
| def parse_curie(curie, prefixes): | ||
| """ | ||
| Parses a CURIE within the context of the given namespaces. Will also accept | ||
| explicit URIs and wrap them in an rdflib URIRef. | ||
| Specifically: | ||
| 1) If the CURIE is not of the form [stuff] and the prefix is in the list of | ||
| standard URIs, it is wrapped in a URIRef and returned unchanged. | ||
| 2) Otherwise, the CURIE is parsed by the rules of CURIE Syntax 1.0: | ||
| http://www.w3.org/TR/2007/WD-curie-20070307/ The default namespace is | ||
| the namespace keyed by the empty string in the namespaces dictionary. | ||
| 3) If the CURIE's namespace cannot be resolved, a ValueError is raised. | ||
| """ | ||
| definitely_curie = False | ||
| if curie[0] == '[' and curie[-1] == ']': | ||
| curie = curie[1:-1] | ||
| definitely_curie = True | ||
| prefix, sep, reference = curie.partition(':') | ||
| if not definitely_curie: | ||
| if prefix in uri_schemes.schemes: | ||
| return NamedNode(curie) | ||
| if not reference and '' in prefixes: | ||
| reference = prefix | ||
| return Prefix(prefixes[''])(reference) | ||
| if prefix in prefixes: | ||
| return Prefix(prefixes[prefix])(reference) | ||
| else: | ||
| raise ValueError( | ||
| 'Could not parse CURIE prefix {} from prefixes {}'.format( | ||
| prefix, prefixes)) | ||
| def parse_curies(curies, namespaces): | ||
| """Parse multiple CURIEs at once.""" | ||
| for curie in curies: | ||
| yield parse_curie(curie, namespaces) | ||
| def to_curie(uri, namespaces, seperator=":", explicit=False): | ||
| """Converts a URI to a CURIE using the prefixes defined in namespaces. If | ||
| there is no matching prefix, return the URI unchanged. | ||
| namespaces - a dictionary of prefix -> namespace mappings. | ||
| separator - the character to use as the separator between the prefix and | ||
| the local name. | ||
| explicit - if True and the URI can be abbreviated, wrap the abbreviated | ||
| form in []s to indicate that it is definitely a CURIE.""" | ||
| matches = [] | ||
| for prefix, namespace in namespaces.items(): | ||
| if uri.startswith(namespace): | ||
| matches.append((prefix, namespace)) | ||
| if len(matches) > 0: | ||
| prefix, namespace = sorted(matches, key=lambda pair: -len(pair[1]))[0] | ||
| if explicit: | ||
| return '[' + uri.replace(namespace, prefix + seperator) + ']' | ||
| else: | ||
| return uri.replace(namespace, prefix + seperator) | ||
| return uri | ||
| class Triple(tuple): | ||
| """Triple(subject, predicate, object) | ||
| The Triple interface represents an RDF Triple. The stringification of a | ||
| Triple results in an N-Triples. | ||
| """ | ||
| __slots__ = () | ||
| _fields = ('subject', 'predicate', 'object') | ||
| def __new__(_cls, subject, predicate, object): | ||
| return tuple.__new__(_cls, (subject, predicate, object)) | ||
| @classmethod | ||
| def _make(cls, iterable, new=tuple.__new__, len=len): | ||
| 'Make a new Triple object from a sequence or iterable' | ||
| result = new(cls, iterable) | ||
| if len(result) != 3: | ||
| raise TypeError('Expected 3 arguments, got %d' % len(result)) | ||
| return result | ||
| def __repr__(self): | ||
| return 'Triple(subject=%r, predicate=%r, object=%r)' % self | ||
| def _asdict(t): | ||
| 'Return a new dict which maps field names to their values' | ||
| return {'subject': t[0], 'predicate': t[1], 'object': t[2]} | ||
| def _replace(_self, **kwds): | ||
| 'Return a new Triple object replacing specified fields with new values' | ||
| result = _self._make(map(kwds.pop, ('subject', 'predicate', 'object'), | ||
| _self)) | ||
| if kwds: | ||
| raise ValueError('Got unexpected field names: %r' % kwds.keys()) | ||
| return result | ||
| def __getnewargs__(self): | ||
| return tuple(self) | ||
| subject = property(itemgetter(0)) | ||
| predicate = property(itemgetter(1)) | ||
| object = property(itemgetter(2)) | ||
| def __str__(self): | ||
| return self.subject.toNT() + ' ' + self.predicate.toNT() + ' ' + \ | ||
| self.object.toNT() + ' .\n' | ||
| def toString(self): | ||
| return str(self) | ||
| class Quad(tuple): | ||
| 'Quad(subject, predicate, object, graph)' | ||
| __slots__ = () | ||
| _fields = ('subject', 'predicate', 'object', 'graph') | ||
| def __new__(_cls, subject, predicate, object, graph): | ||
| return tuple.__new__(_cls, (subject, predicate, object, graph)) | ||
| @classmethod | ||
| def _make(cls, iterable, new=tuple.__new__, len=len): | ||
| 'Make a new Quad object from a sequence or iterable' | ||
| result = new(cls, iterable) | ||
| if len(result) != 4: | ||
| raise TypeError('Expected 4 arguments, got %d' % len(result)) | ||
| return result | ||
| def __repr__(self): | ||
| return 'Quad(subject=%r, predicate=%r, object=%r, graph=%r)' % self | ||
| def _asdict(t): | ||
| 'Return a new dict which maps field names to their values' | ||
| return {'subject': t[0], 'predicate': t[1], 'object': t[2], | ||
| 'graph': t[3], } | ||
| def _replace(_self, **kwds): | ||
| 'Return a new Quad object replacing specified fields with new values' | ||
| result = _self._make(map(kwds.pop, ('subject', 'predicate', 'object', | ||
| 'graph'), _self)) | ||
| if kwds: | ||
| raise ValueError('Got unexpected field names: %r' % kwds.keys()) | ||
| return result | ||
| def __getnewargs__(self): | ||
| return tuple(self) | ||
| subject = property(itemgetter(0)) | ||
| predicate = property(itemgetter(1)) | ||
| object = property(itemgetter(2)) | ||
| graph = property(itemgetter(3)) | ||
| def __str__(self): | ||
| return str(self.subject) + ' ' + str(self.predicate) + ' ' + \ | ||
| str(self.object) + ' ' + str(self.graph) + ' .\n' | ||
| def q_as_t(quad): | ||
| return Triple(quad.subject, quad.predicate, quad.object) | ||
| def t_as_q(graph_name, triple): | ||
| return Quad(triple.subject, triple.predicate, triple.object, graph_name) | ||
| class Literal(tuple): | ||
| """Literal(`value`, `language`, `datatype`) | ||
| Literals represent values such as numbers, dates and strings in RDF data. A | ||
| Literal is comprised of three attributes: | ||
| * a lexical representation of the nominalValue | ||
| * an optional language represented by a string token | ||
| * an optional datatype specified by a NamedNode | ||
| Literals representing plain text in a natural language may have a language | ||
| attribute specified by a text string token, as specified in [BCP47], | ||
| normalized to lowercase (e.g., 'en', 'fr', 'en-gb'). | ||
| Literals may not have both a datatype and a language.""" | ||
| __slots__ = () | ||
| _fields = ('value', 'language', 'datatype') | ||
| types = { | ||
| int: lambda v: (str(v), XSD('integer')), | ||
| datetime.datetime: lambda v: (v.isoformat(), XSD('dateTime')) | ||
| } | ||
| def __new__(_cls, value, language=None, datatype=None): | ||
| if not isinstance(value, string_types): | ||
| value, auto_datatype = _cls.types[type(value)](value) | ||
| if datatype is None: | ||
| datatype = auto_datatype | ||
| return tuple.__new__(_cls, (value, language, datatype)) | ||
| @classmethod | ||
| def _make(cls, iterable, new=tuple.__new__, len=len): | ||
| 'Make a new Literal object from a sequence or iterable' | ||
| result = new(cls, iterable) | ||
| if len(result) != 3: | ||
| raise TypeError('Expected 3 arguments, got %d' % len(result)) | ||
| return result | ||
| def __repr__(self): | ||
| return 'Literal(value=%r, language=%r, datatype=%r)' % self | ||
| def _asdict(t): | ||
| 'Return a new dict which maps field names to their values' | ||
| return {'value': t[0], 'language': t[1], 'datatype': t[2]} | ||
| def _replace(_self, **kwds): | ||
| 'Return a new Literal object replacing specified fields with new value' | ||
| result = _self._make(map(kwds.pop, ('value', 'language', 'datatype'), | ||
| _self)) | ||
| if kwds: | ||
| raise ValueError('Got unexpected field names: %r' % kwds.keys()) | ||
| return result | ||
| def __getnewargs__(self): | ||
| return tuple(self) | ||
| value = property(itemgetter(0)) | ||
| language = property(itemgetter(1)) | ||
| datatype = property(itemgetter(2)) | ||
| interfaceName = "Literal" | ||
| def __str__(self): | ||
| return text_type(self.value) | ||
| def toNT(self): | ||
| quoted = '"' + nt_escape(self.value) + '"' | ||
| if self.language: | ||
| return quoted + '@' + self.language | ||
| elif self.datatype: | ||
| return quoted + '^^' + self.datatype.toNT() | ||
| else: | ||
| return quoted | ||
| class NamedNode(text_type): | ||
| """A node identified by an IRI.""" | ||
| interfaceName = "NamedNode" | ||
| @property | ||
| def value(self): | ||
| return self | ||
| def __repr__(self): | ||
| return 'NamedNode(' + self.toNT() + ')' | ||
| def __str__(self): | ||
| return self.value | ||
| def toNT(self): | ||
| return '<' + nt_escape(quote_normalized_iri(self.value)) + '>' | ||
| class Prefix(NamedNode): | ||
| """Node that when called returns the the argument conctantated with | ||
| self.""" | ||
| def __call__(self, name): | ||
| return NamedNode(self + name) | ||
| XSD = Prefix("http://www.w3.org/2001/XMLSchema#") | ||
| class BlankNode(object): | ||
| """A BlankNode is a reference to an unnamed resource (one for which an IRI | ||
| is not known), and may be used in a Triple as a unique reference to that | ||
| unnamed resource. | ||
| BlankNodes are stringified by prepending "_:" to a unique value, for | ||
| instance _:b142 or _:me, this stringified form is referred to as a | ||
| "blank node identifier".""" | ||
| interfaceName = "BlankNode" | ||
| @property | ||
| def value(self): | ||
| return ''.join(chr(ord(c) + 17) for c in hex(id(self))[2:]) | ||
| def __repr__(self): | ||
| return 'BlankNode()' | ||
| def __str__(self): | ||
| return '_:' + self.value | ||
| def toNT(self): | ||
| return str(self) | ||
| def Index(): | ||
| return defaultdict(Index) | ||
| class Graph(object): | ||
| """A `Graph` holds a set of one or more `Triple`. Implements the Python | ||
| set/sequence API for `in`, `for`, and `len`""" | ||
| def __init__(self, graph_uri=None): | ||
| if not isinstance(graph_uri, NamedNode): | ||
| graph_uri = NamedNode(graph_uri) | ||
| self._uri = graph_uri | ||
| self._triples = set() | ||
| self._spo = Index() | ||
| self._pos = Index() | ||
| self._osp = Index() | ||
| self._actions = set() | ||
| @property | ||
| def uri(self): | ||
| """URI name of the graph, if it has been given a name""" | ||
| return self._uri | ||
| def addAction(self, action): | ||
| self._actions.add(action) | ||
| return self | ||
| def add(self, triple): | ||
| """Adds the specified Triple to the graph. This method returns the | ||
| graph instance it was called on.""" | ||
| self._triples.add(triple) | ||
| self._spo[triple.subject][triple.predicate][triple.object] = triple | ||
| self._pos[triple.predicate][triple.object][triple.subject] = triple | ||
| self._osp[triple.object][triple.subject][triple.predicate] = triple | ||
| return self | ||
| def remove(self, triple): | ||
| """Removes the specified Triple from the graph. This method returns the | ||
| graph instance it was called on.""" | ||
| self._triples.remove(triple) | ||
| del self._spo[triple.subject][triple.predicate][triple.object] | ||
| del self._pos[triple.predicate][triple.object][triple.subject] | ||
| del self._osp[triple.object][triple.subject][triple.predicate] | ||
| return self | ||
| def match(self, subject=None, predicate=None, object=None): | ||
| """This method returns a new sequence of triples which is comprised of | ||
| all those triples in the current instance which match the given | ||
| arguments, that is, for each triple in this graph, it is included in | ||
| the output graph, if: | ||
| * calling triple.subject.equals with the specified subject as an | ||
| argument returns true, or the subject argument is null, AND | ||
| * calling triple.property.equals with the specified property as an | ||
| argument returns true, or the property argument is null, AND | ||
| * calling triple.object.equals with the specified object as an argument | ||
| returns true, or the object argument is null | ||
| This method implements AND functionality, so only triples matching all | ||
| of the given non-null arguments will be included in the result. | ||
| """ | ||
| if subject: | ||
| if predicate: # s, p, ??? | ||
| if object: # s, p, o | ||
| if Triple(subject, predicate, object) in self: | ||
| yield Triple(subject, predicate, object) | ||
| else: # s, p, ?var | ||
| if subject in self._spo and predicate in self._spo[subject]: | ||
| for triple in itervalues(self._spo[subject][predicate]): | ||
| yield triple | ||
| else: # s, ?var, ??? | ||
| if object: # s, ?var, o | ||
| if object in self._osp and subject in self._osp[object]: | ||
| for triple in itervalues(self._osp[object][subject]): | ||
| yield triple | ||
| else: # s, ?var, ?var | ||
| if subject in self._spo: | ||
| for predicate in self._spo[subject]: | ||
| for triple in \ | ||
| itervalues(self._spo[subject][predicate]): | ||
| yield triple | ||
| elif predicate: # ?var, p, ??? | ||
| if object: # ?var, p, o | ||
| if predicate in self._pos and object in self._pos[predicate]: | ||
| for triple in itervalues(self._pos[predicate][object]): | ||
| yield triple | ||
| else: # ?var, p, ?var | ||
| if predicate in self._pos: | ||
| for object in self._pos[predicate]: | ||
| for triple in itervalues(self._pos[predicate][object]): | ||
| yield triple | ||
| elif object: # ?var, ?var, o | ||
| if object in self._osp: | ||
| for subject in self._osp[object]: | ||
| for triple in itervalues(self._osp[object][subject]): | ||
| yield triple | ||
| else: | ||
| for triple in self._triples: | ||
| yield triple | ||
| def removeMatches(self, subject, predicate, object): | ||
| """This method removes those triples in the current graph which match | ||
| the given arguments.""" | ||
| for triple in self.match(subject, predicate, object): | ||
| self.remove(triple) | ||
| return self | ||
| def addAll(self, graph_or_triples): | ||
| """Imports the graph or set of triples in to this graph. This method | ||
| returns the graph instance it was called on.""" | ||
| for triple in graph_or_triples: | ||
| self.add(triple) | ||
| return self | ||
| def merge(self, graph): | ||
| """Returns a new Graph which is a concatenation of this graph and the | ||
| graph given as an argument.""" | ||
| new_graph = Graph() | ||
| for triple in graph: | ||
| new_graph.add(triple) | ||
| for triple in self: | ||
| new_graph.add(triple) | ||
| return new_graph | ||
| def __contains__(self, item): | ||
| return item in self._triples | ||
| def __len__(self): | ||
| return len(self._triples) | ||
| def __iter__(self): | ||
| return iter(self._triples) | ||
| def toArray(self): | ||
| """Return the set of :py:class:`Triple` within the :py:class:`Graph`""" | ||
| return frozenset(self._triples) | ||
| def subjects(self): | ||
| """Returns an iterator over subjects in the graph.""" | ||
| return iterkeys(self._spo) | ||
| def predicates(self): | ||
| """Returns an iterator over predicates in the graph.""" | ||
| return iterkeys(self._pos) | ||
| def objects(self): | ||
| """Returns an iterator over objects in the graph.""" | ||
| return iterkeys(self._osp) | ||
| class Dataset(object): | ||
| def __init__(self): | ||
| self._graphs = defaultdict(Graph) | ||
| def add(self, quad): | ||
| self._graphs[quad.graph]._uri = quad.graph | ||
| self._graphs[quad.graph].add(q_as_t(quad)) | ||
| def remove(self, quad): | ||
| self._graphs[quad.graph].remove(q_as_t(quad)) | ||
| def add_graph(self, graph, named=None): | ||
| name = named or graph.uri | ||
| if name: | ||
| graph._uri = name | ||
| self._graphs[graph.uri] = graph | ||
| else: | ||
| raise ValueError("Graph must be named") | ||
| def remove_graph(self, graph_or_uri): | ||
| pass | ||
| @property | ||
| def graphs(self): | ||
| return self._graphs.values() | ||
| def match(self, subject=None, predicate=None, object=None, graph=None): | ||
| if graph: | ||
| matches = self._graphs[graph].match(subject, predicate, object) | ||
| for match in matches: | ||
| yield t_as_q(graph, match) | ||
| else: | ||
| for graph_uri, graph in iteritems(self._graphs): | ||
| for match in graph.match(subject, predicate, object): | ||
| yield t_as_q(graph_uri, match) | ||
| def removeMatches(self, subject=None, predicate=None, object=None, | ||
| graph=None): | ||
| """This method removes those triples in the current graph which match | ||
| the given arguments.""" | ||
| for quad in self.match(subject, predicate, object, graph): | ||
| self.remove(quad) | ||
| return self | ||
| def addAll(self, dataset_or_quads): | ||
| """Imports the graph or set of triples in to this graph. This method | ||
| returns the graph instance it was called on.""" | ||
| for quad in dataset_or_quads: | ||
| self.add(quad) | ||
| return self | ||
| def __len__(self): | ||
| return sum(len(g) for g in self.graphs) | ||
| def __contains__(self, item): | ||
| if hasattr(item, "graph"): | ||
| if item.graph in self._graphs: | ||
| graph = self._graphs[item.graph] | ||
| return q_as_t(item) in graph | ||
| else: | ||
| for graph in itervalues(self._graphs): | ||
| if item in graph: | ||
| return True | ||
| def __iter__(self): | ||
| for graph in itervalues(self._graphs): | ||
| for triple in graph: | ||
| yield t_as_q(graph.uri, triple) | ||
| def toArray(self): | ||
| return frozenset(self) | ||
| # RDF Enviroment Interfaces | ||
| class PrefixMap(collections.OrderedDict): | ||
| """A map of prefixes to IRIs, and provides methods to | ||
| turn one in to the other. | ||
| Example Usage: | ||
| >>> prefixes = PrefixMap() | ||
| Create a new prefix mapping for the prefix "rdfs" | ||
| >>> prefixes['rdfs'] = "http://www.w3.org/2000/01/rdf-schema#" | ||
| Resolve a known CURIE | ||
| >>> prefixes.resolve("rdfs:label") | ||
| u"http://www.w3.org/2000/01/rdf-schema#label" | ||
| Shrink an IRI for a known CURIE in to a CURIE | ||
| >>> prefixes.shrink("http://www.w3.org/2000/01/rdf-schema#label") | ||
| u"rdfs:label" | ||
| Attempt to resolve a CURIE with an empty prefix | ||
| >>> prefixes.resolve(":me") | ||
| ":me" | ||
| Set the default prefix and attempt to resolve a CURIE with an empty prefix | ||
| >>> prefixes.setDefault("http://example.org/bob#") | ||
| >>> prefixes.resolve(":me") | ||
| u"http://example.org/bob#me" | ||
| """ | ||
| def resolve(self, curie): | ||
| """Given a valid CURIE for which a prefix is known (for example | ||
| "rdfs:label"), this method will return the resulting IRI (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label")""" | ||
| return parse_curie(curie, self) | ||
| def shrink(self, iri): | ||
| """Given an IRI for which a prefix is known (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label") this method returns a | ||
| CURIE (for example "rdfs:label"), if no prefix is known the original | ||
| IRI is returned.""" | ||
| return to_curie(iri, self) | ||
| def addAll(self, other, override=False): | ||
| if override: | ||
| self.update(other) | ||
| else: | ||
| for key, value in iteritems(other): | ||
| if key not in self: | ||
| self[key] = value | ||
| return self | ||
| def setDefault(self, iri): | ||
| """Set the iri to be used when resolving CURIEs without a prefix, for | ||
| example ":this".""" | ||
| self[''] = iri | ||
| class TermMap(dict): | ||
| """A map of simple string terms to IRIs, and provides methods to turn one | ||
| in to the other. | ||
| Example usage: | ||
| >>> terms = TermMap() | ||
| Create a new term mapping for the term "member" | ||
| >>> terms['member'] = "http://www.w3.org/ns/org#member" | ||
| Resolve a known term to an IRI | ||
| >>> terms.resolve("member") | ||
| u"http://www.w3.org/ns/org#member" | ||
| Shrink an IRI for a known term to a term | ||
| >>> terms.shrink("http://www.w3.org/ns/org#member") | ||
| u"member" | ||
| Attempt to resolve an unknown term | ||
| >>> terms.resolve("label") | ||
| None | ||
| Set the default term vocabulary and then attempt to resolve an unknown term | ||
| >>> terms.setDefault("http://www.w3.org/2000/01/rdf-schema#") | ||
| >>> terms.resolve("label") | ||
| u"http://www.w3.org/2000/01/rdf-schema#label" | ||
| """ | ||
| def addAll(self, other, override=False): | ||
| if override: | ||
| self.update(other) | ||
| else: | ||
| for key, value in iteritems(other): | ||
| if key not in self: | ||
| self[key] = value | ||
| return self | ||
| def resolve(self, term): | ||
| """Given a valid term for which an IRI is known (for example "label"), | ||
| this method will return the resulting IRI (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label"). | ||
| If no term is known and a default has been set, the IRI is obtained by | ||
| concatenating the term and the default iri. | ||
| If no term is known and no default is set, then this method returns | ||
| null.""" | ||
| if hasattr(self, 'default'): | ||
| return self.get(term, self.default + term) | ||
| else: | ||
| return self.get(term) | ||
| def setDefault(self, iri): | ||
| """The default iri to be used when an term cannot be resolved, the | ||
| resulting IRI is obtained by concatenating this iri with the term being | ||
| resolved.""" | ||
| self.default = iri | ||
| def shrink(self, iri): | ||
| """Given an IRI for which an term is known (for example | ||
| "http://www.w3.org/2000/01/rdf-schema#label") this method returns a | ||
| term (for example "label"), if no term is known the original IRI is | ||
| returned.""" | ||
| for term, v in iteritems(self): | ||
| if v == iri: | ||
| return term | ||
| return iri | ||
| class Profile(object): | ||
| """Profiles provide an easy to use context for negotiating between CURIEs, | ||
| Terms and IRIs.""" | ||
| def __init__(self, prefixes=None, terms=None): | ||
| self.prefixes = prefixes or PrefixMap() | ||
| self.terms = terms or TermMap() | ||
| if 'rdf' not in self.prefixes: | ||
| self.prefixes['rdf'] = \ | ||
| 'http://www.w3.org/1999/02/22-rdf-syntax-ns#' | ||
| if 'xsd' not in self.prefixes: | ||
| self.prefixes['xsd'] = 'http://www.w3.org/2001/XMLSchema#' | ||
| def resolve(self, toresolve): | ||
| """Given an Term or CURIE this method will return an IRI, or null if it | ||
| cannot be resolved. | ||
| If toresolve contains a : (colon) then this method returns the result | ||
| of calling prefixes.resolve(toresolve) | ||
| otherwise this method returns the result of calling | ||
| terms.resolve(toresolve)""" | ||
| if ':' in toresolve: | ||
| return self.prefixes.resolve(toresolve) | ||
| else: | ||
| return self.terms.resolve(toresolve) | ||
| def setDefaultVocabulary(self, iri): | ||
| """This method sets the default vocabulary for use when resolving | ||
| unknown terms, it is identical to calling the setDefault method on | ||
| terms.""" | ||
| self.terms.setDefault(iri) | ||
| def setDefaultPrefix(self, iri): | ||
| """This method sets the default prefix for use when resolving CURIEs | ||
| without a prefix, for example ":me", it is identical to calling the | ||
| setDefault method on prefixes.""" | ||
| self.prefixes.setDefault(iri) | ||
| def setTerm(self, term, iri): | ||
| """This method associates an IRI with a term, it is identical to | ||
| calling the set method on term.""" | ||
| self.terms[term] = iri | ||
| def setPrefix(self, prefix, iri): | ||
| """This method associates an IRI with a prefix, it is identical to | ||
| calling the set method on prefixes.""" | ||
| self.prefixes[prefix] = iri | ||
| def importProfile(self, profile, override=False): | ||
| """This method functions the same as calling | ||
| prefixes.addAll(profile.prefixes, override) and | ||
| terms.addAll(profile.terms, override), and allows easy updating and | ||
| merging of different profiles. | ||
| This method returns the instance on which it was called.""" | ||
| self.prefixes.addAll(profile.prefixes, override) | ||
| self.terms.addAll(profile.terms, override) | ||
| return self | ||
| class RDFEnvironment(Profile): | ||
| """The RDF Environment is an interface which exposes a high level API for | ||
| working with RDF in a programming environment.""" | ||
| def createBlankNode(self): | ||
| """Creates a new :py:class:`BlankNode`.""" | ||
| return BlankNode() | ||
| def createNamedNode(self, value): | ||
| """Creates a new :py:class:`NamedNode`.""" | ||
| return NamedNode(value) | ||
| def createLiteral(self, value, language=None, datatype=None): | ||
| """Creates a :py:class:`Literal` given a value, an optional language | ||
| and/or an | ||
| optional datatype.""" | ||
| return Literal(value, language, datatype) | ||
| def createTriple(self, subject, predicate, object): | ||
| """Creates a :py:class:`Triple` given a subject, predicate and | ||
| object.""" | ||
| return Triple(subject, predicate, object) | ||
| def createGraph(self, triples=tuple()): | ||
| """Creates a new :py:class:`Graph`, an optional sequence of | ||
| :py:class:`Triple` to include within the graph may be specified, this | ||
| allows easy transition between native sequences and Graphs and is the | ||
| counterpart for :py:meth:`Graph.toArray`.""" | ||
| g = Graph() | ||
| g.addAll(triples) | ||
| return g | ||
| def createAction(self, test, action): | ||
| raise NotImplemented | ||
| def createProfile(self, empty=False): | ||
| if empty: | ||
| return Profile() | ||
| else: | ||
| return Profile(self.prefixes, self.terms) | ||
| def createTermMap(self, empty=False): | ||
| if empty: | ||
| return TermMap() | ||
| else: | ||
| return TermMap(self.terms) | ||
| def createPrefixMap(self, empty=False): | ||
| if empty: | ||
| return PrefixMap() | ||
| else: | ||
| return PrefixMap(self.prefixes) | ||
| # Pymantic DataSet Extensions | ||
| def createQuad(self, subject, predicate, object, graph): | ||
| return Quad(subject, predicate, object, graph) | ||
| def createDataset(self, quads=tuple()): | ||
| ds = Dataset() | ||
| ds.addAll(quads) | ||
| return ds |
-561
| """Provides common classes and functions for modelling an RDF graph using | ||
| Python objects.""" | ||
| import os.path | ||
| from .compat.moves.urllib import parse as urlparse | ||
| import re | ||
| import logging | ||
| from .compat.moves import cStringIO as StringIO | ||
| from string import Template | ||
| import pymantic.util as util | ||
| from pymantic.primitives import * | ||
| from .compat import ( | ||
| add_metaclass, | ||
| string_types, | ||
| text_type, | ||
| ) | ||
| log = logging.getLogger(__name__) | ||
| class MetaResource(type): | ||
| """Aggregates Prefix and scalar information.""" | ||
| _classes = {} # Map of RDF classes to Python classes. | ||
| def __new__(cls, name, bases, dct): | ||
| prefixes = PrefixMap() | ||
| scalars = set() | ||
| for base in bases: | ||
| if hasattr(base, 'prefixes'): | ||
| prefixes.update(base.prefixes) | ||
| if hasattr(base, 'scalars'): | ||
| scalars.update(base.scalars) | ||
| if 'prefixes' in dct: | ||
| for prefix in dct['prefixes']: | ||
| prefixes[prefix] = Prefix(dct['prefixes'][prefix]) | ||
| dct['prefixes'] = prefixes | ||
| if 'scalars' in dct: | ||
| for scalar in dct['scalars']: | ||
| scalars.add(parse_curie(scalar, prefixes)) | ||
| dct['scalars'] = frozenset(scalars) | ||
| dct['_meta_resource'] = cls | ||
| return type.__new__(cls, name, bases, dct) | ||
| def register_class(rdf_type): | ||
| """Register a class for automatic instantiation VIA Resource.classify.""" | ||
| def _register_class(python_class): | ||
| rdf_class = python_class.resolve(rdf_type) | ||
| MetaResource._classes[python_class.resolve(rdf_type)] = python_class | ||
| python_class.rdf_classes = frozenset((python_class.resolve(rdf_type),)) | ||
| return python_class | ||
| return _register_class | ||
| class URLRetrievalError(Exception): | ||
| """Raised when an attempt to retrieve a resource returns a status other | ||
| than 200 OK.""" | ||
| pass | ||
| @add_metaclass(MetaResource) | ||
| class Resource(object): | ||
| """Provides necessary context and utility methods for accessing a Resource | ||
| in an RDF graph. Resources can be used as-is, but are likely somewhat | ||
| unwieldy, since all predicate access must be by complete URL and produces | ||
| sets. By subclassing Resource, you can take advantage of a number of | ||
| quality-of-life features: | ||
| 1) Bind prefixes to prefixes, and refer to them using CURIEs when | ||
| accessing predicates or explicitly resolving CURIEs. Store a dictionary | ||
| mapping prefixes to URLs in the 'prefixes' attribute of your subclass. | ||
| The prefixes dictionaries on all parents are merged with this | ||
| dictionary, and those at the bottom are prioritized. The values in the | ||
| dictionaries will automatically be turned into rdflib Prefix objects. | ||
| 2) Define predicates as scalars. This asserts that a given predicate on this | ||
| resource will only have zero or one value for a given language or | ||
| data-type, or one reference to another resource. This is done using the | ||
| 'scalars' set, which is processed and merged just like prefixes. | ||
| 3) Automatically classify certain RDF types as certain Resource subclasses. | ||
| Decorate your class with the pymantic.RDF.register_class decorator, and | ||
| provide it with the corresponding RDF type. Whenever this type is | ||
| encountered when retrieving objects from a predicate it will | ||
| automatically be instantiated as your class rather than a generic Resource. | ||
| RDF allows for resources to have multiple types. When a resource is | ||
| encountered with two or more types that have different python classes | ||
| registered for them, a new python class is created. This new class | ||
| subclasses all applicable registered classes. | ||
| If you want to perform this classification manually (to, for example, | ||
| instantiate the correct class for an arbitrary URI), you can do so by | ||
| calling Resource.classify. You can also create a new instance of a | ||
| Resource by calling .new on a subclass. | ||
| Automatic retrieval of resources with no type information is currently | ||
| implemented here, but is likely to be refactored into a separate persistence | ||
| layer in the near future.""" | ||
| prefixes = {'rdf': 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', | ||
| 'rdfs': 'http://www.w3.org/2000/01/rdf-schema#'} | ||
| scalars = frozenset(('rdfs:label',)) | ||
| lang = 'en' | ||
| rdf_classes = frozenset() | ||
| global_profile = Profile() | ||
| def __init__(self, graph, subject): | ||
| self.graph = graph | ||
| if not isinstance(subject, NamedNode) and not isinstance(subject, BlankNode): | ||
| subject = NamedNode(subject) | ||
| self.subject = subject | ||
| @classmethod | ||
| def new(cls, graph, subject = None): | ||
| """Add type information to the graph for a new instance of this Resource.""" | ||
| #for prefix, Prefix in cls.prefixes.iteritems(): | ||
| #graph.bind(prefix, Prefix) | ||
| if subject is None: | ||
| subject = BlankNode() | ||
| if not isinstance(subject, NamedNode): | ||
| subject = NamedNode(subject) | ||
| for rdf_class in cls.rdf_classes: | ||
| graph.add(Triple(subject, cls.resolve('rdf:type'), rdf_class)) | ||
| return cls(graph, subject) | ||
| def erase(self): | ||
| """Erase all tripes for this resource from the graph.""" | ||
| for triple in list(self.graph.match(self.subject, None, None)): | ||
| self.graph.remove(triple) | ||
| def is_a(self): | ||
| """Test to see if the subject of this resource has all the necessary | ||
| RDF classes applied to it.""" | ||
| if hasattr(self, 'rdf_classes'): | ||
| for rdf_class in self.rdf_classes: | ||
| if not any(self.graph.match(self.subject, | ||
| self.resolve('rdf:type'), | ||
| rdf_class)): | ||
| return False | ||
| return True | ||
| @classmethod | ||
| def resolve(cls, key): | ||
| """Use this class's prefixes to resolve a curie""" | ||
| try: | ||
| return cls.prefixes.resolve(key) | ||
| except ValueError: | ||
| return cls.global_profile.resolve(key) | ||
| def __eq__(self, other): | ||
| if isinstance(other, Resource): | ||
| return self.subject == other.subject | ||
| elif isinstance(other, NamedNode) or isinstance(other, string_types): | ||
| return text_type(self.subject) == text_type(other) | ||
| return NotImplemented | ||
| def __ne__(self, other): | ||
| eq = self.__eq__(other) | ||
| if eq is NotImplemented: | ||
| return NotImplemented | ||
| else: | ||
| return not eq | ||
| def __hash__(self): | ||
| return hash(self.subject) | ||
| def bare_literals(self, predicate): | ||
| """Objects for a predicate that are language-less, datatype-less Literals.""" | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None) if\ | ||
| hasattr(t.object, 'language') and t.object.language is None and\ | ||
| hasattr(t.object, 'datatype') and t.object.datatype is None] | ||
| def objects_by_lang(self, predicate, lang=None): | ||
| """Objects for a predicate that match a specified language or, if | ||
| language is None, have a language specified.""" | ||
| if lang: | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None) if\ | ||
| hasattr(t.object, 'language') and lang_match(lang, t.object.language)] | ||
| elif lang == '': | ||
| return self.bare_literals(predicate) | ||
| else: | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None) if\ | ||
| hasattr(t.object, 'language') and t.object.language is not None] | ||
| def objects_by_datatype(self, predicate, datatype=None): | ||
| """Objects for a predicate that match a specified datatype or, if | ||
| datatype is None, have a datatype specified.""" | ||
| if datatype: | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None) if\ | ||
| hasattr(t.object, 'datatype') and t.object.datatype == datatype] | ||
| elif datatype == '': | ||
| return self.bare_literals(predicate) | ||
| else: | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None) if\ | ||
| hasattr(t.object, 'datatype') and t.object.datatype is not None] | ||
| def objects_by_type(self, predicate, resource_class = None): | ||
| """Objects for a predicate that are instances of a particular Resource | ||
| subclass or, if resource_class is none, are Resources.""" | ||
| selected_objects = [] | ||
| for t in self.graph.match(self.subject, predicate, None): | ||
| obj = t.object | ||
| if isinstance(obj, BlankNode) or isinstance(obj, NamedNode): | ||
| if resource_class is None or\ | ||
| isinstance(self.classify(self.graph, obj), | ||
| resource_class): | ||
| selected_objects.append(obj) | ||
| return selected_objects | ||
| def objects(self, predicate): | ||
| """All objects for a predicate.""" | ||
| return [t.object for t in self.graph.match(self.subject, predicate, None)] | ||
| def object_of(self, predicate = None): | ||
| """All subjects for which this resource is an object for the given | ||
| predicate.""" | ||
| if predicate is None: | ||
| for triple in self.graph.match(None, None, self.subject): | ||
| yield (self.classify(self.graph, triple.subject), triple.predicate) | ||
| else: | ||
| predicate = self.resolve(predicate) | ||
| for triple in self.graph.match(None, predicate, self.subject): | ||
| yield self.classify(self.graph, triple.subject) | ||
| def __getitem__(self, key): | ||
| """Fetch predicates off this subject by key dictionary-style. | ||
| This is the primary mechanism for predicate access. You can either | ||
| provide a predicate name, as a complete URL or CURIE: | ||
| resource['rdfs:label'] | ||
| resource['http://www.w3.org/2000/01/rdf-schema#label'] | ||
| Or a predicate name and a datatype or language: | ||
| resource['rdfs:label', 'en'] | ||
| Passing in a value of None will result in all values for the predicate | ||
| in question being returned.""" | ||
| predicate, objects = self._objects_for_key(key) | ||
| if predicate not in self.scalars or (isinstance(key, tuple) and key[1] is None): | ||
| def getitem_iter_results(): | ||
| for obj in objects: | ||
| yield self.classify(self.graph, obj) | ||
| return getitem_iter_results() | ||
| else: | ||
| return self.classify(self.graph, util.one_or_none(objects)) | ||
| def get_scalar(self, key): | ||
| """As __getitem__ access, but pretend the key is a scalar even if it isn't. | ||
| Expect random exceptions if using this carelessly.""" | ||
| predicate, objects = self._objects_for_key(key) | ||
| return self.classify(self.graph, util.one_or_none(objects)) | ||
| # Set item | ||
| def __setitem__(self, key, value): | ||
| """Sets objects for predicates for this subject by key dictionary-style. | ||
| Returns 'self', for easy chaining. | ||
| 1) Setting a predicate without a filter replaces the set of all objects | ||
| for that predicate. The exception is assigning a Literal object with | ||
| a language to a scalar predicate. This will only replace objects that | ||
| share its language, though any resources or datatyped literals will | ||
| be removed. | ||
| 2) Setting a predicate with a filter will only replace objects that | ||
| match the specified filter, including all resource references for | ||
| language or datatype filters. The exception is scalars, where | ||
| datatyped literals and objects will replace everything else, and | ||
| language literals can co-exist but will replace all datatyped | ||
| literals. | ||
| 3) Attempting to set a literal that doesn't make sense will raise a | ||
| ValueError. For example, including an english or dateTime literal | ||
| when setting a predicate's objects using a French language filter | ||
| will result in a ValueError. Object references are always acceptable | ||
| to include.""" | ||
| predicate, lang, datatype, rdf_class = self._interpret_key(key) | ||
| value = literalize(self.graph, value, lang, datatype) | ||
| if not isinstance(key, tuple): | ||
| # Implicit specification. | ||
| objects = self._objects_for_implicit_set(predicate, value) | ||
| else: | ||
| # Explicit specification. | ||
| objects = self._objects_for_explicit_set(predicate, value, lang, | ||
| datatype, rdf_class) | ||
| for obj in objects: | ||
| self.graph.remove(Triple(self.subject, predicate, obj)) | ||
| if isinstance(value, frozenset): | ||
| for obj in value: | ||
| if isinstance(obj, Resource): | ||
| self.graph.add(Triple(self.subject, predicate, obj.subject)) | ||
| else: | ||
| self.graph.add(Triple(self.subject, predicate, obj)) | ||
| else: | ||
| if isinstance(value, Resource): | ||
| self.graph.add(Triple(self.subject, predicate, value.subject)) | ||
| else: | ||
| self.graph.add(Triple(self.subject, predicate, value)) | ||
| return self | ||
| # Delete item | ||
| def __delitem__(self, key): | ||
| """Deletes predicates for this subject by key dictionary-style. | ||
| del resource[key] will always remove the same things from the graph as | ||
| resource[key] returns.""" | ||
| predicate, objects = self._objects_for_key(key) | ||
| for obj in objects: | ||
| self.graph.remove(Triple(self.subject, predicate, obj)) | ||
| # Membership test | ||
| def __contains__(self, predicate): | ||
| """Uses the same logic as __getitem__ to determine if a predicate or | ||
| filtered predicate is present for this object.""" | ||
| predicate, objects = self._objects_for_key(predicate) | ||
| if objects: | ||
| return True | ||
| return False | ||
| def __iter__(self): | ||
| for s, p, o in self.graph.match(self.subject, None, None): | ||
| yield p, o | ||
| @classmethod | ||
| def in_graph(cls, graph): | ||
| """Iterate through all instances of this Resource in the graph.""" | ||
| subjects = set() | ||
| for rdf_class in cls.rdf_classes: | ||
| if not subjects: | ||
| subjects.update([t.subject for t in graph.match( | ||
| None, cls.resolve('rdf:type'), rdf_class)]) | ||
| else: | ||
| subjects.intersection_update([t.subject for t in graph.match( | ||
| None, cls.resolve('rdf:type'), rdf_class)]) | ||
| return set(cls(graph, subject) for subject in subjects) | ||
| def __repr__(self): | ||
| return "<%r: %s>" % (type(self), self.subject) | ||
| def __str__(self): | ||
| if self['rdfs:label']: | ||
| return self['rdfs:label'].value | ||
| else: | ||
| return str(self.subject) | ||
| @classmethod | ||
| def classify(cls, graph, obj): | ||
| """Classify an object into an appropriate registered class, or Resource. | ||
| May create a new class if necessary that is a subclass of two or more | ||
| registered Resource classes.""" | ||
| if obj is None: | ||
| return None | ||
| if isinstance(obj, Literal): | ||
| return obj | ||
| if any(graph.match(obj, cls.resolve('rdf:type'), None)): | ||
| #retrieve_resource(graph, obj) | ||
| if not any(graph.match(obj, cls.resolve('rdf:type'), None)): | ||
| return Resource(graph, obj) | ||
| types = frozenset([t.object for t in graph.match( | ||
| obj, cls.resolve('rdf:type'), None)]) | ||
| python_classes = tuple(cls._meta_resource._classes[t] for t in types if\ | ||
| t in cls._meta_resource._classes) | ||
| if len(python_classes) == 0: | ||
| return Resource(graph, obj) | ||
| elif len(python_classes) == 1: | ||
| return python_classes[0](graph, obj) | ||
| else: | ||
| if types not in cls._meta_resource._classes: | ||
| the_class = cls._meta_resource.__new__( | ||
| cls._meta_resource, ''.join(python_class.__name__ for\ | ||
| python_class in python_classes), | ||
| python_classes, {'_autocreate': True}) | ||
| cls._meta_resource._classes[types] = the_class | ||
| the_class.rdf_classes = frozenset(types) | ||
| return cls._meta_resource._classes[types](graph, obj) | ||
| def _interpret_key(self, key): | ||
| """Break up a key into a predicate name and optional language or | ||
| datatype specifier.""" | ||
| lang = None | ||
| datatype = None | ||
| rdf_class = None | ||
| if isinstance(key, tuple) and len(key) >= 2: | ||
| if key[1] is None: | ||
| pass # All values are already None, do nothing. | ||
| elif isinstance(key[1], MetaResource): | ||
| rdf_class = key[1] | ||
| elif is_language(key[1]): | ||
| lang = key[1] | ||
| else: | ||
| datatype = self._interpret_datatype(key[1]) | ||
| predicate = self.resolve(key[0]) | ||
| else: | ||
| predicate = self.resolve(key) | ||
| if not isinstance(key, tuple) and predicate in self.scalars: | ||
| lang = self.lang | ||
| return predicate, lang, datatype, rdf_class | ||
| def _interpret_datatype(self, datatype): | ||
| """Deal with xsd:string vs. plain literal""" | ||
| if datatype == '': | ||
| return '' | ||
| elif datatype == 'http://www.w3.org/2001/XMLSchema#string': | ||
| return '' | ||
| else: | ||
| return datatype | ||
| def _objects_for_key(self, key): | ||
| """Find objects that are potentially interesting when doing normal | ||
| dictionary key-style access - IE, __getitem__, __delitem__, __contains__, | ||
| and pretty much everything but __setitem__.""" | ||
| predicate, lang, datatype, rdf_class = self._interpret_key(key) | ||
| # log.debug("predicate: %r lang: %r datatype: %r rdf_class: %r", predicate, lang, datatype, rdf_class) | ||
| if lang is None and datatype is None and rdf_class is None: | ||
| objects = self.objects(predicate) | ||
| elif lang: | ||
| objects = self.objects_by_lang(predicate, lang) | ||
| if not isinstance(key, tuple) and predicate in self.scalars and not objects: | ||
| objects += self.objects_by_type(predicate) | ||
| if not objects: | ||
| objects += self.objects_by_datatype(predicate) | ||
| if not objects: | ||
| objects += self.bare_literals(predicate) | ||
| if predicate not in self.scalars: | ||
| objects += self.objects_by_type(predicate) | ||
| elif datatype: | ||
| objects = self.objects_by_datatype(predicate, datatype) | ||
| if predicate not in self.scalars: | ||
| objects += self.objects_by_type(predicate) | ||
| elif rdf_class: | ||
| objects = self.objects_by_type(predicate, rdf_class) | ||
| elif lang == '' or datatype == '': | ||
| objects = self.bare_literals(predicate) | ||
| else: | ||
| raise KeyError('Invalid key: ' + repr(key)) | ||
| return predicate, objects | ||
| def _objects_for_implicit_set(self, predicate, value): | ||
| """Find the objects that should be removed from the graph when doing a | ||
| dictionary-style set with implicit type information.""" | ||
| if (isinstance(value, frozenset) or (isinstance(value, tuple) and\ | ||
| not isinstance(value, Literal))) and\ | ||
| predicate in self.scalars: | ||
| raise ValueError('Cannot store sequences in scalars') | ||
| elif predicate in self.scalars and isinstance(value, Literal)\ | ||
| and value.language: | ||
| return self.objects_by_lang(predicate, value.language) +\ | ||
| self.objects_by_datatype(predicate) +\ | ||
| self.objects_by_type(predicate) +\ | ||
| self.bare_literals(predicate) | ||
| else: | ||
| return self.objects(predicate) | ||
| def _objects_for_explicit_set(self, predicate, value, lang, datatype, rdf_class): | ||
| """Find the objects that should be removed from the graph when doing a | ||
| dictionary-style set with explicit type information.""" | ||
| if not check_objects(self.graph, value, lang, datatype, rdf_class): | ||
| raise ValueError('Improper value provided.') | ||
| if lang and predicate in self.scalars: | ||
| return self.objects_by_lang(predicate, lang) +\ | ||
| self.objects_by_datatype(predicate) +\ | ||
| self.objects_by_type(predicate) | ||
| elif lang and predicate not in self.scalars: | ||
| return self.objects_by_lang(predicate, lang) +\ | ||
| self.objects_by_type(predicate) | ||
| elif predicate in self.scalars: | ||
| return self.objects(predicate) | ||
| elif datatype: | ||
| return self.objects_by_datatype(predicate, datatype) +\ | ||
| self.objects_by_type(predicate) | ||
| elif rdf_class: | ||
| return self.objects_by_type(predicate, rdf_class) | ||
| def copy(self, target_subject): | ||
| """Create copies of all triples with this resource as their subject | ||
| with the target subject as their subject. Returns a classified version | ||
| of the target subject.""" | ||
| if not isinstance(target_subject, NamedNode) and\ | ||
| not isinstance(target_subject, BlankNode): | ||
| target_subject = NamedNode(target_subject) | ||
| for t in self.graph.match(self.subject, None, None): | ||
| self.graph.add((target_subject, t.predicate, t.object)) | ||
| return self.classify(self.graph, target_subject) | ||
| def as_(self, target_class): | ||
| return target_class(self.graph, self.subject) | ||
| class List(Resource): | ||
| """Convenience class for dealing with RDF lists. | ||
| Requires considerable use of ``as_``, due to the utter lack of type | ||
| information on said lists.""" | ||
| scalars = frozenset(('rdf:first', 'rdf:rest')) | ||
| def __iter__(self): | ||
| """Iterating over lists works differently from normal Resources.""" | ||
| current = self | ||
| while current.subject != self.resolve('rdf:nil'): | ||
| yield current['rdf:first'] | ||
| current = current['rdf:rest'] | ||
| if current.subject != self.resolve('rdf:nil'): | ||
| current = current.as_(type(self)) | ||
| @classmethod | ||
| def is_list(cls, node, graph): | ||
| """Determine if a given node is plausibly the subject of a list element.""" | ||
| return bool(list(graph.match( | ||
| subject = node, predicate = cls.resolve('rdf:rest')))) | ||
| def literalize(graph, value, lang, datatype): | ||
| """Convert either a value or a sequence of values to either a Literal or | ||
| a Resource.""" | ||
| if isinstance(value, set) or isinstance(value, frozenset) or\ | ||
| isinstance(value, list) or (isinstance(value, tuple) and\ | ||
| not isinstance(value, Literal)): | ||
| return frozenset(objectify_value(graph, v, lang, datatype) for v in value) | ||
| else: | ||
| return objectify_value(graph, value, lang, datatype) | ||
| def objectify_value(graph, value, lang = None, datatype = None): | ||
| """Convert a single value into either a Literal or a Resource.""" | ||
| if isinstance(value, BlankNode) or isinstance(value, NamedNode): | ||
| return Resource.classify(graph, value) | ||
| elif isinstance(value, Literal) or isinstance(value, Resource): | ||
| return value | ||
| elif isinstance(value, string_types): | ||
| return Literal(value, language = lang, datatype = datatype) | ||
| else: | ||
| return Literal(value) | ||
| def check_objects(graph, value, lang, datatype, rdf_class): | ||
| """Determine that value or the things in values are appropriate for the | ||
| specified explicit object access key.""" | ||
| if isinstance(value, frozenset) or (isinstance(value, tuple) and\ | ||
| not isinstance(value, Literal)): | ||
| for v in value: | ||
| if (lang and (not hasattr(v, 'language') or\ | ||
| not lang_match(v.language, lang))) or \ | ||
| (datatype and v.datatype != datatype) or \ | ||
| (rdf_class and not isinstance(v, rdf_class)): | ||
| return False | ||
| return True | ||
| else: | ||
| return (lang and lang_match(value.language, lang)) or \ | ||
| (datatype and value.datatype == datatype) or \ | ||
| (rdf_class and isinstance(value, rdf_class)) |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| from collections import OrderedDict | ||
| import re | ||
| import warnings | ||
| from .compat import ( | ||
| iteritems, | ||
| text_type, | ||
| ) | ||
| def nt_escape(node_string): | ||
| """Properly escape strings for n-triples and n-quads serialization.""" | ||
| output_string = '' | ||
| for char in node_string: | ||
| if char == u'\u0009': | ||
| output_string += '\\t' | ||
| elif char == u'\u000A': | ||
| output_string += '\\n' | ||
| elif char == u'\u000D': | ||
| output_string += '\\r' | ||
| elif char == u'\u0022': | ||
| output_string += '\\"' | ||
| elif char == u'\u005C': | ||
| output_string += '\\\\' | ||
| elif char >= u'\u0020' and char <= u'\u0021' or\ | ||
| char >= u'\u0023' and char <= u'\u005B' or\ | ||
| char >= u'\u005D' and char <= u'\u007E': | ||
| output_string += char | ||
| elif char >= u'\u007F' and char <= u'\uFFFF': | ||
| output_string += '\\u%04X' % ord(char) | ||
| elif char >= u'\U00010000' and char <= u'\U0010FFFF': | ||
| output_string += '\\U%08X' % ord(char) | ||
| return output_string | ||
| def serialize_ntriples(graph, f): | ||
| """Serialize some graph to f as ntriples.""" | ||
| for triple in graph: | ||
| f.write(str(triple)) | ||
| def serialize_nquads(dataset, f): | ||
| """Serialize some graph to f as nquads.""" | ||
| for quad in dataset: | ||
| f.write(str(quad)) | ||
| def default_bnode_name_generator(): | ||
| i = 0 | ||
| while True: | ||
| yield '_b' + str(i) | ||
| i += 1 | ||
| def escape_prefix_local(prefix): | ||
| prefix, colon, local = prefix.partition(':') | ||
| for esc_char in "~.-!$&'()*+,;=:/?#@%_": | ||
| local = local.replace(esc_char, '\\' + esc_char) | ||
| return ''.join((prefix,colon,local)) | ||
| def turtle_string_escape(string): | ||
| """Escape a string appropriately for output in turtle form.""" | ||
| from pymantic.util import ECHAR_MAP | ||
| for escaped, value in iteritems(ECHAR_MAP): | ||
| string = string.replace(value, '\\' + escaped) | ||
| return '"' + string + '"' | ||
| def turtle_repr(node, profile, name_map, bnode_name_maker, base=None): | ||
| """Turn a node in an RDF graph into its turtle representation.""" | ||
| if node.interfaceName == 'NamedNode': | ||
| name = profile.prefixes.shrink(node) | ||
| if base and name.startswith(base): | ||
| if base.endswith("#"): | ||
| name = '<' + text_type(name.replace(base, "#")) + '>' | ||
| else: | ||
| name = '<' + text_type(name.replace(base, "")) + '>' | ||
| if name == node: | ||
| name = '<' + text_type(name) + '>' | ||
| else: | ||
| escape_prefix_local(name) | ||
| elif node.interfaceName == 'BlankNode': | ||
| if node in name_map: | ||
| name = name_map[node] | ||
| else: | ||
| name = bnode_name_maker.next() | ||
| name_map[node] = name | ||
| elif node.interfaceName == 'Literal': | ||
| if node.datatype == profile.resolve('xsd:string'): | ||
| # Simple string. | ||
| name = turtle_string_escape(node.value) | ||
| elif node.datatype == None: | ||
| # String with language? | ||
| name = turtle_string_escape(node.value) | ||
| if node.language: | ||
| name += '@' + node.language | ||
| elif node.datatype == profile.resolve('xsd:integer'): | ||
| name = node.value | ||
| elif node.datatype == profile.resolve('xsd:decimal'): | ||
| name = node.value | ||
| elif node.datatype == profile.resolve('xsd:double'): | ||
| name = node.value | ||
| elif node.datatype == profile.resolve('xsd:boolean'): | ||
| name = node.value | ||
| else: | ||
| # Unrecognized data-type. | ||
| name = turtle_string_escape(node.value) | ||
| name += '^' + turtle_repr(node.datatype, profile, None, None) | ||
| return name | ||
| def turtle_sorted_names(l, name_maker): | ||
| """Sort a list of nodes in a graph by turtle name.""" | ||
| return sorted((name_maker(n), n) for n in l) | ||
| def serialize_turtle(graph, f, base=None, profile=None, | ||
| bnode_name_generator=default_bnode_name_generator): | ||
| """Serialize a graph to f as turtle, optionally using base IRI base | ||
| and prefix map from profile. If provided, subject_key will be used to order | ||
| subjects, and predicate_key predicates within a subject.""" | ||
| if base is not None: | ||
| f.write('@base <' + base + '> .\n') | ||
| if profile is None: | ||
| from pymantic.primitives import Profile | ||
| profile = Profile() | ||
| for prefix, iri in iteritems(profile.prefixes): | ||
| if prefix != 'rdf': | ||
| f.write('@prefix ' + prefix + ': <' + iri + '> .\n') | ||
| name_map = OrderedDict() | ||
| output_order = [] | ||
| bnode_name_maker = bnode_name_generator() | ||
| name_maker = lambda n: turtle_repr(n, profile, name_map, bnode_name_maker, base) | ||
| from pymantic.rdf import List | ||
| subjects = [subj for subj in graph.subjects() if not List.is_list(subj, graph)] | ||
| for subject_name, subject in turtle_sorted_names(subjects, name_maker): | ||
| subj_indent_size = len(subject_name) + 1 | ||
| f.write(subject_name + ' ') | ||
| predicates = set(t.predicate for t in graph.match(subject = subject)) | ||
| sorted_predicates = turtle_sorted_names(predicates, name_maker) | ||
| for i, (predicate_name, predicate) in enumerate(sorted_predicates): | ||
| if i != 0: | ||
| f.write(' ' * subj_indent_size) | ||
| pred_indent_size = subj_indent_size + len(predicate_name) + 1 | ||
| f.write(predicate_name + ' ') | ||
| for j, triple in enumerate(graph.match(subject = subject, | ||
| predicate = predicate)): | ||
| if j != 0: | ||
| f.write(',\n' + ' ' * pred_indent_size) | ||
| if List.is_list(triple.object, graph): | ||
| f.write('(') | ||
| for k, o in enumerate(List(graph, triple.object)): | ||
| if k != 0: | ||
| f.write(' ') | ||
| f.write(name_maker(o)) | ||
| f.write(')') | ||
| else: | ||
| f.write(name_maker(triple.object)) | ||
| f.write(' ;\n') | ||
| f.write(' ' * subj_indent_size + '.\n\n') |
| """Provide an interface to SPARQL query endpoints.""" | ||
| from .compat.moves import cStringIO as StringIO | ||
| import datetime | ||
| import urllib | ||
| from .compat.moves.urllib import parse as urlparse | ||
| from lxml import objectify | ||
| import pytz | ||
| import rdflib | ||
| import requests | ||
| import json | ||
| import logging | ||
| log = logging.getLogger(__name__) | ||
| class SPARQLQueryException(Exception): | ||
| """Raised when the SPARQL store returns an HTTP status code other than 200 OK.""" | ||
| pass | ||
| class UnknownSPARQLReturnTypeException(Exception): | ||
| """Raised when the SPARQL store provides a response with an unrecognized content-type.""" | ||
| pass | ||
| class _SelectOrUpdate(object): | ||
| """A server that can run SPARQL queries.""" | ||
| def __init__(self, server, sparql, default_graph=None, named_graph=None, *args, **kwargs): | ||
| self.server = server | ||
| self.sparql = sparql | ||
| self.default_graphs = default_graph | ||
| self.named_graphs = named_graph | ||
| self.headers = dict() | ||
| self.params = dict() | ||
| # abstract methods, see Select for the idea | ||
| def default_graph_uri(self): | ||
| pass | ||
| def named_graph_uri(self): | ||
| pass | ||
| def query_or_update(self): | ||
| pass | ||
| def directContentType(self): | ||
| pass | ||
| def postQueries(self): | ||
| pass | ||
| def execute(self): | ||
| log.debug("Querying: %s with: %r", self.server.query_url, self.sparql) | ||
| sparql = self.sparql.encode('utf-8') | ||
| if self.default_graphs: | ||
| self.params[self.default_graph_uri()] = self.default_graphs | ||
| if self.named_graphs: | ||
| self.params[self.named_graph_uri()] = self.named_graphs | ||
| if self.server.post_directly: | ||
| self.headers["Content-Type"] = self.directContentType() + "; charset=utf-8" | ||
| uri_params = self.params | ||
| data = sparql | ||
| method = 'post' | ||
| elif self.postQueries(): | ||
| uri_params = None | ||
| self.params[self.query_or_update()] = sparql | ||
| data = self.params | ||
| method = 'post' | ||
| else: | ||
| # select only | ||
| self.params[self.query_or_update()] = sparql | ||
| uri_params = self.params | ||
| data = None | ||
| method = 'get' | ||
| response = self.server.s.request( | ||
| method, self.server.query_url, | ||
| params=uri_params, headers=self.headers, data=data, | ||
| **self.server.requests_kwargs) | ||
| if response.status_code == 204: | ||
| return True | ||
| if response.status_code != 200: | ||
| raise SPARQLQueryException('%s: %s\nQuery: %s' % | ||
| (response.headers, response.content, self.sparql)) | ||
| return response | ||
| class _Select(_SelectOrUpdate): | ||
| acceptable_xml_responses = [ | ||
| 'application/rdf+xml', | ||
| 'application/sparql-results+xml', | ||
| ] | ||
| acceptable_json_responses = [ | ||
| 'application/sparql-results+json', | ||
| 'text/turtle', | ||
| ] | ||
| def __init__(self, server, query, output='json', *args, **kwargs): | ||
| super(_Select, self).__init__(server, query, *args, **kwargs) | ||
| if output == 'xml': | ||
| self.headers['Accept'] = ','.join(self.acceptable_xml_responses) | ||
| else: | ||
| self.headers['Accept'] = ','.join(self.acceptable_json_responses) | ||
| def default_graph_uri(self): | ||
| return 'default-graph-uri' | ||
| def named_graph_uri(self): | ||
| return 'named-graph-uri' | ||
| def query_or_update(self): | ||
| return 'query' | ||
| def directContentType(self): | ||
| return 'application/sparql-query' | ||
| def postQueries(self): | ||
| return self.server.post_queries | ||
| def execute(self): | ||
| response = super(_Select, self).execute() | ||
| format = None | ||
| if response.headers['content-type'].startswith('application/rdf+xml'): | ||
| format = 'xml' | ||
| elif response.headers['content-type'].startswith('text/turtle'): | ||
| format = 'turtle' | ||
| if format: | ||
| graph = rdflib.ConjunctiveGraph() | ||
| graph.parse(StringIO(response.content), self.query_url, format=format) | ||
| return graph | ||
| elif response.headers['content-type'].startswith('application/sparql-results+json'): | ||
| return json.loads(response.content.decode("utf-8")) | ||
| elif response.headers['content-type'].startswith('application/sparql-results+xml'): | ||
| return objectify.parse(StringIO(response.content)) | ||
| else: | ||
| raise UnknownSPARQLReturnTypeException('Got content of type: %s' % | ||
| response.headers['content-type']) | ||
| class _Update(_SelectOrUpdate): | ||
| def default_graph_uri(self): | ||
| return 'using-graph-uri' | ||
| def named_graph_uri(self): | ||
| return 'using-named-graph-uri' | ||
| def query_or_update(self): | ||
| return 'update' | ||
| def directContentType(self): | ||
| return 'application/sparql-update' | ||
| def postQueries(self): | ||
| return True | ||
| class SPARQLServer(object): | ||
| """A server that can run SPARQL queries.""" | ||
| def __init__(self, query_url, post_queries=False, post_directly=False, verify=None): | ||
| self.query_url = query_url | ||
| self.post_queries = post_queries | ||
| self.post_directly = post_directly | ||
| self.requests_kwargs = {} | ||
| if verify is not None: | ||
| self.requests_kwargs = {'verify': verify} | ||
| self.s = requests.Session() | ||
| acceptable_sparql_responses = [ | ||
| 'application/sparql-results+json', | ||
| 'application/rdf+xml', | ||
| 'application/sparql-results+xml', | ||
| ] | ||
| def query(self, sparql, *args, **kwargs): | ||
| """Execute a SPARQL query. | ||
| The return type varies based on what the SPARQL store responds with: | ||
| * application/rdf+xml: an rdflib.ConjunctiveGraph | ||
| * application/sparql-results+json: A dictionary from json | ||
| * application/sparql-results+xml: An lxml.objectify structure | ||
| :param sparql: The SPARQL to execute. | ||
| :returns: The results of the query from the SPARQL store. | ||
| """ | ||
| return _Select(self, sparql, *args, **kwargs).execute() | ||
| def update(self, sparql, **kwargs): | ||
| """Execute a SPARQL update. | ||
| :param sparql: The SPARQL Update request to execute. | ||
| """ | ||
| return _Update(self, sparql, **kwargs).execute() | ||
| class UpdateableGraphStore(SPARQLServer): | ||
| """SPARQL server class that is capable of interacting with SPARQL 1.1 graph stores.""" | ||
| def __init__(self, query_url, dataset_url, param_style=True, **kwargs): | ||
| super(UpdateableGraphStore, self).__init__(query_url, **kwargs) | ||
| self.dataset_url = dataset_url | ||
| self.param_style = param_style | ||
| acceptable_graph_responses = [ | ||
| 'text/plain', | ||
| 'application/rdf+xml', | ||
| 'text/turtle', | ||
| 'text/rdf+n3', | ||
| ] | ||
| def request_url(self, graph_uri): | ||
| if self.param_style: | ||
| return self.dataset_url + '?' + urllib.urlencode({'graph': graph_uri}) | ||
| else: | ||
| return urlparse.urljoin(self.dataset_url, urllib.quote_plus(graph_uri)) | ||
| def get(self, graph_uri): | ||
| response = self.s.get(self.request_url(graph_uri), | ||
| headers={'Accept': ','.join(self.acceptable_graph_responses)}, | ||
| **self.server.requests_kwargs) | ||
| if response.status_code != 200: | ||
| raise Exception('Error from Graph Store (%s): %s' % | ||
| (response.status_code, response.content)) | ||
| graph = rdflib.ConjunctiveGraph() | ||
| if response.headers['content-type'].startswith('text/plain'): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format='nt') | ||
| elif response.headers['content-type'].startswith('application/rdf+xml'): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format='xml') | ||
| elif response.headers['content-type'].startswith('text/turtle'): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format='turtle') | ||
| elif response.headers['content-type'].startswith('text/rdf+n3'): | ||
| graph.parse(StringIO(response.content), publicID=graph_uri, format='n3') | ||
| return graph | ||
| def delete(self, graph_uri): | ||
| response = self.s.delete(self.request_url(graph_uri), | ||
| **self.server.request_kwargs) | ||
| if response.status_code not in (200, 202): | ||
| raise Exception('Error from Graph Store (%s): %s' % | ||
| (response.status_code, response.content)) | ||
| def put(self, graph_uri, graph): | ||
| graph_triples = graph.serialize(format='nt') | ||
| response = self.s.put(self.request_url(graph_uri), | ||
| data=graph_triples, | ||
| headers={'content-type': 'text/plain'}, | ||
| **self.server.requests_kwargs) | ||
| if response.status_code not in (200, 201, 204): | ||
| raise Exception('Error from Graph Store (%s): %s' % | ||
| (response.status_code, response.content)) | ||
| def post(self, graph_uri, graph): | ||
| graph_triples = graph.serialize(format='nt') | ||
| if graph_uri is not None: | ||
| response = self.s.post(self.request_url(graph_uri), | ||
| data=graph_triples, | ||
| headers={'content-type': 'text/plain'}, | ||
| **self.server.requests_kwargs) | ||
| if response.status_code not in (200, 201, 204): | ||
| raise Exception('Error from Graph Store (%s): %s' % | ||
| (response.status_code, response.content)) | ||
| else: | ||
| response = self.s.post(self.dataset_url, | ||
| data=graph_triples, | ||
| headers={'content-type': 'text/plain'}, | ||
| **self.server.requests_kwargs) | ||
| if response.status_code != 201: | ||
| raise Exception('Error from Graph Store (%s): %s' % | ||
| (response.status_code, response.content)) | ||
| class PatchableGraphStore(UpdateableGraphStore): | ||
| """A graph store that supports the optional PATCH method of updating RDF graphs.""" | ||
| def patch(self, graph_uri, changeset): | ||
| graph_xml = changeset.serialize(format='xml', encoding='utf-8') | ||
| response = self.s.patch(self.request_url(graph_uri), data=graph_xml, | ||
| headers={'content-type': 'application/vnd.talis.changeset+xml'}, | ||
| **self.server.requests_kwargs) | ||
| if response.status_code not in (200, 201, 204): | ||
| raise Exception('Error from Graph Store (%s): %s' % | ||
| (response.status_code, response.content)) | ||
| return True | ||
| def changeset(a, b, graph_uri): | ||
| """Create an RDF graph with the changeset between graphs a and b.""" | ||
| cs = rdflib.Namespace("http://purl.org/vocab/changeset/schema#") | ||
| graph = rdflib.Graph() | ||
| graph.namespace_manager.bind("cs", cs) | ||
| removal, addition = differences(a, b) | ||
| change_set = rdflib.BNode() | ||
| graph.add((change_set, rdflib.RDF.type, cs["ChangeSet"])) | ||
| graph.add((change_set, cs["createdDate"], | ||
| rdflib.Literal(datetime.datetime.now(pytz.UTC).isoformat()))) | ||
| graph.add((change_set, cs["subjectOfChange"], rdflib.URIRef(graph_uri))) | ||
| for stmt in removal: | ||
| statement = reify(graph, stmt) | ||
| graph.add((change_set, cs["removal"], statement)) | ||
| for stmt in addition: | ||
| statement = reify(graph, stmt) | ||
| graph.add((change_set, cs["addition"], statement)) | ||
| return graph | ||
| def reify(graph, statement): | ||
| """Add reifed statement to graph.""" | ||
| s, p, o = statement | ||
| statement_node = rdflib.BNode() | ||
| graph.add((statement_node, rdflib.RDF.type, rdflib.RDF.Statement)) | ||
| graph.add((statement_node, rdflib.RDF.subject, s)) | ||
| graph.add((statement_node, rdflib.RDF.predicate, p)) | ||
| graph.add((statement_node, rdflib.RDF.object, o)) | ||
| return statement_node | ||
| def differences(a, b, exclude=[]): | ||
| """Return (removes,adds) excluding statements with a predicate in exclude.""" | ||
| exclude = [rdflib.URIRef(excluded) for excluded in exclude] | ||
| return ([s for s in a if s not in b and s[1] not in exclude], | ||
| [s for s in b if s not in a and s[1] not in exclude]) |
| from pymantic.compat.moves import cStringIO as StringIO | ||
| from nose.tools import * | ||
| from nose.plugins.skip import Skip, SkipTest | ||
| from pymantic.parsers import * | ||
| from pymantic.primitives import * | ||
| def test_parse_ntriples_named_nodes(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> <http://example.com/objects/2> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> <http://example.com/objects/1> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert Triple(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| NamedNode('http://example.com/objects/2')) in g | ||
| assert Triple(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| NamedNode('http://example.com/objects/1')) in g | ||
| def test_parse_ntriples_bare_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo" . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "Bar" . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert Triple(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| Literal("Foo")) in g | ||
| assert Triple(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| Literal("Bar")) in g | ||
| def test_parse_ntriples_language_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo"@en-US . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "Bar"@fr . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert Triple(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| Literal("Foo", language='en-US')) in g | ||
| assert Triple(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| Literal("Bar", language='fr')) in g | ||
| def test_parse_ntriples_datatyped_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo"^^<http://www.w3.org/2001/XMLSchema#string> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "9.99"^^<http://www.w3.org/2001/XMLSchema#decimal> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert Triple(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| Literal("Foo", datatype=NamedNode('http://www.w3.org/2001/XMLSchema#string'))) in g | ||
| assert Triple(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| Literal("9.99", datatype=NamedNode('http://www.w3.org/2001/XMLSchema#decimal'))) in g | ||
| def test_parse_ntriples_mixed_literals(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> "Foo"@en-US . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> "9.99"^^<http://www.w3.org/2001/XMLSchema#decimal> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| assert Triple(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| Literal("Foo", language='en-US')) in g | ||
| assert Triple(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| Literal("9.99", datatype=NamedNode('http://www.w3.org/2001/XMLSchema#decimal'))) in g | ||
| def test_parse_ntriples_bnodes(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> _:A1 . | ||
| _:A1 <http://example.com/predicates/2> <http://example.com/objects/1> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(StringIO(test_ntriples), g) | ||
| assert len(g) == 2 | ||
| #assert Triple(NamedNode('http://example.com/objects/1'), | ||
| #NamedNode('http://example.com/predicates/1'), | ||
| #NamedNode('http://example.com/objects/2')) in g | ||
| #assert Triple(NamedNode('http://example.com/objects/2'), | ||
| #NamedNode('http://example.com/predicates/2'), | ||
| #NamedNode('http://example.com/objects/1')) in g | ||
| def test_parse_nquads_named_nodes(): | ||
| test_nquads = """<http://example.com/objects/1> <http://example.com/predicates/1> <http://example.com/objects/2> <http://example.com/graphs/1> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> <http://example.com/objects/1> <http://example.com/graphs/1> . | ||
| """ | ||
| g = Graph() | ||
| nquads_parser.parse(StringIO(test_nquads), g) | ||
| assert len(g) == 2 | ||
| assert Quad(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/graphs/1')) in g | ||
| assert Quad(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/graphs/1')) in g | ||
| def test_parse_turtle_example_1(): | ||
| ttl = """@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> . | ||
| @prefix dc: <http://purl.org/dc/elements/1.1/> . | ||
| @prefix ex: <http://example.org/stuff/1.0/> . | ||
| <http://www.w3.org/TR/rdf-syntax-grammar> | ||
| dc:title "RDF/XML Syntax Specification (Revised)" ; | ||
| ex:editor [ | ||
| ex:fullname "Dave Beckett"; | ||
| ex:homePage <http://purl.org/net/dajobe/> | ||
| ] .""" | ||
| g = Graph() | ||
| turtle_parser.parse(ttl, g) | ||
| assert len(g) == 4 | ||
| def test_jsonld_basic(): | ||
| import json | ||
| jsonld = """[{ | ||
| "@id": "http://example.com/id1", | ||
| "@type": ["http://example.com/t1"], | ||
| "http://example.com/term1": ["v1"], | ||
| "http://example.com/term2": [{"@value": "v2", "@type": "http://example.com/t2"}], | ||
| "http://example.com/term3": [{"@value": "v3", "@language": "en"}], | ||
| "http://example.com/term4": [4], | ||
| "http://example.com/term5": [50, 51] | ||
| }] | ||
| """ | ||
| g = Graph() | ||
| jsonld_parser.parse_json(json.loads(jsonld), g) | ||
| assert len(g) == 7 |
| from nose.tools import * | ||
| from pymantic.primitives import * | ||
| import random | ||
| def en(s): | ||
| return Literal(s, "en") | ||
| def test_to_curie_multi_match(): | ||
| """Test that the longest match for prefix is used""" | ||
| namespaces = {'short': "aa", 'long': "aaa"} | ||
| curie = to_curie("aaab", namespaces) | ||
| assert curie == 'long:b' | ||
| def test_simple_add(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| assert t in g | ||
| def test_simple_remove(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| g.remove(t) | ||
| assert t not in g | ||
| def test_match_VVV_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, None, None) | ||
| assert t in matches | ||
| def test_match_sVV_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(NamedNode("http://example.com"), None, None) | ||
| assert t in matches | ||
| def test_match_sVo_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(NamedNode("http://example.com"), None, en("Never!")) | ||
| assert t in matches | ||
| def test_match_spV_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"), None) | ||
| assert t in matches | ||
| def test_match_Vpo_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, NamedNode("http://purl.org/dc/terms/issued"), en("Never!")) | ||
| assert t in matches | ||
| def test_match_VVo_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, None, en("Never!")) | ||
| assert t in matches | ||
| def test_match_VpV_pattern(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),en("Never!")) | ||
| g = Graph() | ||
| g.add(t) | ||
| matches = g.match(None, NamedNode("http://purl.org/dc/terms/issued"), None) | ||
| assert t in matches | ||
| def generate_triples(n=10): | ||
| for i in range(1,n): | ||
| yield Triple(NamedNode("http://example/" + str(random.randint(1,1000))), | ||
| NamedNode("http://example/terms/" + str(random.randint(1,1000))), | ||
| Literal(random.randint(1,1000))) | ||
| def test_10000_triples(): | ||
| n = 10000 | ||
| g = Graph() | ||
| for t in generate_triples(n): | ||
| g.add(t) | ||
| assert len(g) > n * .9 | ||
| matches = g.match(NamedNode("http://example.com/42"), None, None) | ||
| matches = g.match(None, NamedNode("http://example/terms/42"), None) | ||
| matches = g.match(None, None, Literal(42)) | ||
| def test_iter_10000_triples(): | ||
| n = 10000 | ||
| g = Graph() | ||
| triples = set() | ||
| for t in generate_triples(n): | ||
| g.add(t) | ||
| triples.add(t) | ||
| assert len(g) > n * .9 | ||
| for t in g: | ||
| triples.remove(t) | ||
| assert len(triples) == 0 | ||
| # Dataset Tests | ||
| def test_add_quad(): | ||
| q = Quad(NamedNode("http://example.com/graph"),NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),Literal("Never!")) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| assert q in ds | ||
| def test_remove_quad(): | ||
| q = Quad(NamedNode("http://example.com/graph"),NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),Literal("Never!")) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| ds.remove(q) | ||
| assert q not in ds | ||
| def test_ds_len(): | ||
| n = 10 | ||
| ds = Dataset() | ||
| for q in generate_quads(n): | ||
| ds.add(q) | ||
| assert len(ds) == 10 | ||
| def test_match_ds_sVV_pattern(): | ||
| q = Quad(NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"),Literal("Never!"), | ||
| NamedNode("http://example.com/graph")) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| matches = ds.match(subject=NamedNode("http://example.com")) | ||
| assert q in matches | ||
| def test_match_ds_quad_pattern(): | ||
| q = Quad(NamedNode("http://example.com"), | ||
| NamedNode("http://purl.org/dc/terms/issued"),Literal("Never!"), | ||
| NamedNode("http://example.com/graph")) | ||
| ds = Dataset() | ||
| ds.add(q) | ||
| matches = ds.match(graph="http://example.com/graph") | ||
| assert q in matches | ||
| def test_add_graph(): | ||
| t = Triple(NamedNode("http://example.com"), NamedNode("http://purl.org/dc/terms/issued"),Literal("Never!")) | ||
| g = Graph("http://example.com/graph") | ||
| g.add(t) | ||
| ds = Dataset() | ||
| ds.add_graph(g) | ||
| assert t in ds | ||
| def generate_quads(n): | ||
| for i in range(n): | ||
| yield Quad(NamedNode("http://example/" + str(random.randint(1,1000))), | ||
| NamedNode("http://purl.org/dc/terms/" + str(random.randint(1,100))), | ||
| Literal(random.randint(1,1000)), | ||
| NamedNode("http://example/graph/"+str(random.randint(1,1000)))) | ||
| def test_10000_quads(): | ||
| n = 10000 | ||
| ds = Dataset() | ||
| for q in generate_quads(n): | ||
| ds.add(q) | ||
| assert len(ds) > n * .9 | ||
| matches = ds.match(subject=NamedNode("http://example.com/42"), | ||
| graph=NamedNode("http://example/graph/42")) | ||
| def test_iter_10000_quads(): | ||
| n = 10000 | ||
| ds = Dataset() | ||
| quads = set() | ||
| for q in generate_quads(n): | ||
| ds.add(q) | ||
| quads.add(q) | ||
| assert len(ds) > n * .9 | ||
| for quad in ds: | ||
| quads.remove(quad) | ||
| assert len(quads) == 0 | ||
| def test_interfaceName(): | ||
| assert Literal("Bob", "en").interfaceName == "Literal" | ||
| assert NamedNode().interfaceName == "NamedNode" | ||
| def test_BlankNode_id(): | ||
| b1 = BlankNode() | ||
| b2 = BlankNode() | ||
| assert b1.value != b2.value |
| import datetime | ||
| import unittest | ||
| import pymantic.rdf | ||
| import pymantic.util | ||
| from pymantic.primitives import * | ||
| from pymantic.compat import add_metaclass | ||
| XSD = Prefix('http://www.w3.org/2001/XMLSchema#') | ||
| RDF = Prefix("http://www.w3.org/1999/02/22-rdf-syntax-ns#") | ||
| class TestRDF(unittest.TestCase): | ||
| def tearDown(self): | ||
| pymantic.rdf.MetaResource._classes = {} | ||
| def testCurieURI(self): | ||
| """Test CURIE parsing of explicit URIs.""" | ||
| test_ns = {'http': Prefix('WRONG!'), | ||
| 'urn': Prefix('WRONG!'),} | ||
| self.assertEqual(pymantic.rdf.parse_curie('http://oreilly.com', test_ns), | ||
| NamedNode('http://oreilly.com')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('urn:isbn:1234567890123', test_ns), | ||
| NamedNode('urn:isbn:1234567890123')) | ||
| def testCurieDefaultPrefix(self): | ||
| """Test CURIE parsing of CURIEs in the default Prefix.""" | ||
| test_ns = {'': Prefix('foo/'), | ||
| 'wrong': Prefix('WRONG!')} | ||
| self.assertEqual(pymantic.rdf.parse_curie('bar', test_ns), | ||
| NamedNode('foo/bar')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('[bar]', test_ns), | ||
| NamedNode('foo/bar')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('baz', test_ns), | ||
| NamedNode('foo/baz')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('[aap]', test_ns), | ||
| NamedNode('foo/aap')) | ||
| def testCurieprefixes(self): | ||
| """Test CURIE parsing of CURIEs in non-default prefixes.""" | ||
| test_ns = {'': Prefix('WRONG!'), | ||
| 'foo': Prefix('foobly/'), | ||
| 'bar': Prefix('bardle/'), | ||
| 'http': Prefix('reallybadidea/'),} | ||
| self.assertEqual(pymantic.rdf.parse_curie('foo:aap', test_ns), | ||
| NamedNode('foobly/aap')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('[bar:aap]', test_ns), | ||
| NamedNode('bardle/aap')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('[foo:baz]', test_ns), | ||
| NamedNode('foobly/baz')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('bar:baz', test_ns), | ||
| NamedNode('bardle/baz')) | ||
| self.assertEqual(pymantic.rdf.parse_curie('[http://oreilly.com]', test_ns), | ||
| NamedNode('reallybadidea///oreilly.com')) | ||
| def testCurieNoSuffix(self): | ||
| """Test CURIE parsing of CURIEs with no suffix.""" | ||
| pass | ||
| def testUnparseableCuries(self): | ||
| """Test some CURIEs that shouldn't parse.""" | ||
| test_ns = {'foo': Prefix('WRONG!'),} | ||
| self.assertRaises(ValueError, pymantic.rdf.parse_curie, '[bar]', test_ns) | ||
| self.assertRaises(ValueError, pymantic.rdf.parse_curie, 'bar', test_ns) | ||
| self.assertRaises(ValueError, pymantic.rdf.parse_curie, 'bar:baz', test_ns) | ||
| self.assertRaises(ValueError, pymantic.rdf.parse_curie, '[bar:baz]', test_ns) | ||
| def testMetaResourceNothingUseful(self): | ||
| """Test applying a MetaResource to a class without anything it uses.""" | ||
| @add_metaclass(pymantic.rdf.MetaResource) | ||
| class Foo(object): | ||
| pass | ||
| def testMetaResourceprefixes(self): | ||
| """Test the handling of prefixes by MetaResource.""" | ||
| @add_metaclass(pymantic.rdf.MetaResource) | ||
| class Foo(object): | ||
| prefixes = {'foo': 'bar', 'baz': 'garply', 'meme': 'lolcatz!',} | ||
| self.assertEqual(Foo.prefixes, {'foo': Prefix('bar'), | ||
| 'baz': Prefix('garply'), | ||
| 'meme': Prefix('lolcatz!'),}) | ||
| def testMetaResourcePrefixInheritance(self): | ||
| """Test the composition of Prefix dictionaries by MetaResource.""" | ||
| @add_metaclass(pymantic.rdf.MetaResource) | ||
| class Foo(object): | ||
| prefixes = {'foo': 'bar', 'baz': 'garply', 'meme': 'lolcatz!',} | ||
| class Bar(Foo): | ||
| prefixes = {'allyourbase': 'arebelongtous!', 'bunny': 'pancake',} | ||
| self.assertEqual(Foo.prefixes, {'foo': Prefix('bar'), | ||
| 'baz': Prefix('garply'), | ||
| 'meme': Prefix('lolcatz!'),}) | ||
| self.assertEqual(Bar.prefixes, {'foo': Prefix('bar'), | ||
| 'baz': Prefix('garply'), | ||
| 'meme': Prefix('lolcatz!'), | ||
| 'allyourbase': Prefix('arebelongtous!'), | ||
| 'bunny': Prefix('pancake'),}) | ||
| def testMetaResourcePrefixInheritanceReplacement(self): | ||
| """Test the composition of Prefix dictionaries by MetaResource where | ||
| some prefixes on the parent get replaced.""" | ||
| @add_metaclass(pymantic.rdf.MetaResource) | ||
| class Foo(object): | ||
| prefixes = {'foo': 'bar', 'baz': 'garply', 'meme': 'lolcatz!',} | ||
| class Bar(Foo): | ||
| prefixes = {'allyourbase': 'arebelongtous!', 'bunny': 'pancake', | ||
| 'foo': 'notbar', 'baz': 'notgarply',} | ||
| self.assertEqual(Foo.prefixes, {'foo': Prefix('bar'), | ||
| 'baz': Prefix('garply'), | ||
| 'meme': Prefix('lolcatz!'),}) | ||
| self.assertEqual(Bar.prefixes, {'foo': Prefix('notbar'), | ||
| 'baz': Prefix('notgarply'), | ||
| 'meme': Prefix('lolcatz!'), | ||
| 'allyourbase': Prefix('arebelongtous!'), | ||
| 'bunny': Prefix('pancake'),}) | ||
| def testResourceEquality(self): | ||
| graph = Graph() | ||
| otherGraph = Graph() | ||
| testResource = pymantic.rdf.Resource(graph, 'foo') | ||
| self.assertEqual(testResource, pymantic.rdf.Resource( | ||
| graph, 'foo')) | ||
| self.assertEqual(testResource, NamedNode('foo')) | ||
| self.assertEqual(testResource, 'foo') | ||
| self.assertNotEqual(testResource, pymantic.rdf.Resource( | ||
| graph, 'bar')) | ||
| self.assertEqual(testResource, pymantic.rdf.Resource( | ||
| otherGraph, 'foo')) | ||
| self.assertNotEqual(testResource, NamedNode('bar')) | ||
| self.assertNotEqual(testResource, 'bar') | ||
| self.assertNotEqual(testResource, 42) | ||
| def testClassification(self): | ||
| """Test classification of a resource.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| test_subject = NamedNode('http://example.com/athing') | ||
| graph = Graph() | ||
| graph.add(Triple(test_subject, Offering.resolve('rdf:type'), | ||
| Offering.resolve('gr:Offering'))) | ||
| offering = pymantic.rdf.Resource.classify(graph, test_subject) | ||
| self.assert_(isinstance(offering, Offering)) | ||
| def testMulticlassClassification(self): | ||
| """Test classification of a resource that matches multiple registered | ||
| classes.""" | ||
| @pymantic.rdf.register_class('foaf:Organization') | ||
| class Organization(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'foaf': 'http://xmlns.com/foaf/0.1/', | ||
| } | ||
| @pymantic.rdf.register_class('foaf:Group') | ||
| class Group(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'foaf': 'http://xmlns.com/foaf/0.1/', | ||
| } | ||
| test_subject1 = NamedNode('http://example.com/aorganization') | ||
| test_subject2 = NamedNode('http://example.com/agroup') | ||
| test_subject3 = NamedNode('http://example.com/aorgandgroup') | ||
| graph = Graph() | ||
| graph.add(Triple(test_subject1, Organization.resolve('rdf:type'), | ||
| Organization.resolve('foaf:Organization'))) | ||
| graph.add(Triple(test_subject2, Group.resolve('rdf:type'), | ||
| Group.resolve('foaf:Group'))) | ||
| graph.add(Triple(test_subject3, Organization.resolve('rdf:type'), | ||
| Organization.resolve('foaf:Organization'))) | ||
| graph.add(Triple(test_subject3, Organization.resolve('rdf:type'), | ||
| Organization.resolve('foaf:Group'))) | ||
| organization = pymantic.rdf.Resource.classify(graph, test_subject1) | ||
| group = pymantic.rdf.Resource.classify(graph, test_subject2) | ||
| both = pymantic.rdf.Resource.classify(graph, test_subject3) | ||
| self.assert_(isinstance(organization, Organization)) | ||
| self.assertFalse(isinstance(organization, Group)) | ||
| self.assertFalse(isinstance(group, Organization)) | ||
| self.assert_(isinstance(group, Group)) | ||
| self.assert_(isinstance(both, Organization)) | ||
| self.assert_(isinstance(both, Group)) | ||
| def testStr(self): | ||
| """Test str-y serialization of Resources.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/aorganization') | ||
| test_label = Literal('Test Label', language='en') | ||
| graph.add(Triple(test_subject1, | ||
| pymantic.rdf.Resource.resolve('rdfs:label'), | ||
| test_label)) | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| self.assertEqual(r['rdfs:label'], test_label) | ||
| self.assertEqual(str(r), test_label.value) | ||
| def testGetSetDelPredicate(self): | ||
| """Test getting, setting, and deleting a multi-value predicate.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r['rdfs:example'] = set(('foo', 'bar')) | ||
| example_values = set(r['rdfs:example']) | ||
| self.assert_(Literal('foo') in example_values) | ||
| self.assert_(Literal('bar') in example_values) | ||
| self.assertEqual(len(example_values), 2) | ||
| del r['rdfs:example'] | ||
| example_values = set(r['rdfs:example']) | ||
| self.assertEqual(len(example_values), 0) | ||
| def testGetSetDelScalarPredicate(self): | ||
| """Test getting, setting, and deleting a scalar predicate.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r['rdfs:label'] = 'foo' | ||
| self.assertEqual(r['rdfs:label'], Literal('foo', language='en')) | ||
| del r['rdfs:label'] | ||
| self.assertEqual(r['rdfs:label'], None) | ||
| def testGetSetDelPredicateLanguage(self): | ||
| """Test getting, setting and deleting a multi-value predicate with an explicit language.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r['rdfs:example', 'en'] = set(('baz',)) | ||
| r['rdfs:example', 'fr'] = set(('foo', 'bar')) | ||
| example_values = set(r['rdfs:example', 'fr']) | ||
| self.assert_(Literal('foo', language='fr') in example_values) | ||
| self.assert_(Literal('bar', language='fr') in example_values) | ||
| self.assert_(Literal('baz', language='en') not in example_values) | ||
| self.assertEqual(len(example_values), 2) | ||
| example_values = set(r['rdfs:example', 'en']) | ||
| self.assert_(Literal('foo', language='fr') not in example_values) | ||
| self.assert_(Literal('bar', language='fr') not in example_values) | ||
| self.assert_(Literal('baz', language='en') in example_values) | ||
| self.assertEqual(len(example_values), 1) | ||
| del r['rdfs:example', 'fr'] | ||
| example_values = set(r['rdfs:example', 'fr']) | ||
| self.assertEqual(len(example_values), 0) | ||
| example_values = set(r['rdfs:example', 'en']) | ||
| self.assert_(Literal('foo', language='fr') not in example_values) | ||
| self.assert_(Literal('bar', language='fr') not in example_values) | ||
| self.assert_(Literal('baz', language='en') in example_values) | ||
| self.assertEqual(len(example_values), 1) | ||
| def testGetSetDelScalarPredicateLanguage(self): | ||
| """Test getting, setting, and deleting a scalar predicate with an explicit language.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r['rdfs:label'] = 'foo' | ||
| r['rdfs:label', 'fr'] = 'bar' | ||
| self.assertEqual(r['rdfs:label'], Literal('foo', language='en')) | ||
| self.assertEqual(r['rdfs:label', 'en'], Literal('foo', language='en')) | ||
| self.assertEqual(r['rdfs:label', 'fr'], Literal('bar', language='fr')) | ||
| del r['rdfs:label'] | ||
| self.assertEqual(r['rdfs:label'], None) | ||
| self.assertEqual(r['rdfs:label', 'en'], None) | ||
| self.assertEqual(r['rdfs:label', 'fr'], Literal('bar', language='fr')) | ||
| def testGetSetDelPredicateDatatype(self): | ||
| """Test getting, setting and deleting a multi-value predicate with an explicit datatype.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| now = datetime.datetime.now() | ||
| then = datetime.datetime.now() - datetime.timedelta(days=1) | ||
| number = 42 | ||
| r['rdfs:example', XSD('integer')] = set((number,)) | ||
| r['rdfs:example', XSD('dateTime')] = set((now, then,)) | ||
| example_values = set(r['rdfs:example', XSD('dateTime')]) | ||
| self.assert_(Literal(now) in example_values) | ||
| self.assert_(Literal(then) in example_values) | ||
| self.assert_(Literal(number) not in example_values) | ||
| self.assertEqual(len(example_values), 2) | ||
| example_values = set(r['rdfs:example', XSD('integer')]) | ||
| self.assert_(Literal(now) not in example_values) | ||
| self.assert_(Literal(then) not in example_values) | ||
| self.assert_(Literal(number) in example_values) | ||
| self.assertEqual(len(example_values), 1) | ||
| del r['rdfs:example', XSD('dateTime')] | ||
| example_values = set(r['rdfs:example', XSD('dateTime')]) | ||
| self.assertEqual(len(example_values), 0) | ||
| example_values = set(r['rdfs:example', XSD('integer')]) | ||
| self.assert_(Literal(now) not in example_values) | ||
| self.assert_(Literal(then) not in example_values) | ||
| self.assert_(Literal(number) in example_values) | ||
| self.assertEqual(len(example_values), 1) | ||
| def testGetSetDelScalarPredicateDatatype(self): | ||
| """Test getting, setting, and deleting a scalar predicate with an explicit datatype.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| now = datetime.datetime.now() | ||
| number = 42 | ||
| r['rdfs:label', XSD('integer')] = number | ||
| self.assertEqual(r['rdfs:label', XSD('integer')], | ||
| Literal(number, datatype=XSD('integer'))) | ||
| self.assertEqual(r['rdfs:label', XSD('dateTime')], None) | ||
| self.assertEqual(r['rdfs:label'], | ||
| Literal(number, datatype=XSD('integer'))) | ||
| r['rdfs:label', XSD('dateTime')] = now | ||
| self.assertEqual(r['rdfs:label', XSD('dateTime')], Literal(now)) | ||
| self.assertEqual(r['rdfs:label', XSD('integer')], None) | ||
| self.assertEqual(r['rdfs:label'], Literal(now)) | ||
| del r['rdfs:label', XSD('integer')] | ||
| self.assertEqual(r['rdfs:label', XSD('dateTime')], Literal(now)) | ||
| self.assertEqual(r['rdfs:label', XSD('integer')], None) | ||
| self.assertEqual(r['rdfs:label'], Literal(now)) | ||
| del r['rdfs:label', XSD('dateTime')] | ||
| self.assertEqual(r['rdfs:label'], None) | ||
| r['rdfs:label', XSD('integer')] = number | ||
| self.assertEqual(r['rdfs:label', XSD('integer')], | ||
| Literal(number, datatype=XSD('integer'))) | ||
| self.assertEqual(r['rdfs:label', XSD('dateTime')], None) | ||
| self.assertEqual(r['rdfs:label'], Literal(number, datatype=XSD('integer'))) | ||
| del r['rdfs:label'] | ||
| self.assertEqual(r['rdfs:label'], None) | ||
| def testGetSetDelPredicateType(self): | ||
| """Test getting, setting and deleting a multi-value predicate with an explicit expected RDF Class.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/offering') | ||
| test_subject2 = NamedNode('http://example.com/aposi1') | ||
| test_subject3 = NamedNode('http://example.com/aposi2') | ||
| test_subject4 = NamedNode('http://example.com/possip1') | ||
| shared_prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| @pymantic.rdf.register_class('gr:ActualProductOrServiceInstance') | ||
| class ActualProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| @pymantic.rdf.register_class('gr:ProductOrServicesSomeInstancesPlaceholder') | ||
| class PlaceholderProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| offering = Offering.new(graph, test_subject1) | ||
| aposi1 = ActualProduct.new(graph, test_subject2) | ||
| aposi2 = ActualProduct.new(graph, test_subject3) | ||
| possip1 = PlaceholderProduct.new(graph, test_subject4) | ||
| offering['gr:includes', ActualProduct] = set((aposi1, aposi2,)) | ||
| offering['gr:includes', PlaceholderProduct] = set((possip1,)) | ||
| example_values = set(offering['gr:includes', ActualProduct]) | ||
| self.assert_(aposi1 in example_values) | ||
| self.assert_(aposi2 in example_values) | ||
| self.assert_(possip1 not in example_values) | ||
| self.assertEqual(len(example_values), 2) | ||
| example_values = set(offering['gr:includes', PlaceholderProduct]) | ||
| self.assert_(aposi1 not in example_values) | ||
| self.assert_(aposi2 not in example_values) | ||
| self.assert_(possip1 in example_values) | ||
| self.assertEqual(len(example_values), 1) | ||
| del offering['gr:includes', ActualProduct] | ||
| example_values = set(offering['gr:includes', ActualProduct]) | ||
| self.assertEqual(len(example_values), 0) | ||
| example_values = set(offering['gr:includes', PlaceholderProduct]) | ||
| self.assert_(aposi1 not in example_values) | ||
| self.assert_(aposi2 not in example_values) | ||
| self.assert_(possip1 in example_values) | ||
| self.assertEqual(len(example_values), 1) | ||
| def testGetSetDelScalarPredicateType(self): | ||
| """Test getting, setting, and deleting a scalar predicate with an explicit language.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/offering') | ||
| test_subject2 = NamedNode('http://example.com/aposi') | ||
| test_subject4 = NamedNode('http://example.com/possip') | ||
| shared_prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| scalars = frozenset(('gr:includes',)) | ||
| @pymantic.rdf.register_class('gr:ActualProductOrServiceInstance') | ||
| class ActualProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| @pymantic.rdf.register_class('gr:ProductOrServicesSomeInstancesPlaceholder') | ||
| class PlaceholderProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| offering = Offering.new(graph, test_subject1) | ||
| aposi1 = ActualProduct.new(graph, test_subject2) | ||
| possip1 = PlaceholderProduct.new(graph, test_subject4) | ||
| offering['gr:includes', ActualProduct] = aposi1 | ||
| self.assertEqual(aposi1, offering['gr:includes', ActualProduct]) | ||
| self.assertEqual(None, offering['gr:includes', PlaceholderProduct]) | ||
| self.assertEqual(aposi1, offering['gr:includes']) | ||
| offering['gr:includes', PlaceholderProduct] = possip1 | ||
| self.assertEqual(None, offering['gr:includes', ActualProduct]) | ||
| self.assertEqual(possip1, offering['gr:includes', PlaceholderProduct]) | ||
| self.assertEqual(possip1, offering['gr:includes']) | ||
| del offering['gr:includes', ActualProduct] | ||
| self.assertEqual(offering['gr:includes', ActualProduct], None) | ||
| self.assertEqual(possip1, offering['gr:includes', PlaceholderProduct]) | ||
| del offering['gr:includes', PlaceholderProduct] | ||
| self.assertEqual(offering['gr:includes', ActualProduct], None) | ||
| self.assertEqual(offering['gr:includes', PlaceholderProduct], None) | ||
| offering['gr:includes', ActualProduct] = aposi1 | ||
| self.assertEqual(aposi1, offering['gr:includes', ActualProduct]) | ||
| self.assertEqual(None, offering['gr:includes', PlaceholderProduct]) | ||
| self.assertEqual(aposi1, offering['gr:includes']) | ||
| del offering['gr:includes'] | ||
| self.assertEqual(None, offering['gr:includes', ActualProduct]) | ||
| self.assertEqual(None, offering['gr:includes', PlaceholderProduct]) | ||
| self.assertEqual(None, offering['gr:includes']) | ||
| def testSetMixedScalarPredicate(self): | ||
| """Test getting and setting a scalar predicate with mixed typing.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/offering') | ||
| test_subject2 = NamedNode('http://example.com/aposi') | ||
| shared_prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| scalars = frozenset(('gr:includes',)) | ||
| @pymantic.rdf.register_class('gr:ActualProductOrServiceInstance') | ||
| class ActualProduct(pymantic.rdf.Resource): | ||
| prefixes = shared_prefixes | ||
| offering = Offering.new(graph, test_subject1) | ||
| aposi1 = ActualProduct.new(graph, test_subject2) | ||
| test_en = Literal('foo', language='en') | ||
| test_fr = Literal('le foo', language='fr') | ||
| test_dt = Literal('42', datatype = XSD('integer')) | ||
| offering['gr:includes'] = aposi1 | ||
| self.assertEqual(offering['gr:includes'], aposi1) | ||
| offering['gr:includes'] = test_dt | ||
| self.assertEqual(offering['gr:includes'], test_dt) | ||
| self.assertEqual(offering['gr:includes', ActualProduct], None) | ||
| offering['gr:includes'] = test_en | ||
| self.assertEqual(offering['gr:includes', ActualProduct], None) | ||
| self.assertEqual(offering['gr:includes', XSD('integer')], None) | ||
| self.assertEqual(offering['gr:includes'], test_en) | ||
| self.assertEqual(offering['gr:includes', 'en'], test_en) | ||
| self.assertEqual(offering['gr:includes', 'fr'], None) | ||
| offering['gr:includes'] = test_fr | ||
| self.assertEqual(offering['gr:includes', ActualProduct], None) | ||
| self.assertEqual(offering['gr:includes', XSD('integer')], None) | ||
| self.assertEqual(offering['gr:includes'], test_en) | ||
| self.assertEqual(offering['gr:includes', 'en'], test_en) | ||
| self.assertEqual(offering['gr:includes', 'fr'], test_fr) | ||
| offering['gr:includes'] = aposi1 | ||
| self.assertEqual(offering['gr:includes'], aposi1) | ||
| self.assertEqual(offering['gr:includes', XSD('integer')], None) | ||
| self.assertEqual(offering['gr:includes', 'en'], None) | ||
| self.assertEqual(offering['gr:includes', 'fr'], None) | ||
| def testResourcePredicate(self): | ||
| """Test instantiating a class when accessing a predicate.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| @pymantic.rdf.register_class('gr:PriceSpecification') | ||
| class PriceSpecification(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| test_subject1 = NamedNode('http://example.com/offering') | ||
| test_subject2 = NamedNode('http://example.com/price') | ||
| graph = Graph() | ||
| graph.add(Triple(test_subject1, Offering.resolve('rdf:type'), | ||
| Offering.resolve('gr:Offering'))) | ||
| graph.add(Triple(test_subject1, Offering.resolve('gr:hasPriceSpecification'), | ||
| test_subject2)) | ||
| graph.add(Triple(test_subject2, PriceSpecification.resolve('rdf:type'), | ||
| PriceSpecification.resolve('gr:PriceSpecification'))) | ||
| offering = Offering(graph, test_subject1) | ||
| price_specification = PriceSpecification(graph, test_subject2) | ||
| prices = set(offering['gr:hasPriceSpecification']) | ||
| self.assertEqual(len(prices), 1) | ||
| self.assert_(price_specification in prices) | ||
| def testResourcePredicateAssignment(self): | ||
| """Test assigning an instance of a resource to a predicate.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| @pymantic.rdf.register_class('gr:PriceSpecification') | ||
| class PriceSpecification(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| test_subject1 = NamedNode('http://example.com/offering') | ||
| test_subject2 = NamedNode('http://example.com/price') | ||
| graph = Graph() | ||
| graph.add(Triple(test_subject1, Offering.resolve('rdf:type'), | ||
| Offering.resolve('gr:Offering'))) | ||
| graph.add(Triple(test_subject2, PriceSpecification.resolve('rdf:type'), | ||
| PriceSpecification.resolve('gr:PriceSpecification'))) | ||
| offering = Offering(graph, test_subject1) | ||
| price_specification = PriceSpecification(graph, test_subject2) | ||
| before_prices = set(offering['gr:hasPriceSpecification']) | ||
| self.assertEqual(len(before_prices), 0) | ||
| offering['gr:hasPriceSpecification'] = price_specification | ||
| after_prices = set(offering['gr:hasPriceSpecification']) | ||
| self.assertEqual(len(after_prices), 1) | ||
| self.assert_(price_specification in after_prices) | ||
| def testNewResource(self): | ||
| """Test creating a new resource.""" | ||
| graph = Graph() | ||
| @pymantic.rdf.register_class('foaf:Person') | ||
| class Person(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'foaf': 'http://xmlns.com/foaf/0.1/', | ||
| } | ||
| test_subject = NamedNode('http://example.com/') | ||
| p = Person.new(graph, test_subject) | ||
| def testGetAllResourcesInGraph(self): | ||
| """Test iterating over all of the resources in a graph with a | ||
| particular RDF type.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| graph = Graph() | ||
| test_subject_base = NamedNode('http://example.com/') | ||
| for i in range(10): | ||
| graph.add(Triple(NamedNode(test_subject_base + str(i)), | ||
| Offering.resolve('rdf:type'), | ||
| Offering.resolve('gr:Offering'))) | ||
| offerings = Offering.in_graph(graph) | ||
| self.assertEqual(len(offerings), 10) | ||
| for i in range(10): | ||
| this_subject = NamedNode(test_subject_base + str(i)) | ||
| offering = Offering(graph, this_subject) | ||
| self.assert_(offering in offerings) | ||
| def testContained(self): | ||
| """Test in against a multi-value predicate.""" | ||
| graph = Graph() | ||
| test_subject1 = NamedNode('http://example.com/') | ||
| r = pymantic.rdf.Resource(graph, test_subject1) | ||
| r['rdfs:example'] = set(('foo', 'bar')) | ||
| self.assert_('rdfs:example' in r) | ||
| self.assertFalse(('rdfs:example', 'en') in r) | ||
| self.assertFalse(('rdfs:example', 'fr') in r) | ||
| self.assertFalse('rdfs:examplefoo' in r) | ||
| del r['rdfs:example'] | ||
| self.assertFalse('rdfs:example' in r) | ||
| self.assertFalse(('rdfs:example', 'en') in r) | ||
| self.assertFalse(('rdfs:example', 'fr') in r) | ||
| self.assertFalse('rdfs:examplefoo' in r) | ||
| r['rdfs:example', 'fr'] = 'le foo' | ||
| def testBack(self): | ||
| """Test following a predicate backwards.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| @pymantic.rdf.register_class('gr:PriceSpecification') | ||
| class PriceSpecification(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| graph = Graph() | ||
| offering1 = Offering.new(graph, 'http://example.com/offering1') | ||
| offering2 = Offering.new(graph, 'http://example.com/offering2') | ||
| offering3 = Offering.new(graph, 'http://example.com/offering3') | ||
| price1 = PriceSpecification.new(graph, 'http://example.com/price1') | ||
| price2 = PriceSpecification.new(graph, 'http://example.com/price2') | ||
| price3 = PriceSpecification.new(graph, 'http://example.com/price3') | ||
| offering1['gr:hasPriceSpecification'] = set((price1, price2, price3,)) | ||
| offering2['gr:hasPriceSpecification'] = set((price2, price3,)) | ||
| self.assertEqual(set(price1.object_of(predicate='gr:hasPriceSpecification')), | ||
| set((offering1,))) | ||
| self.assertEqual(set(price2.object_of(predicate='gr:hasPriceSpecification')), | ||
| set((offering1,offering2,))) | ||
| self.assertEqual(set(price3.object_of(predicate='gr:hasPriceSpecification')), | ||
| set((offering1,offering2,))) | ||
| def testGetAllValues(self): | ||
| """Test getting all values for a predicate.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| en = Literal('foo', language='en') | ||
| fr = Literal('bar', language='fr') | ||
| es = Literal('baz', language='es') | ||
| xsdstring = Literal('aap') | ||
| xsddecimal = Literal('9.95', datatype=XSD('decimal')) | ||
| graph = Graph() | ||
| offering = Offering.new(graph, 'http://example.com/offering') | ||
| offering['gr:description'] = set((en, fr, es,)) | ||
| self.assertEqual(frozenset(offering['gr:description']), frozenset((en,fr,es,))) | ||
| self.assertEqual(frozenset(offering['gr:description', 'en']), frozenset((en,))) | ||
| self.assertEqual(frozenset(offering['gr:description', 'fr']), frozenset((fr,))) | ||
| self.assertEqual(frozenset(offering['gr:description', 'es']), frozenset((es,))) | ||
| self.assertEqual(frozenset(offering['gr:description', None]), | ||
| frozenset((en, fr, es,))) | ||
| offering['gr:description'] = set((xsdstring, xsddecimal,)) | ||
| self.assertEqual(frozenset(offering['gr:description', '']), | ||
| frozenset((xsdstring,))) | ||
| self.assertEqual(frozenset(offering['gr:description', XSD('string')]), | ||
| frozenset((xsdstring,))) | ||
| self.assertEqual(frozenset(offering['gr:description', XSD('decimal')]), | ||
| frozenset((xsddecimal,))) | ||
| self.assertEqual(frozenset(offering['gr:description', None]), | ||
| frozenset((xsdstring, xsddecimal,))) | ||
| offering['gr:description'] = set((en, fr, es, xsdstring, xsddecimal,)) | ||
| self.assertEqual(frozenset(offering['gr:description']), frozenset((en, fr, es, xsdstring, xsddecimal,))) | ||
| self.assertEqual(frozenset(offering['gr:description', 'en']), frozenset((en,))) | ||
| self.assertEqual(frozenset(offering['gr:description', 'fr']), frozenset((fr,))) | ||
| self.assertEqual(frozenset(offering['gr:description', 'es']), frozenset((es,))) | ||
| self.assertEqual(frozenset(offering['gr:description', '']), | ||
| frozenset((xsdstring,))) | ||
| self.assertEqual(frozenset(offering['gr:description', XSD('string')]), | ||
| frozenset((xsdstring,))) | ||
| self.assertEqual(frozenset(offering['gr:description', XSD('decimal')]), | ||
| frozenset((xsddecimal,))) | ||
| self.assertEqual(frozenset(offering['gr:description', None]), | ||
| frozenset((en, fr, es, xsdstring, xsddecimal,))) | ||
| def testGetAllValuesScalar(self): | ||
| """Test getting all values for a predicate.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| scalars = frozenset(('gr:description',)) | ||
| en = Literal('foo', language='en') | ||
| fr = Literal('bar', language='fr') | ||
| es = Literal('baz', language='es') | ||
| graph = Graph() | ||
| offering = Offering.new(graph, 'http://example.com/offering') | ||
| offering['gr:description'] = en | ||
| offering['gr:description'] = fr | ||
| offering['gr:description'] = es | ||
| self.assertEqual(offering['gr:description'], en) | ||
| self.assertEqual(offering['gr:description', 'en'], en) | ||
| self.assertEqual(offering['gr:description', 'fr'], fr) | ||
| self.assertEqual(offering['gr:description', 'es'], es) | ||
| self.assertEqual(frozenset(offering['gr:description', None]), | ||
| frozenset((en, fr, es,))) | ||
| def testErase(self): | ||
| """Test erasing an object from the graph.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| scalars = frozenset(('gr:name',)) | ||
| graph = Graph() | ||
| offering1 = Offering.new(graph, 'http://example.com/offering1') | ||
| offering2 = Offering.new(graph, 'http://example.com/offering2') | ||
| offering1['gr:name'] = 'Foo' | ||
| offering1['gr:description'] = set(('Baz', 'Garply',)) | ||
| offering2['gr:name'] = 'Bar' | ||
| offering2['gr:description'] = set(('Aap', 'Mies',)) | ||
| self.assert_(offering1.is_a()) | ||
| self.assert_(offering2.is_a()) | ||
| offering1.erase() | ||
| self.assertFalse(offering1.is_a()) | ||
| self.assert_(offering2.is_a()) | ||
| self.assertFalse(offering1['gr:name']) | ||
| self.assertFalse(frozenset(offering1['gr:description'])) | ||
| self.assertEqual(offering2['gr:name'], Literal('Bar', language='en')) | ||
| def testUnboundClass(self): | ||
| """Test classifying objects with one or more unbound classes.""" | ||
| @pymantic.rdf.register_class('gr:Offering') | ||
| class Offering(pymantic.rdf.Resource): | ||
| prefixes = { | ||
| 'gr': 'http://purl.org/goodrelations/', | ||
| } | ||
| graph = Graph() | ||
| funky_class = NamedNode('http://example.com/AFunkyClass') | ||
| funky_subject = NamedNode('http://example.com/aFunkyResource') | ||
| offering1 = Offering.new(graph, 'http://example.com/offering1') | ||
| graph.add(Triple(offering1.subject, RDF('type'), funky_class)) | ||
| self.assertEqual(type(pymantic.rdf.Resource.classify(graph, offering1.subject)), | ||
| Offering) | ||
| graph.add(Triple(funky_subject, RDF('type'), funky_class)) | ||
| self.assertEqual(type(pymantic.rdf.Resource.classify(graph, funky_subject)), | ||
| pymantic.rdf.Resource) |
| from unittest import TestCase | ||
| from pymantic.compat.moves import cStringIO | ||
| from nose.tools import * | ||
| from pymantic.parsers import * | ||
| from pymantic.primitives import * | ||
| from pymantic.serializers import * | ||
| def test_parse_ntriples_named_nodes(): | ||
| test_ntriples = """<http://example.com/objects/1> <http://example.com/predicates/1> <http://example.com/objects/2> . | ||
| <http://example.com/objects/2> <http://example.com/predicates/2> <http://example.com/objects/1> . | ||
| """ | ||
| g = Graph() | ||
| ntriples_parser.parse(cStringIO(test_ntriples), g) | ||
| f = cStringIO() | ||
| serialize_ntriples(g, f) | ||
| f.seek(0) | ||
| g2 = Graph() | ||
| ntriples_parser.parse(f, g2) | ||
| assert len(g) == 2 | ||
| assert Triple(NamedNode('http://example.com/objects/1'), | ||
| NamedNode('http://example.com/predicates/1'), | ||
| NamedNode('http://example.com/objects/2')) in g2 | ||
| assert Triple(NamedNode('http://example.com/objects/2'), | ||
| NamedNode('http://example.com/predicates/2'), | ||
| NamedNode('http://example.com/objects/1')) in g2 | ||
| class TestTurtleRepresentation(TestCase): | ||
| def setUp(self): | ||
| from pymantic.serializers import turtle_repr | ||
| self.turtle_repr = turtle_repr | ||
| import pymantic.primitives | ||
| self.primitives = pymantic.primitives | ||
| self.profile = self.primitives.Profile() | ||
| def test_integer(self): | ||
| lit = self.primitives.Literal(value='42', datatype=self.profile.resolve('xsd:integer')) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '42') | ||
| def test_decimal(self): | ||
| lit = self.primitives.Literal(value='4.2', datatype=self.profile.resolve('xsd:decimal')) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '4.2') | ||
| def test_double(self): | ||
| lit = self.primitives.Literal(value='4.2e1', datatype=self.profile.resolve('xsd:double')) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '4.2e1') | ||
| def test_boolean(self): | ||
| lit = self.primitives.Literal(value='true', datatype=self.profile.resolve('xsd:boolean')) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, 'true') | ||
| def test_bare_string(self): | ||
| lit = self.primitives.Literal(value='Foo', datatype=self.profile.resolve('xsd:string')) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '"Foo"') | ||
| def test_language_string(self): | ||
| lit = self.primitives.Literal(value='Foo', language="en") | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '"Foo"@en') | ||
| def test_random_datatype_bare_url(self): | ||
| lit = self.primitives.Literal(value='Foo', datatype=self.primitives.NamedNode("http://example.com/garply")) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '"Foo"^<http://example.com/garply>') | ||
| def test_random_datatype_prefixed(self): | ||
| self.profile.setPrefix('ex', self.primitives.NamedNode('http://example.com/')) | ||
| lit = self.primitives.Literal(value='Foo', datatype=self.primitives.NamedNode("http://example.com/garply")) | ||
| name = self.turtle_repr(node = lit, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '"Foo"^ex:garply') | ||
| def test_named_node_bare(self): | ||
| node = self.primitives.NamedNode('http://example.com/foo') | ||
| name = self.turtle_repr(node = node, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, '<http://example.com/foo>') | ||
| def test_named_node_prefixed(self): | ||
| self.profile.setPrefix('ex', self.primitives.NamedNode('http://example.com/')) | ||
| node = self.primitives.NamedNode('http://example.com/foo') | ||
| name = self.turtle_repr(node = node, profile = self.profile, name_map = None, bnode_name_maker = None) | ||
| self.assertEqual(name, 'ex:foo') | ||
| def test_named_node_with_hash_base(self): | ||
| node = self.primitives.NamedNode('https://example.com/foo#bar') | ||
| name = self.turtle_repr(node = node, profile = self.profile, name_map = None, bnode_name_maker = None, | ||
| base='https://example.com/foo#') | ||
| self.assertEqual(name, '<#bar>') | ||
| def test_named_node_with_path_base(self): | ||
| node = self.primitives.NamedNode('https://example.com/foo') | ||
| name = self.turtle_repr(node = node, profile = self.profile, name_map = None, bnode_name_maker = None, | ||
| base='https://example.com/') | ||
| self.assertEqual(name, '<foo>') | ||
| def test_named_node_with_multi_path_base(self): | ||
| node = self.primitives.NamedNode('https://example.com/foo/bar') | ||
| name = self.turtle_repr(node = node, profile = self.profile, name_map = None, bnode_name_maker = None, | ||
| base='https://example.com/') | ||
| self.assertEqual(name, '<foo/bar>') | ||
| class TestTurtleSerializer(TestCase): | ||
| def setUp(self): | ||
| from pymantic.parsers import turtle_parser | ||
| from pymantic.serializers import serialize_turtle | ||
| self.turtle_parser = turtle_parser | ||
| self.serialize_turtle = serialize_turtle | ||
| import pymantic.primitives | ||
| self.primitives = pymantic.primitives | ||
| self.profile = self.primitives.Profile() | ||
| def testSimpleSerialization(self): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" . | ||
| example:bar dc:title "Bar" . | ||
| example:baz dc:subject example:foo .""" | ||
| graph = self.turtle_parser.parse(basic_turtle) | ||
| f = cStringIO() | ||
| self.profile.setPrefix('ex', self.primitives.NamedNode('http://example.com/')) | ||
| self.profile.setPrefix('dc', self.primitives.NamedNode('http://purl.org/dc/terms/')) | ||
| self.serialize_turtle(graph = graph, f = f, profile = self.profile) | ||
| f.seek(0) | ||
| graph2 = self.turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| self.assertEqual(f.read().strip(), """ | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:bar dc:title "Bar" ; | ||
| . | ||
| ex:baz dc:subject ex:foo ; | ||
| . | ||
| ex:foo dc:title "Foo" ; | ||
| . | ||
| """.strip()) | ||
| def testBaseSerialization(self): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" . | ||
| example:bar dc:title "Bar" . | ||
| example:baz dc:subject example:foo .""" | ||
| graph = self.turtle_parser.parse(basic_turtle) | ||
| f = cStringIO() | ||
| self.profile.setPrefix('dc', self.primitives.NamedNode('http://purl.org/dc/terms/')) | ||
| self.serialize_turtle( | ||
| graph = graph, f = f, profile = self.profile, | ||
| base = 'http://example.com/', | ||
| ) | ||
| f.seek(0) | ||
| graph2 = self.turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| self.assertEqual(f.read().strip(), """@base <http://example.com/> . | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| <bar> dc:title "Bar" ; | ||
| . | ||
| <baz> dc:subject <foo> ; | ||
| . | ||
| <foo> dc:title "Foo" ; | ||
| . | ||
| """.strip()) | ||
| def testBaseAndPrefixSerialization(self): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" . | ||
| example:bar dc:title "Bar" . | ||
| example:baz dc:subject example:foo .""" | ||
| graph = self.turtle_parser.parse(basic_turtle) | ||
| f = cStringIO() | ||
| self.profile.setPrefix('ex', self.primitives.NamedNode('http://example.com/')) | ||
| self.profile.setPrefix('dc', self.primitives.NamedNode('http://purl.org/dc/terms/')) | ||
| self.serialize_turtle( | ||
| graph = graph, f = f, profile = self.profile, | ||
| base = 'http://example.com/', | ||
| ) | ||
| f.seek(0) | ||
| graph2 = self.turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| self.assertEqual(f.read().strip(), """@base <http://example.com/> . | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:bar dc:title "Bar" ; | ||
| . | ||
| ex:baz dc:subject ex:foo ; | ||
| . | ||
| ex:foo dc:title "Foo" ; | ||
| . | ||
| """.strip()) | ||
| def testMultiplePredicates(self): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:title "Foo" ; | ||
| dc:author "Bar" ; | ||
| dc:subject example:yesfootoo . | ||
| example:garply dc:title "Garply" ; | ||
| dc:author "Baz" ; | ||
| dc:subject example:thegarply .""" | ||
| graph = self.turtle_parser.parse(basic_turtle) | ||
| f = cStringIO() | ||
| self.profile.setPrefix('ex', self.primitives.NamedNode('http://example.com/')) | ||
| self.profile.setPrefix('dc', self.primitives.NamedNode('http://purl.org/dc/terms/')) | ||
| self.serialize_turtle(graph = graph, f = f, profile = self.profile) | ||
| f.seek(0) | ||
| graph2 = self.turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| self.assertEqual(f.read().strip(), """ | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:foo dc:author "Bar" ; | ||
| dc:subject ex:yesfootoo ; | ||
| dc:title "Foo" ; | ||
| . | ||
| ex:garply dc:author "Baz" ; | ||
| dc:subject ex:thegarply ; | ||
| dc:title "Garply" ; | ||
| .""".strip()) | ||
| def testListSerialization(self): | ||
| basic_turtle = """@prefix dc: <http://purl.org/dc/terms/> . | ||
| @prefix example: <http://example.com/> . | ||
| example:foo dc:author ("Foo" "Bar" "Baz") .""" | ||
| graph = self.turtle_parser.parse(basic_turtle) | ||
| f = cStringIO() | ||
| self.profile.setPrefix('ex', self.primitives.NamedNode('http://example.com/')) | ||
| self.profile.setPrefix('dc', self.primitives.NamedNode('http://purl.org/dc/terms/')) | ||
| self.serialize_turtle(graph = graph, f = f, profile = self.profile) | ||
| f.seek(0) | ||
| graph2 = self.turtle_parser.parse(f.read()) | ||
| f.seek(0) | ||
| self.assertEqual(f.read().strip(), """ | ||
| @prefix xsd: <http://www.w3.org/2001/XMLSchema#> . | ||
| @prefix ex: <http://example.com/> . | ||
| @prefix dc: <http://purl.org/dc/terms/> . | ||
| ex:foo dc:author ("Foo" "Bar" "Baz") ; | ||
| .""".strip()) |
| import os.path | ||
| import unittest | ||
| from betamax import Betamax | ||
| from pymantic.sparql import SPARQLServer, SPARQLQueryException | ||
| with Betamax.configure() as config: | ||
| config.cassette_library_dir = os.path.join( | ||
| os.path.dirname(__file__), 'playbacks', | ||
| ) | ||
| class TestSparql(unittest.TestCase): | ||
| def testMockSPARQL(self): | ||
| """Test a SPARQL query against a mocked-up endpoint.""" | ||
| test_query = """PREFIX dc: <http://purl.org/dc/terms/> | ||
| SELECT ?product ?title WHERE { ?product dc:title ?title } LIMIT 10""" | ||
| sparql = SPARQLServer( | ||
| 'http://localhost/tenuki/sparql', post_queries=True, | ||
| ) | ||
| with Betamax(sparql.s).use_cassette('mock_sparql', record='none'): | ||
| results = sparql.query(test_query) | ||
| self.assertEqual(results['results']['bindings'][0]['product']['value'], | ||
| 'test_product') | ||
| self.assertEqual(results['results']['bindings'][0]['title']['value'], | ||
| 'Test Title') | ||
| def testMockSPARQLError(self): | ||
| """Test a SPARQL query against a mocked-up endpoint.""" | ||
| test_query = """PREFIX dc: <http://purl.org/dc/terms/> | ||
| SELECT ?product ?title WHERE { ?product dc:title ?title } LIMIT 10""" | ||
| sparql = SPARQLServer( | ||
| 'http://localhost/tenuki/sparql', post_queries=True, | ||
| ) | ||
| with Betamax(sparql.s).use_cassette( | ||
| 'mock_sparql_error', record='none', | ||
| ): | ||
| self.assertRaises(SPARQLQueryException, sparql.query, test_query) |
| import os.path | ||
| from pymantic.compat.moves.urllib.parse import urljoin | ||
| import unittest | ||
| from pymantic.parsers import turtle_parser, ntriples_parser | ||
| import pymantic.rdf as rdf | ||
| from pymantic.compat import text_type | ||
| turtle_tests_url =\ | ||
| 'http://www.w3.org/2013/TurtleTests/' | ||
| prefixes = { | ||
| 'mf': 'http://www.w3.org/2001/sw/DataAccess/tests/test-manifest#', | ||
| 'qt': 'http://www.w3.org/2001/sw/DataAccess/tests/test-query#', | ||
| 'rdft': 'http://www.w3.org/ns/rdftest#', | ||
| } | ||
| def isomorph_triple(triple): | ||
| from pymantic.primitives import ( | ||
| BlankNode, | ||
| Literal, | ||
| NamedNode, | ||
| ) | ||
| if isinstance(triple.subject, BlankNode): | ||
| triple = triple._replace(subject=None) | ||
| if isinstance(triple.object, BlankNode): | ||
| triple = triple._replace(object=None) | ||
| if isinstance(triple.object, Literal) and triple.object.datatype is None: | ||
| triple = triple._replace(object=triple.object._replace( | ||
| datatype=NamedNode('http://www.w3.org/2001/XMLSchema#string'))) | ||
| return triple | ||
| def isomorph(graph): | ||
| return { | ||
| isomorph_triple(t) for t in graph._triples | ||
| } | ||
| @rdf.register_class('mf:Manifest') | ||
| class Manifest(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(('rdfs:comment', 'mf:entries')) | ||
| @rdf.register_class('rdft:TestTurtleEval') | ||
| class TestTurtleEval(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(('mf:name', 'rdfs:comment', 'mf:action', 'mf:result')) | ||
| def execute(self, test_case): | ||
| with open(text_type(self['mf:action']), 'rb') as f: | ||
| in_data = f.read() | ||
| with open(text_type(self['mf:result']), 'rb') as f: | ||
| compare_data = f.read() | ||
| base = urljoin(turtle_tests_url, | ||
| os.path.basename(text_type(self['mf:action']))) | ||
| test_graph = turtle_parser.parse(in_data, base=base) | ||
| compare_graph = ntriples_parser.parse_string(compare_data) | ||
| test_case.assertEqual( | ||
| isomorph(test_graph), | ||
| isomorph(compare_graph), | ||
| ) | ||
| @rdf.register_class('rdft:TestTurtlePositiveSyntax') | ||
| class TestTurtlePositiveSyntax(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(('mf:name', 'rdfs:comment', 'mf:action')) | ||
| def execute(self, test_case): | ||
| with open(text_type(self['mf:action']), 'rb') as f: | ||
| in_data = f.read() | ||
| base = urljoin(turtle_tests_url, | ||
| os.path.basename(text_type(self['mf:action']))) | ||
| test_graph = turtle_parser.parse(in_data, base=base) | ||
| @rdf.register_class('rdft:TestTurtleNegativeSyntax') | ||
| class TestTurtleNegativeSyntax(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(('mf:name', 'rdfs:comment', 'mf:action')) | ||
| def execute(self, test_case): | ||
| with open(text_type(self['mf:action']), 'rb') as f: | ||
| in_data = f.read() | ||
| base = urljoin(turtle_tests_url, | ||
| os.path.basename(text_type(self['mf:action']))) | ||
| with test_case.assertRaises(Exception): | ||
| turtle_parser.parse(in_data, base=base) | ||
| @rdf.register_class('rdft:TestTurtleNegativeEval') | ||
| class TestTurtleNegativeEval(rdf.Resource): | ||
| prefixes = prefixes | ||
| scalars = frozenset(('mf:name', 'rdfs:comment', 'mf:action')) | ||
| def execute(self, test_case): | ||
| with open(text_type(self['mf:action']), 'rb') as f: | ||
| in_data = f.read() | ||
| base = urljoin(turtle_tests_url, | ||
| os.path.basename(text_type(self['mf:action']))) | ||
| with test_case.assertRaises(Exception): | ||
| turtle_parser.parse(in_data, base=base) | ||
| base = os.path.join(os.path.dirname(__file__), 'TurtleTests/') | ||
| manifest_name = os.path.join(base, 'manifest.ttl') | ||
| with open(manifest_name, 'rb') as f: | ||
| manifest_turtle = f.read() | ||
| manifest_graph = turtle_parser.parse(manifest_turtle, base=base) | ||
| manifest = Manifest(manifest_graph, base) | ||
| class TurtleTests(unittest.TestCase): | ||
| @classmethod | ||
| def _make_test_case(cls, test_case): | ||
| def test_method(self): | ||
| return test_case.execute(self) | ||
| test_name = 'test_' + test_case['mf:name'].value.replace('-', '_') | ||
| test_method.__name__ = str(test_name) | ||
| test_method.func_name = test_name.encode('utf8') | ||
| test_method.func_doc = u'{} ({})'.format( | ||
| test_case['rdfs:comment'].value, | ||
| test_name, | ||
| ) | ||
| setattr(cls, test_name, test_method) | ||
| for test_case in list(manifest['mf:entries'].as_(rdf.List)): | ||
| TurtleTests._make_test_case(test_case) |
| from nose.tools import * | ||
| from pymantic.util import * | ||
| def test_normalize_iri_no_escapes(): | ||
| uri = 'http://example.com/foo/bar?garply=aap&maz=bies' | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == u'http://example.com/foo/bar?garply=aap&maz=bies' | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri | ||
| def test_normalize_iri_escaped_slash(): | ||
| uri = 'http://example.com/foo%2Fbar?garply=aap&maz=bies' | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == u'http://example.com/foo%2Fbar?garply=aap&maz=bies' | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri | ||
| def test_normalize_iri_escaped_ampersand(): | ||
| uri = 'http://example.com/foo/bar?garply=aap%26yak&maz=bies' | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == u'http://example.com/foo/bar?garply=aap%26yak&maz=bies' | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri | ||
| def test_normalize_iri_escaped_international(): | ||
| uri = 'http://example.com/foo/bar?garply=aap&maz=bi%C3%89s' | ||
| normalized = normalize_iri(uri) | ||
| assert normalized == u'http://example.com/foo/bar?garply=aap&maz=bi\u00C9s' | ||
| assert normalized == normalize_iri(normalized) | ||
| assert quote_normalized_iri(normalized) == uri |
| """A complete list of URI schemes registered as of Sept 26th, 2008, used when | ||
| parsing CURIEs to differentiate explicit URIs from CURIEs.""" | ||
| schemes = ['aaa', | ||
| 'aaas', | ||
| 'acap', | ||
| 'cap', | ||
| 'cid', | ||
| 'crid', | ||
| 'data', | ||
| 'dav', | ||
| 'dict', | ||
| 'dns', | ||
| 'fax', | ||
| 'file', | ||
| 'ftp', | ||
| 'go', | ||
| 'gopher', | ||
| 'h323', | ||
| 'http', | ||
| 'https', | ||
| 'icap', | ||
| 'im', | ||
| 'imap', | ||
| 'info', | ||
| 'ipp', | ||
| 'iris', | ||
| 'iris.beep', | ||
| 'iris.xpc', | ||
| 'iris.xpcs', | ||
| 'iris.lwz', | ||
| 'ldap', | ||
| 'mailto', | ||
| 'mid', | ||
| 'modem', | ||
| 'msrp', | ||
| 'msrps', | ||
| 'mtqp', | ||
| 'mupdate', | ||
| 'news', | ||
| 'nfs', | ||
| 'nntp', | ||
| 'opaquelocktoken', | ||
| 'pop', | ||
| 'pres', | ||
| 'rtsp', | ||
| 'service', | ||
| 'shttp', | ||
| 'sip', | ||
| 'sips', | ||
| 'snmp', | ||
| 'soap.beep', | ||
| 'soap.beeps', | ||
| 'tag', | ||
| 'tel', | ||
| 'telnet', | ||
| 'tftp', | ||
| 'thismessage', | ||
| 'tip', | ||
| 'tv', | ||
| 'urn', | ||
| 'vemmi', | ||
| 'xmlrpc.beep', | ||
| 'xmlrpc.beeps', | ||
| 'xmpp', | ||
| 'z39.50r', | ||
| 'z39.50s', | ||
| 'afs', | ||
| 'dtn', | ||
| 'iax', | ||
| 'mailserver', | ||
| 'pack', | ||
| 'tn3270', | ||
| 'prospero', | ||
| 'snews', | ||
| 'videotex', | ||
| 'wais',] |
-117
| """Utility functions used throughout pymantic.""" | ||
| __all__ = ['en', 'de', 'one_or_none', 'normalize_iri', 'quote_normalized_iri',] | ||
| import re | ||
| from .compat.moves.urllib.parse import quote | ||
| from .compat import int2byte | ||
| def en(value): | ||
| """Returns an RDF literal from the en language for the given value.""" | ||
| from pymantic.primitives import Literal | ||
| return Literal(value, language='en') | ||
| def de(value): | ||
| """Returns an RDF literal from the de language for the given value.""" | ||
| from pymantic.primitives import Literal | ||
| return Literal(value, language='de') | ||
| def one_or_none(values): | ||
| """Fetch the first value from values, or None if values is empty. Raises | ||
| ValueError if values has more than one thing in it.""" | ||
| if not values: | ||
| return None | ||
| if len(values) > 1: | ||
| raise ValueError('Got more than one value.') | ||
| return values[0] | ||
| percent_encoding_re = re.compile(r'(?:%(?![01][0-9a-fA-F])(?!20)[a-fA-F0-9][a-fA-F0-9])+') | ||
| reserved_in_iri = ["%", ":", "/", "?", "#", "[", "]", "@", "!", "$", "&", "'",\ | ||
| "(", ")", "*", "+", ",", ";", "="] | ||
| def percent_decode(regmatch): | ||
| encoded = b'' | ||
| for group in regmatch.group(0)[1:].split('%'): | ||
| encoded += int2byte(int(group, 16)) | ||
| uni = encoded.decode('utf-8') | ||
| for res in reserved_in_iri: | ||
| uni = uni.replace(res, '%%%02X' % ord(res)) | ||
| return uni | ||
| def normalize_iri(iri): | ||
| """Normalize an IRI using the Case Normalization (5.3.2.1) and | ||
| Percent-Encoding Normalization (5.3.2.3) from RFC 3987. The IRI should be a | ||
| unicode object.""" | ||
| return percent_encoding_re.sub(percent_decode, iri) | ||
| def percent_encode(char): | ||
| from .compat import ordbyte | ||
| return ''.join('%%%02X' % ordbyte(char) for char in char.encode('utf-8')) | ||
| def quote_normalized_iri(normalized_iri): | ||
| """Percent-encode a normalized IRI; IE, all reserved characters are presumed | ||
| to be themselves and not percent encoded. All other unsafe characters are | ||
| percent-encoded.""" | ||
| normalized_uri = ''.join(percent_encode(char) if ord(char) > 127 else char for\ | ||
| char in normalized_iri) | ||
| return quote(normalized_uri, safe=''.join(reserved_in_iri)) | ||
| def smart_urljoin(base, url): | ||
| """urljoin, only an empty fragment from the relative(?) URL will be | ||
| preserved. | ||
| """ | ||
| from .compat.moves.urllib.parse import urljoin | ||
| joined = urljoin(base, url) | ||
| if url.endswith('#') and not joined.endswith('#'): | ||
| joined += '#' | ||
| return joined | ||
| def grouper(iterable, n, fillvalue=None): | ||
| "Collect data into fixed-length chunks or blocks" | ||
| # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx" | ||
| from .compat.moves import zip_longest | ||
| args = [iter(iterable)] * n | ||
| return zip_longest(*args, fillvalue=fillvalue) | ||
| ESCAPE_MAP = { | ||
| 't': '\t', | ||
| 'b': '\b', | ||
| 'n': '\n', | ||
| 'r': '\r', | ||
| 'f': '\f', | ||
| '"': '\"', | ||
| "'": "\'", | ||
| "\\": "\\", | ||
| } | ||
| ECHAR_MAP = { | ||
| v: '\\' + k for k, v in ESCAPE_MAP.items() | ||
| } | ||
| def process_escape(escape): | ||
| from .compat import unichr | ||
| escape = escape.group(0)[1:] | ||
| if escape[0] in ('u', 'U'): | ||
| return unichr(int(escape[1:], 16)) | ||
| else: | ||
| return ESCAPE_MAP.get(escape[0], escape[0]) | ||
| def decode_literal(literal): | ||
| return re.sub( | ||
| r'\\u[a-fA-F0-9]{4}|\\U[a-fA-F0-9]{8}|\\[^uU]', | ||
| process_escape, | ||
| literal, | ||
| ) |
| from pymantic.rdf import Resource, register_class | ||
| SKOS_NS = "http://www.w3.org/2004/02/skos/core#" | ||
| NS_DICT = dict(skos = SKOS_NS) | ||
| class SKOSResource(Resource): | ||
| namespaces = NS_DICT | ||
| scaler = ['skos:prefLabel'] | ||
| @register_class("skos:Concept") | ||
| class Concept(SKOSResource): | ||
| pass | ||
| @register_class("skos:ConceptScheme") | ||
| class ConceptScheme(SKOSResource): | ||
| pass |
-60
| from setuptools import setup, find_packages | ||
| from pymantic import version | ||
| tests_require = [ | ||
| 'betamax', | ||
| ] | ||
| testing_extras = tests_require + [ | ||
| 'nose', | ||
| 'coverage', | ||
| ] | ||
| from io import open | ||
| f = open('README.rst', mode='r', encoding='utf8') | ||
| setup(name='pymantic', | ||
| version=version, | ||
| description="Semantic Web and RDF library for Python", | ||
| long_description=f.read(), | ||
| classifiers=[ | ||
| 'Development Status :: 3 - Alpha', | ||
| 'Intended Audience :: Developers', | ||
| 'License :: OSI Approved :: BSD License', | ||
| 'Topic :: Internet :: WWW/HTTP', | ||
| 'Topic :: Scientific/Engineering :: Information Analysis', | ||
| 'Topic :: Text Processing :: Markup', | ||
| 'Programming Language :: Python :: 3.6', | ||
| 'Programming Language :: Python :: 3.7', | ||
| 'Programming Language :: Python :: 3.8', | ||
| 'Programming Language :: Python :: 3.9', | ||
| ], # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers | ||
| keywords='RDF N3 Turtle Semantics Web3.0', | ||
| author='Gavin Carothers, Nick Pilon', | ||
| author_email='gavin@carothers.name, npilon@gmail.com', | ||
| url='https://github.com/norcalrdf/pymantic/', | ||
| license='BSD', | ||
| packages=find_packages(), | ||
| include_package_data=True, | ||
| zip_safe=False, | ||
| test_suite='nose.collector', | ||
| install_requires=[ | ||
| 'requests', | ||
| 'lxml', | ||
| 'pytz', | ||
| 'rdflib', | ||
| 'lark-parser>=0.11.1,<0.12.0', | ||
| 'pyld', | ||
| ], | ||
| extras_require={ | ||
| 'testing': testing_extras, | ||
| }, | ||
| entry_points=""" | ||
| # -*- Entry points: -*- | ||
| """, | ||
| scripts=[ | ||
| 'pymantic/scripts/named_graph_to_nquads', | ||
| 'pymantic/scripts/bnf2html', | ||
| ]) |
Alert delta unavailable
Currently unable to show alert delta for PyPI packages.
171869
-21.95%37
-11.9%4083
-10.07%