Skip to content

Commit

Permalink
general: 3.8 reached EOL, remove legacy code and make pytz optional d…
Browse files Browse the repository at this point in the history
…ependency

also enable 3.13 on CI
  • Loading branch information
karlicoss committed Oct 17, 2024
1 parent 2383901 commit b58b942
Show file tree
Hide file tree
Showing 8 changed files with 50 additions and 80 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
fail-fast: false
matrix:
platform: [ubuntu-latest, macos-latest] # todo windows-latest]
python-version: ['3.8', '3.9', '3.10', '3.11', '3.12']
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
# vvv just an example of excluding stuff from matrix
# exclude: [{platform: macos-latest, python-version: '3.6'}]

Expand Down
5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,8 @@ dependencies = [
"appdirs" , # default cache dir
"sqlalchemy>=1.0", # cache DB interaction
"orjson", # fast json serialization
"pytz", # used to properly marshall pytz datatimes
]
requires-python = ">=3.8"
requires-python = ">=3.9"

## these need to be set if you're planning to upload to pypi
# description = "TODO"
Expand All @@ -32,6 +31,8 @@ Homepage = "https://github.com/karlicoss/cachew"

[project.optional-dependencies]
testing = [
"pytz", "types-pytz", # optional runtime only dependency

"pytest",
"more-itertools",
"patchy", # for injecting sleeps and testing concurrent behaviour
Expand Down
18 changes: 18 additions & 0 deletions src/cachew/legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from dataclasses import dataclass
from datetime import date, datetime
from itertools import chain, islice
from pathlib import Path
from typing import (
Any,
Generic,
Expand Down Expand Up @@ -487,3 +488,20 @@ def test_ntbinder_primitive(tp, val) -> None:
row = b.to_row(val)
vv = b.from_row(list(row))
assert vv == val


def test_unique_columns(tmp_path: Path) -> None:
class Job(NamedTuple):
company: str
title: Optional[str]

class Breaky(NamedTuple):
job_title: int
job: Optional[Job]

assert [c.name for c in NTBinder.make(Breaky).columns] == [
'job_title',
'_job_is_null',
'job_company',
'_job_title',
]
54 changes: 18 additions & 36 deletions src/cachew/marshall/cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
get_type_hints,
)

from zoneinfo import ZoneInfo

from ..utils import TypeNotSupported, is_namedtuple
from .common import (
AbstractMarshall,
Expand Down Expand Up @@ -249,20 +251,6 @@ def make_tz_pytz(zone: str):
make_tz_pytz = pytz.timezone


if sys.version_info[:2] >= (3, 9):
import zoneinfo

ZoneInfo = zoneinfo.ZoneInfo
make_tz_zoneinfo = ZoneInfo
else:
# dummy, this is only needed for isinstance check below
class ZoneInfo:
key: str

def make_tz_zoneinfo(zone: str):
raise RuntimeError(f"Need to use python3.9+ to deserialize {zone}")


# just ints to avoid inflating db size
# for now, we try to preserve actual timezone object just in case since they do have somewhat incompatible apis
_TZTAG_ZONEINFO = 1
Expand Down Expand Up @@ -293,7 +281,7 @@ def load(self, dct: tuple):
if zone is None:
return dt

make_tz = make_tz_zoneinfo if zone_tag == _TZTAG_ZONEINFO else make_tz_pytz
make_tz = ZoneInfo if zone_tag == _TZTAG_ZONEINFO else make_tz_pytz
tz = make_tz(zone)
return dt.astimezone(tz)

Expand Down Expand Up @@ -488,16 +476,14 @@ def test_serialize_and_deserialize() -> None:
helper([1, 2, 3], Sequence[int], expected=(1, 2, 3))
helper((1, 2, 3), Sequence[int])
helper((1, 2, 3), Tuple[int, int, int])
if sys.version_info[:2] >= (3, 9):
# TODO test with from __future__ import annotations..
helper([1, 2, 3], list[int])
helper((1, 2, 3), tuple[int, int, int])
# TODO test with from __future__ import annotations..
helper([1, 2, 3], list[int])
helper((1, 2, 3), tuple[int, int, int])

# dicts
helper({'a': 'aa', 'b': 'bb'}, Dict[str, str])
helper({'a': None, 'b': 'bb'}, Dict[str, Optional[str]])
if sys.version_info[:2] >= (3, 9):
helper({'a': 'aa', 'b': 'bb'}, dict[str, str])
helper({'a': 'aa', 'b': 'bb'}, dict[str, str])

# compounds of simple types
helper(['1', 2, '3'], List[Union[str, int]])
Expand Down Expand Up @@ -559,19 +545,16 @@ class WithJson:
dsummer_tz,
]

if sys.version_info[:2] >= (3, 9):
from zoneinfo import ZoneInfo

tz_sydney = ZoneInfo('Australia/Sydney')
## these will have same local time (2025-04-06 02:01:00) in Sydney due to DST shift!
## the second one will have fold=1 set to disambiguate
utc_before_shift = datetime.fromisoformat('2025-04-05T15:01:00+00:00')
utc_after__shift = datetime.fromisoformat('2025-04-05T16:01:00+00:00')
##
sydney_before = utc_before_shift.astimezone(tz_sydney)
sydney__after = utc_after__shift.astimezone(tz_sydney)
tz_sydney = ZoneInfo('Australia/Sydney')
## these will have same local time (2025-04-06 02:01:00) in Sydney due to DST shift!
## the second one will have fold=1 set to disambiguate
utc_before_shift = datetime.fromisoformat('2025-04-05T15:01:00+00:00')
utc_after__shift = datetime.fromisoformat('2025-04-05T16:01:00+00:00')
##
sydney_before = utc_before_shift.astimezone(tz_sydney)
sydney__after = utc_after__shift.astimezone(tz_sydney)

dates_tz.extend([sydney_before, sydney__after])
dates_tz.extend([sydney_before, sydney__after])

dates = [
*dates_tz,
Expand All @@ -591,9 +574,8 @@ class WithJson:
assert helper(dsummer_tz, datetime)[0] == ('2020-08-03T01:02:03+01:00', 'Europe/London', _TZTAG_PYTZ)
assert helper(dwinter, datetime)[0] == ('2020-02-03T01:02:03', None, None)

if sys.version_info[:2] >= (3, 9):
assert helper(sydney_before, datetime)[0] == ('2025-04-06T02:01:00+11:00', 'Australia/Sydney', _TZTAG_ZONEINFO)
assert helper(sydney__after, datetime)[0] == ('2025-04-06T02:01:00+10:00', 'Australia/Sydney', _TZTAG_ZONEINFO)
assert helper(sydney_before, datetime)[0] == ('2025-04-06T02:01:00+11:00', 'Australia/Sydney', _TZTAG_ZONEINFO)
assert helper(sydney__after, datetime)[0] == ('2025-04-06T02:01:00+10:00', 'Australia/Sydney', _TZTAG_ZONEINFO)

assert helper(dwinter.date(), date)[0] == '2020-02-03'

Expand Down
3 changes: 2 additions & 1 deletion src/cachew/tests/marshall.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@

import orjson
import pytest
import pytz

from ..marshall.cachew import CachewMarshall
from ..marshall.common import Json
Expand Down Expand Up @@ -215,6 +214,8 @@ def test_datetimes(impl: Impl, count: int, gc_control, request) -> None:
if impl == 'cattrs':
pytest.skip('TODO support datetime with pytz for cattrs')

import pytz

def factory(*, count: int):
tzs = [
pytz.timezone('Europe/Berlin'),
Expand Down
40 changes: 7 additions & 33 deletions src/cachew/tests/test_cachew.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@

import patchy
import pytest
import pytz
from more_itertools import ilen, last, one, unique_everseen

from .. import (
Expand Down Expand Up @@ -563,35 +562,6 @@ def make_people_data(count: int) -> Iterator[Person]:
)


def test_unique_columns(tmp_path: Path) -> None:
# TODO remove this test? it's for legacy stuff..
from ..legacy import NTBinder

class Breaky(NamedTuple):
job_title: int
job: Optional[Job]

assert [c.name for c in NTBinder.make(Breaky).columns] == [
'job_title',
'_job_is_null',
'job_company',
'_job_title',
]

b = Breaky(
job_title=123,
job=Job(company='123', title='whatever'),
)

@cachew(cache_path=tmp_path)
def iter_breaky() -> Iterator[Breaky]:
yield b
yield b

assert list(iter_breaky()) == [b, b]
assert list(iter_breaky()) == [b, b]


def test_stats(tmp_path: Path) -> None:
cache_file = tmp_path / 'cache'

Expand Down Expand Up @@ -648,13 +618,15 @@ class Dates:


def test_dates(tmp_path: Path) -> None:
tz = pytz.timezone('Europe/London')
from zoneinfo import ZoneInfo

tz = ZoneInfo('Europe/London')
dwinter = datetime.strptime('20200203 01:02:03', '%Y%m%d %H:%M:%S')
dsummer = datetime.strptime('20200803 01:02:03', '%Y%m%d %H:%M:%S')

x = Dates(
d1=tz.localize(dwinter),
d2=tz.localize(dsummer),
d1=dwinter.replace(tzinfo=tz),
d2=dsummer.replace(tzinfo=tz),
d3=dwinter,
d4=dsummer,
d5=dsummer.replace(tzinfo=timezone.utc),
Expand Down Expand Up @@ -696,6 +668,8 @@ class AllTypes:


def test_types(tmp_path: Path) -> None:
import pytz

tz = pytz.timezone('Europe/Berlin')
# fmt: off
obj = AllTypes(
Expand Down
6 changes: 0 additions & 6 deletions src/cachew/tests/test_future_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,6 @@ class NewStyleTypes1:


def test_types1(tmp_path: Path) -> None:
if sys.version_info[:2] <= (3, 8):
pytest.skip("too annoying to adjust for 3.8 and it's EOL soon anyway")

# fmt: off
obj = NewStyleTypes1(
a_str = 'abac',
Expand Down Expand Up @@ -86,9 +83,6 @@ def test_future_annotations(
Checks handling of postponed evaluation of annotations (from __future__ import annotations)
"""

if sys.version_info[:2] <= (3, 8):
pytest.skip("too annoying to adjust for 3.8 and it's EOL soon anyway")

# NOTE: to avoid weird interactions with existing interpreter in which pytest is running
# , we compose a program and running in python directly instead
# (also not sure if it's even possible to tweak postponed annotations without doing that)
Expand Down
2 changes: 1 addition & 1 deletion tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ commands =
deps =
-e .[testing,optional]
commands =
{envpython} -m mypy --install-types --non-interactive \
{envpython} -m mypy \
-p {[testenv]package_name} \
# txt report is a bit more convenient to view on CI
--txt-report .coverage.mypy \
Expand Down

0 comments on commit b58b942

Please sign in to comment.