diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7f737b852..2a72ed689 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,6 +72,7 @@ jobs: - '3.14t' - 'pypy3.10' - 'pypy3.11' + - 'graalpy-3.11' runs-on: ubuntu-latest @@ -424,6 +425,10 @@ jobs: manylinux: auto target: x86_64 interpreter: pypy3.10 pypy3.11 + - os: linux + manylinux: auto + target: x86_64 + interpreter: graalpy3.11 # musllinux - os: linux @@ -485,6 +490,7 @@ jobs: args: --release --out dist --interpreter ${{ matrix.interpreter || '3.9 3.10 3.11 3.12 3.13 3.14 pypy3.10 pypy3.11' }} rust-toolchain: stable docker-options: -e CI + before-script-linux: ${{ contains(matrix.interpreter, 'graalpy') && 'manylinux-interpreters ensure-all' || '' }} - run: ${{ (matrix.os == 'windows' && 'dir') || 'ls -lh' }} dist/ diff --git a/pyproject.toml b/pyproject.toml index 202cf41b5..8cab50714 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -27,6 +27,9 @@ classifiers = [ 'Programming Language :: Python :: 3.12', 'Programming Language :: Python :: 3.13', 'Programming Language :: Python :: 3.14', + 'Programming Language :: Python :: Implementation :: CPython', + 'Programming Language :: Python :: Implementation :: PyPy', + 'Programming Language :: Python :: Implementation :: GraalPy', 'Programming Language :: Rust', 'Framework :: Pydantic', 'Intended Audience :: Developers', diff --git a/tests/serializers/test_functions.py b/tests/serializers/test_functions.py index c31c4e6b6..ec1ced855 100644 --- a/tests/serializers/test_functions.py +++ b/tests/serializers/test_functions.py @@ -661,8 +661,8 @@ def f(value, handler, _info): @pytest.mark.skipif( - platform.python_implementation() == 'PyPy' or sys.platform in {'emscripten', 'win32'}, - reason='fails on pypy, emscripten and windows', + platform.python_implementation() in ('PyPy', 'GraalVM') or sys.platform in {'emscripten', 'win32'}, + reason='fails on pypy, graalpy, emscripten and windows', ) def test_recursive_call(): def bad_recursive(value): diff --git a/tests/test_errors.py b/tests/test_errors.py index 3e52697dd..8a87489d7 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1155,9 +1155,8 @@ def test_errors_include_url_envvar(env_var, env_var_value, expected_to_have_url) env.pop('PYDANTIC_ERRORS_OMIT_URL', None) # in case the ambient environment has it set if env_var_value is not None: env[env_var] = env_var_value - env['PYTHONDEVMODE'] = '1' # required to surface the deprecation warning result = subprocess.run( - [sys.executable, '-c', code], + [sys.executable, '-W', 'default', '-c', code], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, encoding='utf-8', diff --git a/tests/test_garbage_collection.py b/tests/test_garbage_collection.py index 3b5bb3c4f..e53cdc564 100644 --- a/tests/test_garbage_collection.py +++ b/tests/test_garbage_collection.py @@ -25,6 +25,7 @@ @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') def test_gc_schema_serializer() -> None: # test for https://github.com/pydantic/pydantic/issues/5136 class BaseModel: @@ -53,6 +54,7 @@ class MyModel(BaseModel): @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') def test_gc_schema_validator() -> None: # test for https://github.com/pydantic/pydantic/issues/5136 class BaseModel: @@ -81,6 +83,7 @@ class MyModel(BaseModel): @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') def test_gc_validator_iterator() -> None: # test for https://github.com/pydantic/pydantic/issues/9243 class MyModel: diff --git a/tests/validators/test_dataclasses.py b/tests/validators/test_dataclasses.py index 6ae9f54f9..93058d4e4 100644 --- a/tests/validators/test_dataclasses.py +++ b/tests/validators/test_dataclasses.py @@ -1519,6 +1519,7 @@ def test_dataclass_wrap_json(): @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') @pytest.mark.parametrize('validator', [None, 'field', 'dataclass']) def test_leak_dataclass(validator): def fn(): diff --git a/tests/validators/test_datetime.py b/tests/validators/test_datetime.py index 5e319dc23..a91a9de52 100644 --- a/tests/validators/test_datetime.py +++ b/tests/validators/test_datetime.py @@ -276,7 +276,7 @@ def tzname(self, _dt): schema.validate_python(dt) # exception messages differ between python and pypy - if platform.python_implementation() == 'PyPy': + if platform.python_implementation() in ('PyPy', 'GraalVM'): error_message = 'NotImplementedError: tzinfo subclass must override utcoffset()' else: error_message = 'NotImplementedError: a tzinfo subclass must implement utcoffset()' @@ -489,7 +489,7 @@ def test_neg_7200(): def test_tz_constraint_too_high(): - with pytest.raises(SchemaError, match='OverflowError: Python int too large to convert to C long'): + with pytest.raises(SchemaError, match='OverflowError: Python int too large.*'): SchemaValidator(core_schema.datetime_schema(tz_constraint=2**64)) diff --git a/tests/validators/test_model_init.py b/tests/validators/test_model_init.py index 463a9a48a..59dc8a15b 100644 --- a/tests/validators/test_model_init.py +++ b/tests/validators/test_model_init.py @@ -414,6 +414,7 @@ def __init__(self, **kwargs): @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') @pytest.mark.parametrize('validator', [None, 'field', 'model']) def test_leak_model(validator): def fn(): diff --git a/tests/validators/test_nullable.py b/tests/validators/test_nullable.py index 779b2507d..af73a8875 100644 --- a/tests/validators/test_nullable.py +++ b/tests/validators/test_nullable.py @@ -42,6 +42,7 @@ def test_union_nullable_bool_int(): @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') def test_leak_nullable(): def fn(): def validate(v, info): diff --git a/tests/validators/test_time.py b/tests/validators/test_time.py index 9a643acfb..78ca0a9e7 100644 --- a/tests/validators/test_time.py +++ b/tests/validators/test_time.py @@ -293,5 +293,5 @@ def test_neg_7200(): def test_tz_constraint_too_high(): - with pytest.raises(SchemaError, match='OverflowError: Python int too large to convert to C long'): + with pytest.raises(SchemaError, match='OverflowError: Python int too large.*'): SchemaValidator(core_schema.time_schema(tz_constraint=2**64)) diff --git a/tests/validators/test_typed_dict.py b/tests/validators/test_typed_dict.py index 3d96d40e0..f7bd47aba 100644 --- a/tests/validators/test_typed_dict.py +++ b/tests/validators/test_typed_dict.py @@ -1143,6 +1143,7 @@ def test_extra_behavior_ignore(config: Union[core_schema.CoreConfig, None], sche @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') def test_leak_typed_dict(): def fn(): def validate(v, info): diff --git a/tests/validators/test_with_default.py b/tests/validators/test_with_default.py index bb8775305..c0b8db8f8 100644 --- a/tests/validators/test_with_default.py +++ b/tests/validators/test_with_default.py @@ -645,6 +645,7 @@ def val_func(v: Any, handler: core_schema.ValidatorFunctionWrapHandler) -> Any: @pytest.mark.xfail( condition=platform.python_implementation() == 'PyPy', reason='https://foss.heptapod.net/pypy/pypy/-/issues/3899' ) +@pytest.mark.skipif(platform.python_implementation() == 'GraalVM', reason='Cannot reliably trigger GC on GraalPy') def test_leak_with_default(): def fn(): class Defaulted(int):