Skip to content

Commit bbd6bbd

Browse files
committed
Drop Python 3.8 support
1 parent 74ebfe0 commit bbd6bbd

File tree

13 files changed

+47
-215
lines changed

13 files changed

+47
-215
lines changed

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ jobs:
8080
env:
8181
CIBW_TEST_REQUIRES: "pytest msgpack pyyaml tomli tomli_w"
8282
CIBW_TEST_COMMAND: "pytest {project}/tests"
83-
CIBW_BUILD: "cp38-* cp39-* cp310-* cp311-* cp312-* cp313-*"
83+
CIBW_BUILD: "cp39-* cp310-* cp311-* cp312-* cp313-*"
8484
CIBW_SKIP: "*-win32 *_i686 *_s390x *_ppc64le"
8585
CIBW_ARCHS_MACOS: "x86_64 arm64"
8686
CIBW_ARCHS_LINUX: "x86_64 aarch64"
@@ -99,7 +99,7 @@ jobs:
9999
- name: Set up Environment
100100
if: github.event_name != 'release'
101101
run: |
102-
echo "CIBW_SKIP=${CIBW_SKIP} *-musllinux_* cp38-*_aarch64 cp39-*_aarch64 cp311-*_aarch64 cp312-*_aarch64 cp313-*_aarch64" >> $GITHUB_ENV
102+
echo "CIBW_SKIP=${CIBW_SKIP} *-musllinux_* cp39-*_aarch64 cp311-*_aarch64 cp312-*_aarch64 cp313-*_aarch64" >> $GITHUB_ENV
103103
104104
- name: Build & Test Wheels
105105
uses: pypa/cibuildwheel@v2.21.3
@@ -122,7 +122,7 @@ jobs:
122122
- name: Install Python
123123
uses: actions/setup-python@v5
124124
with:
125-
python-version: 3.8
125+
python-version: "3.11"
126126

127127
- name: Build source distribution
128128
run: python setup.py sdist

.github/workflows/docs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ jobs:
1414
- name: Install Python
1515
uses: actions/setup-python@v5
1616
with:
17-
python-version: 3.8
17+
python-version: "3.11"
1818

1919
- name: Install msgspec and dependencies
2020
run: |

msgspec/_core.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5529,9 +5529,6 @@ structmeta_collect_base(StructMetaInfo *info, MsgspecState *mod, PyObject *base)
55295529
if (((PyTypeObject *)base)->tp_dictoffset) {
55305530
info->has_non_slots_bases = true;
55315531
}
5532-
/* XXX: in Python 3.8 Generic defines __new__, but we can ignore it.
5533-
* This can be removed when Python 3.8 support is dropped */
5534-
if (base == mod->typing_generic) return 0;
55355532

55365533
static const char *attrs[] = {"__init__", "__new__"};
55375534
Py_ssize_t nattrs = 2;

msgspec/_utils.py

Lines changed: 3 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,7 @@
33
import sys
44
import typing
55

6-
try:
7-
from typing_extensions import _AnnotatedAlias
8-
except Exception:
9-
try:
10-
from typing import _AnnotatedAlias
11-
except Exception:
12-
_AnnotatedAlias = None
6+
from typing import _AnnotatedAlias # noqa: F401
137

148
try:
159
from typing_extensions import get_type_hints as _get_type_hints
@@ -25,13 +19,8 @@
2519
Required = NotRequired = None
2620

2721

28-
if Required is None and _AnnotatedAlias is None:
29-
# No extras available, so no `include_extras`
30-
get_type_hints = _get_type_hints
31-
else:
32-
33-
def get_type_hints(obj):
34-
return _get_type_hints(obj, include_extras=True)
22+
def get_type_hints(obj):
23+
return _get_type_hints(obj, include_extras=True)
3524

3625

