Skip to content

Commit 9c469b5

Browse files
Redo XSD Datetime, Date, Time, Duration parser and serializers (#2929)
* New xsd_datetime module, with parsers and serializers for XSD_Duration, XSD_Date, XSD_DatetTime, XSD_Time, XSD_gYear, XSD_gYearMonth. Based on isoformat for Python <3.11, and builtin fromisoformat for Python 3.11+ * ruff fixes in test suite changes * Fix a failing test * Add missing exports to xsd_datetime * Fix some version constraints to help CI tests pass * Fix generating negative duartion strings. This fixes the broken doctest. * Fix black formatting in xsd_datetime _again_. * Add isodate back into the dockerfile requirements so that can still build correctly with RDFLib v7.0 * correctly calculate total years in Duration constructor. * Fix some docstring generation errors * For documentation-generation reasons, don't re-export builtin parsers as xsd parsers. * Add ashleysommer to contributors list on the xsd_datetime module. * Fix wording in xsd_datetime header. --------- Co-authored-by: Nicholas Car <[email protected]>
1 parent cc25f16 commit 9c469b5

File tree

15 files changed

+752
-90
lines changed

15 files changed

+752
-90
lines changed

devtools/constraints.min

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# This file selects minimum versions to ensure that the test suite passes on
22
# these versions. The file's extension (`.min`) is chosen to evade Dependabot
33
# which operates on `*.{txt,in}` files.
4-
isodate==0.6.0
4+
isodate==0.7.2; python_version < "3.11"
55
pyparsing==2.1.0
66
importlib-metadata==4.0.0
77
berkeleydb==18.1.2
88
networkx==2.0
9-
html5lib==1.0.1
9+
html5lib-modern==1.2.0
1010
lxml==4.3.0
1111
orjson==3.9.14

docker/latest/requirements.in

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
1-
# This file is used for building a docker image of hte latest rdflib release. It
1+
# This file is used for building a docker image of the latest rdflib release. It
22
# will be updated by dependabot when new releases are made.
33
rdflib==7.0.0
4-
html5lib
4+
html5lib-modern==1.2.0
5+
# isodate is required to allow the Dockerfile to build on with pre-RDFLib-7.1 releases.
6+
isodate==0.7.2

docker/latest/requirements.txt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
#
77
html5lib-modern==1.2
88
# via -r docker/latest/requirements.in
9-
isodate==0.6.1
9+
isodate==0.7.2; python_version < "3.11"
1010
# via rdflib
1111
pyparsing==3.0.9
1212
# via rdflib
1313
rdflib==7.0.0
1414
# via -r docker/latest/requirements.in
15-
six==1.16.0
16-
# via
17-
# isodate
15+
# isodate is required to allow the Dockerfile to build on with pre-RDFLib-7.1 releases.
16+
isodate==0.7.2
17+
# via -r docker/latest/requirements.in

docs/rdf_terms.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -207,7 +207,8 @@ rdf:HTML :class:`xml.dom.minidom.DocumentFragment`
207207
.. [#f1] plain literals map directly to value space
208208
209209
.. [#f2] Date, time and datetime literals are mapped to Python
210-
instances using the `isodate <http://pypi.python.org/pypi/isodate/>`_
210+
instances using the RDFlib xsd_datetime module, that is based
211+
on the `isodate <http://pypi.python.org/pypi/isodate/>`_
211212
package).
212213
213214
.. [#f3] this is a bit dirty - by accident the ``html5lib`` parser

poetry.lock

Lines changed: 5 additions & 20 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ rdfgraphisomorphism = 'rdflib.tools.graphisomorphism:main'
3939

4040
[tool.poetry.dependencies]
4141
python = "^3.8.1"
42-
isodate = "^0.6.0"
42+
isodate = {version=">=0.7.2,<1.0.0", python = "<3.11"}
4343
pyparsing = ">=2.1.0,<4"
4444
berkeleydb = {version = "^18.1.0", optional = true}
4545
networkx = {version = ">=2,<4", optional = true}

rdflib/plugins/sparql/operators.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@
2121
from typing import Any, Callable, Dict, NoReturn, Optional, Tuple, Union, overload
2222
from urllib.parse import quote
2323

24-
import isodate
2524
from pyparsing import ParseResults
2625

2726
from rdflib.namespace import RDF, XSD
@@ -47,6 +46,7 @@
4746
URIRef,
4847
Variable,
4948
)
49+
from rdflib.xsd_datetime import Duration, parse_datetime # type: ignore[attr-defined]
5050

5151

5252
def Builtin_IRI(expr: Expr, ctx: FrozenBindings) -> URIRef:
@@ -521,8 +521,13 @@ def Builtin_TZ(e: Expr, ctx) -> Literal:
521521
if not d.tzinfo:
522522
return Literal("")
523523
n = d.tzinfo.tzname(d)
524-
if n == "UTC":
524+
if n is None:
525+
n = ""
526+
elif n == "UTC":
525527
n = "Z"
528+
elif n.startswith("UTC"):
529+
# Replace tzname like "UTC-05:00" with simply "-05:00" to match Jena tz fn
530+
n = n[3:]
526531
return Literal(n)
527532

528533

@@ -687,7 +692,7 @@ def default_cast(e: Expr, ctx: FrozenBindings) -> Literal: # type: ignore[retur
687692
if x.datatype and x.datatype not in (XSD.dateTime, XSD.string):
688693
raise SPARQLError("Cannot cast %r to XSD:dateTime" % x.datatype)
689694
try:
690-
return Literal(isodate.parse_datetime(x), datatype=e.iri)
695+
return Literal(parse_datetime(x), datatype=e.iri)
691696
except: # noqa: E722
692697
raise SPARQLError("Cannot interpret '%r' as datetime" % x)
693698

@@ -1085,7 +1090,7 @@ def dateTimeObjects(expr: Literal) -> Any:
10851090
def isCompatibleDateTimeDatatype( # type: ignore[return]
10861091
obj1: Union[py_datetime.date, py_datetime.datetime],
10871092
dt1: URIRef,
1088-
obj2: Union[isodate.Duration, py_datetime.timedelta],
1093+
obj2: Union[Duration, py_datetime.timedelta],
10891094
dt2: URIRef,
10901095
) -> bool:
10911096
"""
@@ -1098,7 +1103,7 @@ def isCompatibleDateTimeDatatype( # type: ignore[return]
10981103
return True
10991104
elif dt2 == XSD.dayTimeDuration or dt2 == XSD.Duration:
11001105
# checking if the dayTimeDuration has no Time Component
1101-
# else it wont be compatible with Date Literal
1106+
# else it won't be compatible with Date Literal
11021107
if "T" in str(obj2):
11031108
return False
11041109
else:
@@ -1110,7 +1115,7 @@ def isCompatibleDateTimeDatatype( # type: ignore[return]
11101115
elif dt2 == XSD.dayTimeDuration or dt2 == XSD.Duration:
11111116
# checking if the dayTimeDuration has no Date Component
11121117
# (by checking if the format is "PT...." )
1113-
# else it wont be compatible with Time Literal
1118+
# else it won't be compatible with Time Literal
11141119
if "T" == str(obj2)[1]:
11151120
return True
11161121
else:
@@ -1139,7 +1144,7 @@ def calculateDuration(
11391144
def calculateFinalDateTime(
11401145
obj1: Union[py_datetime.date, py_datetime.datetime],
11411146
dt1: URIRef,
1142-
obj2: Union[isodate.Duration, py_datetime.timedelta],
1147+
obj2: Union[Duration, py_datetime.timedelta],
11431148
dt2: URIRef,
11441149
operation: str,
11451150
) -> Literal:

rdflib/plugins/sparql/sparql.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919
Union,
2020
)
2121

22-
import isodate
23-
2422
import rdflib.plugins.sparql
2523
from rdflib.graph import ConjunctiveGraph, Dataset, Graph
2624
from rdflib.namespace import NamespaceManager
@@ -302,7 +300,7 @@ def __init__(
302300
@property
303301
def now(self) -> datetime.datetime:
304302
if self._now is None:
305-
self._now = datetime.datetime.now(isodate.tzinfo.UTC)
303+
self._now = datetime.datetime.now(datetime.timezone.utc)
306304
return self._now
307305

308306
def clone(

rdflib/term.py

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,6 @@
3838
"Literal",
3939
"Variable",
4040
]
41-
4241
import logging
4342
import math
4443
import warnings
@@ -67,19 +66,22 @@
6766
from uuid import uuid4
6867

6968
import html5lib
70-
from isodate import (
69+
70+
import rdflib
71+
import rdflib.util
72+
from rdflib.compat import long_type
73+
74+
from .xsd_datetime import ( # type: ignore[attr-defined]
7175
Duration,
7276
duration_isoformat,
73-
parse_date,
7477
parse_datetime,
75-
parse_duration,
7678
parse_time,
79+
parse_xsd_date,
80+
parse_xsd_duration,
81+
parse_xsd_gyear,
82+
parse_xsd_gyearmonth,
7783
)
7884

79-
import rdflib
80-
import rdflib.util
81-
from rdflib.compat import long_type
82-
8385
if TYPE_CHECKING:
8486
from .namespace import NamespaceManager
8587
from .paths import AlternativePath, InvPath, NegatedPath, Path, SequencePath
@@ -1424,7 +1426,7 @@ def eq(self, other: Any) -> bool:
14241426
):
14251427
return self.value == other
14261428
# NOTE for type ignore: bool is a subclass of int so this won't ever run.
1427-
elif isinstance(other, bool): # type: ignore[unreachable]
1429+
elif isinstance(other, bool): # type: ignore[unreachable, unused-ignore]
14281430
if self.datatype == _XSD_BOOLEAN:
14291431
return self.value == other
14301432

@@ -2030,13 +2032,13 @@ def _castPythonToLiteral( # noqa: N802
20302032
XSDToPython: Dict[Optional[str], Optional[Callable[[str], Any]]] = {
20312033
None: None, # plain literals map directly to value space
20322034
URIRef(_XSD_PFX + "time"): parse_time,
2033-
URIRef(_XSD_PFX + "date"): parse_date,
2034-
URIRef(_XSD_PFX + "gYear"): parse_date,
2035-
URIRef(_XSD_PFX + "gYearMonth"): parse_date,
2035+
URIRef(_XSD_PFX + "date"): parse_xsd_date,
2036+
URIRef(_XSD_PFX + "gYear"): parse_xsd_gyear,
2037+
URIRef(_XSD_PFX + "gYearMonth"): parse_xsd_gyearmonth,
20362038
URIRef(_XSD_PFX + "dateTime"): parse_datetime,
2037-
URIRef(_XSD_PFX + "duration"): parse_duration,
2038-
URIRef(_XSD_PFX + "dayTimeDuration"): parse_duration,
2039-
URIRef(_XSD_PFX + "yearMonthDuration"): parse_duration,
2039+
URIRef(_XSD_PFX + "duration"): parse_xsd_duration,
2040+
URIRef(_XSD_PFX + "dayTimeDuration"): parse_xsd_duration,
2041+
URIRef(_XSD_PFX + "yearMonthDuration"): parse_xsd_duration,
20402042
URIRef(_XSD_PFX + "hexBinary"): _unhexlify,
20412043
URIRef(_XSD_PFX + "string"): None,
20422044
URIRef(_XSD_PFX + "normalizedString"): None,

0 commit comments

Comments
 (0)