Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions .github/workflows/code-quality.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,24 +19,24 @@ jobs:
max-parallel: 5
matrix:
# https://github.com/actions/python-versions/blob/main/versions-manifest.json
python-version: [ "3.9", "3.11" ]
python-version: [ "3.10", "3.14" ]

if: github.ref != 'refs/heads/main'
steps:
- uses: actions/checkout@v3

- uses: actions/setup-python@v4
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install Poetry and dependencies
run: |
set -e

curl -sSL https://install.python-poetry.org | python3 -
poetry install --all-extras

allow-prereleases: true
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
python-version: ${{ matrix.python-version }}
- uses: reviewdog/action-setup@v1

- name: run mypy
run: poetry run mypy --no-warn-no-return dataclasses_json
run: |
uv sync
uv run mypy --no-warn-no-return dataclasses_json
103 changes: 49 additions & 54 deletions .github/workflows/pythonpackage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,53 +12,45 @@ on:
- master
workflow_dispatch:

env:
LATEST_PY_VERSION: &latest '3.13'

jobs:
test:
runs-on: ubuntu-latest
strategy:
max-parallel: 5
matrix:
# https://github.com/actions/python-versions/blob/main/versions-manifest.json
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.14t", "pypy3.11"]

steps:
- uses: actions/checkout@v3
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install Poetry and dependencies
shell: bash
env:
PYTHON_VERSION: ${{ matrix.python-version }}
run: |
set -e

if [[ "$PYTHON_VERSION" == '3.7' ]]
then
curl -sSL https://install.python-poetry.org | python3 - --version 1.5.1
else
curl -sSL https://install.python-poetry.org | python3 -
fi

poetry install --all-extras
- name: Lint with flake8
run: |
set -e
poetry run flake8 dataclasses_json --show-source --statistics --count
- name: Test with pytest
run: |
set -euxo pipefail

poetry run pytest ./tests --doctest-modules --junitxml=junit/test-results.xml --cov=. --cov-report=term-missing:skip-covered | tee pytest-coverage.txt

- name: Publish Code Coverage
uses: MishaKav/pytest-coverage-comment@main
if: ${{ !github.event.pull_request.head.repo.fork }}
with:
pytest-coverage-path: ./pytest-coverage.txt
junitxml-path: ./junit/test-results.xml
- name: Check out code
uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
python-version: ${{ matrix.python-version }}
- name: Install dependencies
run: uv sync
- name: Test with pytest
run: |
set -euxo pipefail

uv run pytest ./tests --doctest-modules --junitxml=junit/test-results.xml --cov=. --cov-report=term-missing:skip-covered | tee pytest-coverage.txt

- name: Python Coverage Comment if main
if: ${{ github.ref == 'refs/heads/main' && matrix.python-version == env.LATEST_PY_VERSION && matrix.os == 'ubuntu-latest' }}
uses: py-cov-action/python-coverage-comment-action@v3.23
with:
GITHUB_TOKEN: ${{ github.token }}


release:
Expand All @@ -68,29 +60,32 @@ jobs:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
- name: Check out code
uses: actions/checkout@v4
with:
python-version: "3.9.x"
- name: Install Poetry and Prepare version
run: |
set -e

curl -sSL https://install.python-poetry.org | python3 -
poetry self add "poetry-dynamic-versioning[plugin]"
- name: Build package
fetch-depth: 0
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
- name: Install uv
uses: astral-sh/setup-uv@v6
with:
enable-cache: true
python-version: ${{ matrix.python-version }}
- name: Install dependencies and build
run: |
set -e

poetry build

uv sync
uv build
- name: Publish distribution 📦 to Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
repository-url: https://test.pypi.org/legacy/

- name: Publish distribution 📦 to PyPI
if: startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
# - name: Trusted Publisher Upload package to PyPI
# if: startsWith(github.ref, 'refs/tags/v')
# uses: pypa/gh-action-pypi-publish@release/v1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ venv
.mypy_cache
.hypothesis
pip-wheel-metadata

__pycache__
54 changes: 54 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
default_language_version:
python: python3
exclude: "^$"

repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-added-large-files
# - repo: https://github.com/astral-sh/ruff-pre-commit
# # Ruff version.
# rev: v0.12.9
# hooks:
# # Run the linter.
# - id: ruff-check
# types_or: [ python, pyi, jupyter ]
# args: [ --fix ]
# # Run the formatter.
# - id: ruff-format
# types_or: [ python, pyi, jupyter ]
- repo: local
hooks:
- entry: pyright
id: "pyright"
name: "pyright"
types_or: [python]
language: node
pass_filenames: true
additional_dependencies: ["pyright"]
- repo: local
hooks:
- id: pylint
name: pylint
entry: pylint
language: system
types: [python]
require_serial: true
always_run: false
pass_filenames: true
args:
[
"-rn",
"-sn",
]
- repo: local
hooks:
- id: pytest
name: pytest
entry: pytest -n 6
language: system
types: [python]
pass_filenames: false
always_run: false
files: '.*\.(py|ipynb)$'
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