3726
# The `is_class` argument was new in 3.11, but was backported to 3.9 and 3.10.

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,11 +82,11 @@
8282
classifiers=[
8383
"License :: OSI Approved :: BSD License",
8484
"Development Status :: 4 - Beta",
85-
"Programming Language :: Python :: 3.8",
8685
"Programming Language :: Python :: 3.9",
8786
"Programming Language :: Python :: 3.10",
8887
"Programming Language :: Python :: 3.11",
8988
"Programming Language :: Python :: 3.12",
89+
"Programming Language :: Python :: 3.13",
9090
],
9191
extras_require=extras_require,
9292
license="BSD",
@@ -99,6 +99,6 @@
9999
else ""
100100
),
101101
long_description_content_type="text/markdown",
102-
python_requires=">=3.8",
102+
python_requires=">=3.9",
103103
zip_safe=False,
104104
)

tests/conftest.py

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,18 +46,3 @@ def shuffle(self, obj):
4646
@pytest.fixture
4747
def rand():
4848
yield Rand()
49-
50-
51-
@pytest.fixture
52-
def Annotated():
53-
try:
54-
from typing import Annotated
55-
56-
return Annotated
57-
except ImportError:
58-
try:
59-
from typing_extensions import Annotated
60-
61-
return Annotated
62-
except ImportError:
63-
pytest.skip("Annotated types not available")

tests/test_common.py

