Skip to content

Commit ce24ea3

Browse files
committed
Merge remote-tracking branch 'origin/remove-iso8601' into slim
2 parents df7da5a + f1f2cd2 commit ce24ea3

File tree

6 files changed

+2887
-2826
lines changed

6 files changed

+2887
-2826
lines changed

.github/workflows/ci.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ jobs:
7777
DEST_SANIC: examples/sanic
7878
PYTHONDEVMODE: 1
7979
PYTEST_ARGS: "-n auto --cov=tortoise --cov-append --cov-branch --tb=native -q"
80+
- name: Test not ciso8601
81+
run: |
82+
uv pip uninstall ciso8601
83+
uv run --no-sync make test_sqlite
8084
- name: Upload Coverage
8185
run: |
8286
uvx coveralls --service=github

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ dynamic = [ "version" ]
99
requires-python = ">=3.9"
1010
dependencies = [
1111
"pypika-tortoise (>=0.6.3,<1.0.0)",
12-
"iso8601 (>=2.1.0,<3.0.0); python_version < '4.0'",
1312
"aiosqlite (>=0.16.0,<1.0.0)",
1413
"anyio",
1514
"pytz",

tests/fields/test_time.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
from unittest.mock import patch
66

77
import pytz
8-
from iso8601 import ParseError
98

109
from tests import testmodels
1110
from tortoise import Model, fields, timezone
@@ -316,7 +315,7 @@ async def test_date_str(self):
316315
obj0 = await self.model.create(date="2020-08-17")
317316
obj1 = await self.model.get(date="2020-08-17")
318317
self.assertEqual(obj0.date, obj1.date)
319-
with self.assertRaises((ParseError, ValueError)):
318+
with self.assertRaises(ValueError):
320319
await self.model.create(date="2020-08-xx")
321320
await self.model.filter(date="2020-08-17").update(date="2020-08-18")
322321
obj2 = await self.model.get(date="2020-08-18")

tortoise/backends/oracle/client.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
from __future__ import annotations
22

33
import datetime
4-
import functools
54
from typing import TYPE_CHECKING, Any, SupportsInt, cast
65

76
import pyodbc
87
import pytz
9-
10-
try:
11-
from ciso8601 import parse_datetime
12-
except ImportError: # pragma: nocoverage
13-
from iso8601 import parse_date
14-
15-
parse_datetime = functools.partial(parse_date, default_timezone=None)
168
from pypika_tortoise import OracleQuery
179

1810
from tortoise.backends.base.client import (
@@ -29,6 +21,7 @@
2921
)
3022
from tortoise.backends.oracle.executor import OracleExecutor
3123
from tortoise.backends.oracle.schema_generator import OracleSchemaGenerator
24+
from tortoise.fields.data import parse_datetime
3225

3326
if TYPE_CHECKING: # pragma: nocoverage
3427
import asyncodbc # pylint: disable=W0611

tortoise/fields/data.py

Lines changed: 79 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import datetime
44
import functools
55
import json
6+
import re
67
import warnings
78
from collections.abc import Callable
89
from decimal import Decimal
@@ -23,9 +24,85 @@
2324
try:
2425
from ciso8601 import parse_datetime
2526
except ImportError: # pragma: nocoverage
26-
from iso8601 import parse_date
27+
# Copied from https://github.com/micktwomey/pyiso8601/blob/main/iso8601/iso8601.py
28+
ISO8601_REGEX = re.compile(
29+
r"""
30+
(?P<year>[0-9]{4})
31+
(
32+
(
33+
(-(?P<monthdash>[0-9]{1,2}))
34+
|
35+
(?P<month>[0-9]{2})
36+
(?!$) # Don't allow YYYYMM
37+
)
38+
(
39+
(
40+
(-(?P<daydash>[0-9]{1,2}))
41+
|
42+
(?P<day>[0-9]{2})
43+
)
44+
(
45+
(
46+
(?P<separator>[ T])
47+
(?P<hour>[0-9]{2})
48+
(:{0,1}(?P<minute>[0-9]{2})){0,1}
49+
(
50+
:{0,1}(?P<second>[0-9]{1,2})
51+
([.,](?P<second_fraction>[0-9]+)){0,1}
52+
){0,1}
53+
(?P<timezone>
54+
Z
55+
|
56+
(
57+
(?P<tz_sign>[-+])
58+
(?P<tz_hour>[0-9]{2})
59+
:{0,1}
60+
(?P<tz_minute>[0-9]{2}){0,1}
61+
)
62+
){0,1}
63+
){0,1}
64+
)
65+
){0,1} # YYYY-MM
66+
){0,1} # YYYY only
67+
$
68+
""",
69+
re.VERBOSE,
70+
)
71+
72+
def parse_datetime(datetime_string: str) -> datetime.datetime:
73+
if not (m := ISO8601_REGEX.match(datetime_string)):
74+
raise ValueError(f"Unable to parse date string {datetime_string!r}")
75+
# Drop any Nones from the regex matches
76+
# TODO: check if there's a way to omit results in regexes
77+
groups: dict[str, str] = {k: v for k, v in m.groupdict().items() if v is not None}
78+
zone = None
79+
if tz := groups.get("timezone", None):
80+
if tz.upper() == "Z":
81+
zone = datetime.timezone.utc
82+
else:
83+
sign = groups.get("tz_sign", None)
84+
hours = int(groups.get("tz_hour", 0))
85+
minutes = int(groups.get("tz_minute", 0))
86+
description = f"{sign}{hours:02d}:{minutes:02d}"
87+
if sign == "-":
88+
hours = -hours
89+
minutes = -minutes
90+
zone = datetime.timezone(
91+
datetime.timedelta(hours=hours, minutes=minutes), description
92+
)
93+
return datetime.datetime(
94+
year=int(groups.get("year", 0)),
95+
month=int(groups.get("month", groups.get("monthdash", 1))),
96+
day=int(groups.get("day", groups.get("daydash", 1))),
97+
hour=int(groups.get("hour", 0)),
98+
minute=int(groups.get("minute", 0)),
99+
second=int(groups.get("second", 0)),
100+
microsecond=int(
101+
Decimal(f"0.{groups.get('second_fraction', 0)}") * Decimal("1000000.0")
102+
),
103+
tzinfo=zone,
104+
)
27105

28-
parse_datetime = functools.partial(parse_date, default_timezone=None)
29106

30107
if TYPE_CHECKING: # pragma: nocoverage
31108
from tortoise.models import Model

0 commit comments

Comments
 (0)