![](https://github.com/lidatong/dataclasses-json/workflows/dataclasses-json/badge.svg)

[![PyPI Version](https://badge.fury.io/py/dataclasses-json.svg)](https://pypi.python.org/pypi/dataclasses-json)
![Python Versions](https://img.shields.io/pypi/pyversions/dataclasses-json)

[![Coverage badge](https://github.com/lidatong/dataclasses-json/raw/python-coverage-comment-action-data/badge.svg)](https://github.com/lidatong/dataclasses-json/tree/python-coverage-comment-action-data)


This library provides a simple API for encoding and decoding [dataclasses](https://docs.python.org/3/library/dataclasses.html) to and from JSON.

It's very easy to get started.
Expand Down Expand Up @@ -668,8 +674,8 @@ Any version that is not listed in the table below we do not test against, though

| Python version range | Compatible dataclasses-json version |
|----------------------|:-----------------------------------:|
| 3.7.x - 3.12.x | 0.5.x - 0.6.x |
| >= 3.13.x | No official support (yet) |
| 3.9.x - 3.12.x | 0.5.x - 0.6.x |
| 3.13.x, 3.14, pypy3.11 | TBD |


## Roadmap
Expand Down
40 changes: 39 additions & 1 deletion dataclasses_json/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
from uuid import UUID

from typing_inspect import is_union_type # type: ignore
import typing

from dataclasses_json import cfg
from dataclasses_json.utils import (_get_type_cons, _get_type_origin,
Expand All @@ -44,6 +45,43 @@
Set: frozenset,
})

PEP649 = sys.version_info >= (3, 14)

if PEP649:
import inspect

def _safe_get_type_hints(c, **kwargs):

if not PEP649:
# not running under PEP 649 (future/deferred annotations),
return typing.get_type_hints(c, include_extras=True, **kwargs)

else:
if not isinstance(c, type):
# If we're passed an instance instead of a class, normalize to its type
c = c.__class__
if "." not in getattr(c, "__qualname__", ""):
# If this is a *top-level class* (no "." in __qualname__),
# typing.get_type_hints works fine even under PEP 649.
return typing.get_type_hints(c, include_extras=True, **kwargs)
else:
# Otherwise, this is a *nested class* (defined inside another class or function),
# where typing.get_type_hints may fail under PEP 649.
ann = {}

# First collect annotations from bases in the MRO
for base in reversed(c.__mro__[:-1]):
ann.update(inspect.get_annotations(base, format=inspect.Format.VALUE) or {})

# For the class itself, use FORWARDREF format to keep "Self"/recursive types intact
ann.update(inspect.get_annotations(c, format=inspect.Format.FORWARDREF) or {})

if ann:
return ann
else:
return {f.name: f.type for f in fields(c)}



class _ExtendedEncoder(json.JSONEncoder):
def default(self, o) -> Json:
Expand Down Expand Up @@ -175,7 +213,7 @@ def _decode_dataclass(cls, kvs, infer_missing):
kvs = _handle_undefined_parameters_safe(cls, kvs, usage="from")

init_kwargs = {}
types = get_type_hints(cls)
types = _safe_get_type_hints(cls)
for field in fields(cls):
# The field should be skipped from being added
# to init_kwargs as it's not intended as a constructor argument.
Expand Down
3 changes: 2 additions & 1 deletion dataclasses_json/undefined.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from typing import Any, Callable, Dict, Optional, Tuple, Union, Type, get_type_hints
from enum import Enum

from .core import _safe_get_type_hints
from marshmallow.exceptions import ValidationError # type: ignore

from dataclasses_json.utils import CatchAllVar
Expand Down Expand Up @@ -248,7 +249,7 @@ def _catch_all_init(self, *args, **kwargs):
@staticmethod
def _get_catch_all_field(cls) -> Field:
cls_globals = vars(sys.modules[cls.__module__])
types = get_type_hints(cls, globalns=cls_globals)
types = _safe_get_type_hints(cls, globalns=cls_globals)
catch_all_fields = list(
filter(lambda f: types[f.name] == Optional[CatchAllVar], fields(cls)))
number_of_catch_all_fields = len(catch_all_fields)
Expand Down
Loading
Loading