Lines changed: 16 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import abc
43
import base64
54
import collections
65
import datetime
@@ -15,6 +14,7 @@
1514
from dataclasses import dataclass, field, make_dataclass
1615
from datetime import timedelta
1716
from typing import (
17+
Annotated,
1818
ClassVar,
1919
Deque,
2020
Dict,
@@ -44,12 +44,10 @@
4444

4545
UTC = datetime.timezone.utc
4646

47-
PY39 = sys.version_info[:2] >= (3, 9)
4847
PY310 = sys.version_info[:2] >= (3, 10)
4948
PY311 = sys.version_info[:2] >= (3, 11)
5049
PY312 = sys.version_info[:2] >= (3, 12)
5150

52-
py39_plus = pytest.mark.skipif(not PY39, reason="3.9+ only")
5351
py310_plus = pytest.mark.skipif(not PY310, reason="3.10+ only")
5452
py311_plus = pytest.mark.skipif(not PY311, reason="3.11+ only")
5553
py312_plus = pytest.mark.skipif(not PY312, reason="3.12+ only")
@@ -156,7 +154,6 @@ class subclass(cls):
156154

157155

158156
class TestDecoder:
159-
@py39_plus
160157
def test_decoder_runtime_type_parameters(self, proto):
161158
dec = proto.Decoder[int](int)
162159
assert isinstance(dec, proto.Decoder)
@@ -896,8 +893,6 @@ def test_multiple_literals(self):
896893
dec.decode(msgspec.msgpack.encode("carrot"))
897894

898895
def test_nested_literals(self):
899-
"""Python 3.9+ automatically denest literals, can drop this test when
900-
python 3.8 is dropped"""
901896
integers = Literal[-1, -2, -3]
902897
strings = Literal["apple", "banana"]
903898
both = Literal[integers, strings]
@@ -2098,10 +2093,6 @@ class Base(cls):
20982093
class Ex(Base, total=False):
20992094
c: str
21002095

2101-
if not hasattr(Ex, "__required_keys__"):
2102-
# This should be Python 3.8, builtin typing only
2103-
pytest.skip("partially optional TypedDict not supported")
2104-
21052096
dec = proto.Decoder(Ex)
21062097

21072098
x = {"a": 1, "b": "two", "c": "extra"}
@@ -2126,10 +2117,6 @@ def test_required_and_notrequired(self, proto, use_typing_extensions):
21262117
if not hasattr(ns, "Required"):
21272118
pytest.skip(f"{module}.Required is not available")
21282119

2129-
if not hasattr(ns.TypedDict("C", {}), "__required_keys__"):
2130-
# This should be Python 3.8, builtin typing only
2131-
pytest.skip("partially optional TypedDict not supported")
2132-
21332120
source = f"""
21342121
from __future__ import annotations
21352122
from {module} import TypedDict, Required, NotRequired
@@ -3273,7 +3260,6 @@ def test_encode_time_offset_rounds_to_nearest_minute(self, proto, offset, t_str)
32733260
sol = proto.encode(t_str)
32743261
assert res == sol
32753262

3276-
@py39_plus
32773263
def test_encode_time_zoneinfo(self):
32783264
import zoneinfo
32793265

@@ -3743,23 +3729,23 @@ def test_decode_newtype(self, proto):
37433729
with pytest.raises(ValidationError):
37443730
proto.decode(proto.encode("bad"), type=UserId2)
37453731

3746-
def test_decode_annotated_newtype(self, proto, Annotated):
3732+
def test_decode_annotated_newtype(self, proto):
37473733
UserId = NewType("UserId", int)
37483734
dec = proto.Decoder(Annotated[UserId, msgspec.Meta(ge=0)])
37493735
assert dec.decode(proto.encode(1)) == 1
37503736

37513737
with pytest.raises(ValidationError):
37523738
dec.decode(proto.encode(-1))
37533739

3754-
def test_decode_newtype_annotated(self, proto, Annotated):
3740+
def test_decode_newtype_annotated(self, proto):
37553741
UserId = NewType("UserId", Annotated[int, msgspec.Meta(ge=0)])
37563742
dec = proto.Decoder(UserId)
37573743
assert dec.decode(proto.encode(1)) == 1
37583744

37593745
with pytest.raises(ValidationError):
37603746
dec.decode(proto.encode(-1))
37613747

3762-
def test_decode_annotated_newtype_annotated(self, proto, Annotated):
3748+
def test_decode_annotated_newtype_annotated(self, proto):
37633749
UserId = Annotated[
37643750
NewType("UserId", Annotated[int, msgspec.Meta(ge=0)]), msgspec.Meta(le=10)
37653751
]
@@ -3975,10 +3961,9 @@ def test_abstract_sequence(self, proto, typ):
39753961
with pytest.raises(ValidationError, match="Expected `array`, got `str`"):
39763962
proto.decode(proto.encode("a"), type=typ)
39773963

3978-
if PY39 or type(typ) is not abc.ABCMeta:
3979-
assert proto.decode(msg, type=typ[int]) == sol
3980-
with pytest.raises(ValidationError, match="Expected `int`, got `str`"):
3981-
proto.decode(proto.encode(["a"]), type=typ[int])
3964+
assert proto.decode(msg, type=typ[int]) == sol
3965+
with pytest.raises(ValidationError, match="Expected `int`, got `str`"):
3966+
proto.decode(proto.encode(["a"]), type=typ[int])
39823967

39833968
@pytest.mark.parametrize(
39843969
"typ",
@@ -3996,10 +3981,9 @@ def test_abstract_mapping(self, proto, typ):
39963981
with pytest.raises(ValidationError, match="Expected `object`, got `str`"):
39973982
proto.decode(proto.encode("a"), type=typ)
39983983

3999-
if PY39 or type(typ) is not abc.ABCMeta:
4000-
assert proto.decode(msg, type=typ[str, int]) == sol
4001-
with pytest.raises(ValidationError, match="Expected `int`, got `str`"):
4002-
proto.decode(proto.encode({"a": "b"}), type=typ[str, int])
3984+
assert proto.decode(msg, type=typ[str, int]) == sol
3985+
with pytest.raises(ValidationError, match="Expected `int`, got `str`"):
3986+
proto.decode(proto.encode({"a": "b"}), type=typ[str, int])
40033987

40043988

40053989
class TestUnset:
@@ -4239,7 +4223,7 @@ def test_decode_final(self, proto):
42394223
with pytest.raises(ValidationError):
42404224
dec.decode(proto.encode("bad"))
42414225

4242-
def test_decode_final_annotated(self, proto, Annotated):
4226+
def test_decode_final_annotated(self, proto):
42434227
dec = proto.Decoder(Final[Annotated[int, msgspec.Meta(ge=0)]])
42444228

42454229
assert dec.decode(proto.encode(1)) == 1
@@ -4323,7 +4307,7 @@ def test_lax_int_from_float(self, proto):
43234307
with pytest.raises(ValidationError, match="Expected `int`, got `float`"):
43244308
proto.decode(msg, type=int, strict=False)
43254309

4326-
def test_lax_int_constr(self, proto, Annotated):
4310+
def test_lax_int_constr(self, proto):
43274311
typ = Annotated[int, Meta(ge=0)]
43284312
msg = proto.encode("1")
43294313
assert proto.decode(msg, type=typ, strict=False) == 1
@@ -4370,7 +4354,7 @@ def test_lax_float(self, proto):
43704354
with pytest.raises(ValidationError, match="Expected `float`, got `str`"):
43714355
proto.decode(msg, type=float, strict=False)
43724356

4373-
def test_lax_float_constr(self, proto, Annotated):
4357+
def test_lax_float_constr(self, proto):
43744358
msg = proto.encode("1.5")
43754359
assert proto.decode(msg, type=Annotated[float, Meta(ge=0)], strict=False) == 1.5
43764360

@@ -4383,7 +4367,7 @@ def test_lax_str(self, proto):
43834367
msg = proto.encode(x)
43844368
assert proto.decode(msg, type=str, strict=False) == x
43854369

4386-
def test_lax_str_constr(self, proto, Annotated):
4370+
def test_lax_str_constr(self, proto):
43874371
typ = Annotated[str, Meta(max_length=10)]
43884372
msg = proto.encode("xxx")
43894373
assert proto.decode(msg, type=typ, strict=False) == "xxx"
@@ -4438,7 +4422,7 @@ def test_lax_datetime_invalid_numeric_str(self, proto):
44384422
proto.decode(msg, type=datetime.datetime, strict=False)
44394423

44404424
@pytest.mark.parametrize("val", [123, -123, 123.456, "123.456"])
4441-
def test_lax_datetime_naive_required(self, val, proto, Annotated):
4425+
def test_lax_datetime_naive_required(self, val, proto):
44424426
msg = proto.encode(val)
44434427
with pytest.raises(ValidationError, match="no timezone component"):
44444428
proto.decode(
@@ -4527,7 +4511,7 @@ def test_lax_union_invalid(self, x, proto):
45274511
("100.5", "`float` <= 100.0"),
45284512
],
45294513
)
4530-
def test_lax_union_invalid_constr(self, x, err, proto, Annotated):
4514+
def test_lax_union_invalid_constr(self, x, err, proto):
45314515
"""Ensure that values that parse properly but don't meet the specified
45324516
constraints error with a specific constraint error"""
45334517
msg = proto.encode(x)

tests/test_constraints.py

Lines changed: 3 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,10 @@
11
import datetime
22
import math
33
import re
4-
from typing import Dict, List, Union
4+
from typing import Dict, List, Union, Annotated
55

66
import pytest
77

8-
try:
9-
from typing import Annotated
10-
except ImportError:
11-
try:
12-
from typing_extensions import Annotated
13-
except ImportError:
14-
pytestmark = pytest.mark.skip("Annotated types not available")
15-
168
import msgspec
179
from msgspec import Meta
1810

@@ -25,26 +17,6 @@ def proto(request):
2517
return msgspec.msgpack
2618

2719

28-
try:
29-
nextafter = math.nextafter
30-
except AttributeError:
31-
32-
def nextafter(x, towards):
33-
"""This isn't a 100% accurate implementation, but is fine
34-
for rough testing of Python 3.8"""
35-
factor = float.fromhex("0x1.fffffffffffffp-1")
36-
37-
def sign(x):
38-
return -1 if x < 0 else 1
39-
40-
scale_up = sign(x) == sign(towards)
41-
if scale_up:
42-
out = (abs(x) / factor) * sign(x)
43-
else:
44-
out = (abs(x) * factor) * sign(x)
45-
return out
46-
47-
4820
FIELDS = {
4921
"gt": 0,
5022
"ge": 0,
@@ -398,9 +370,9 @@ def floorm1(x):
398370

399371
if name.endswith("e"):
400372
good = bound
401-
bad = nextafter(bound, -good_dir)
373+
bad = math.nextafter(bound, -good_dir)
402374
else:
403-
good = nextafter(bound, good_dir)
375+
good = math.nextafter(bound, good_dir)
404376
bad = bound
405377
good_cases = [good, good_round(good), float(good_round(good))]
406378
bad_cases = [bad, bad_round(bad), float(bad_round(bad))]

0 commit comments

Comments
 (0)