Skip to content

Commit

Permalink
Enhancement: Pydantic v2 Package Shim Compatibility (#55)
Browse files Browse the repository at this point in the history
* Implements `compatibility` subpackage for easier version compatibility maintenance
* Adds Pydantic v1 / Pydantic v2 compatibility via shim
* Adds Pydantic v1 / Pydantic v2 to testing CI matrix
* Updates examples and adds compatibility notice
  • Loading branch information
SupImDos authored Apr 29, 2024
1 parent a52b494 commit 61e979d
Show file tree
Hide file tree
Showing 31 changed files with 86 additions and 80 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ jobs:
matrix:
os: [ "ubuntu-latest", "macos-13", "windows-latest" ]
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12" ]
pydantic-version: [ "1.0", "2.0" ]
fail-fast: false
defaults:
run:
Expand All @@ -34,9 +35,11 @@ jobs:
uses: actions/cache@v4
with:
path: .venv
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ hashFiles('pyproject.toml') }}
key: venv-${{ runner.os }}-${{ matrix.python-version }}-${{ matrix.pydantic-version }}-${{ hashFiles('pyproject.toml') }}
- name: Install Hatch
run: pip install git+https://github.com/pypa/hatch.git@master
- name: Install Pydantic
run: hatch run uv pip install pydantic~=${{ matrix.pydantic-version }}
- name: Run Build Hooks
run: hatch build --hooks-only
- name: Run Tests
Expand All @@ -45,7 +48,7 @@ jobs:
uses: coverallsapp/github-action@v2
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
flag-name: ${{ matrix.os }}-${{ matrix.python-version }}
flag-name: ${{ matrix.os }}-${{ matrix.python-version }}-${{ matrix.pydantic-version }}
parallel: true

coverage:
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@
## Help
See [documentation](https://pydantic-argparse.supimdos.com) for help.

## Requirements
Requires Python 3.8+, and is compatible with the Pydantic v1 API.

## Installation
Installation with `pip` is simple:
```console
Expand All @@ -46,7 +49,7 @@ $ pip install pydantic-argparse

## Example
```py
import pydantic
import pydantic.v1 as pydantic
import pydantic_argparse


Expand Down
3 changes: 2 additions & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@
provides declarative *typed* argument parsing using `pydantic` models.

## Requirements
`pydantic-argparse` requires Python 3.8+
`pydantic-argparse` requires Python 3.8+, and is compatible with the Pydantic
v1 API.

## Installation
Installation with `pip` is simple:
Expand Down
2 changes: 1 addition & 1 deletion docs/showcase.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ The `pydantic-argparse` command-line interface construction is simple.

=== "Pydantic Argparse"
```python
import pydantic
import pydantic.v1 as pydantic
import pydantic_argparse

# Declare Arguments
Expand Down
2 changes: 1 addition & 1 deletion examples/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


# Third-Party
import pydantic
import pydantic.v1 as pydantic
import pydantic_argparse

# Typing
Expand Down
2 changes: 1 addition & 1 deletion examples/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@


# Third-Party
import pydantic
import pydantic.v1 as pydantic
import pydantic_argparse


Expand Down
4 changes: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ authors = [{ name = "Hayden Richards", email = "[email protected]" }]
readme = "README.md"
license = { file = "LICENSE" }
requires-python = ">=3.8"
dependencies = ["pydantic<2"]
dependencies = ["pydantic"]
classifiers = [
"Development Status :: 4 - Beta",
'Programming Language :: Python',
Expand Down Expand Up @@ -102,7 +102,7 @@ warn_return_any = true
warn_unused_ignores = true
no_implicit_optional = true
show_error_codes = true
plugins = ["pydantic.mypy"]
plugins = ["pydantic.v1.mypy"]

[tool.pydantic-mypy]
init_forbid_extra = true
Expand Down
4 changes: 2 additions & 2 deletions src/pydantic_argparse/argparse/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@
"""


# Standard
import argparse
# Local
from pydantic_argparse.compatibility import argparse

# Typing
from typing import Any, Callable, Iterable, List, Optional, Sequence, Tuple, TypeVar, Union, cast
Expand Down
7 changes: 2 additions & 5 deletions src/pydantic_argparse/argparse/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,14 @@


# Standard
import argparse
import sys

# Third-Party
import pydantic

# Local
from pydantic_argparse import parsers
from pydantic_argparse import utils
from pydantic_argparse.argparse import actions
from pydantic_argparse.argparse import patches # noqa: F401
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic

# Typing
from typing import Any, Dict, Generic, List, NoReturn, Optional, Type, TypeVar
Expand Down
11 changes: 11 additions & 0 deletions src/pydantic_argparse/compatibility/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
"""Compatibiltity Shims for Declarative Typed Argument Parsing.
This package contains compatibility shims for the `pydantic` and `argparse`
modules, so that we can properly maintain version compatibility in one place.
The public interface exposed by this package is the module shims themselves.
"""

# Local
from pydantic_argparse.compatibility.argparse import argparse as argparse
from pydantic_argparse.compatibility.pydantic import pydantic as pydantic
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Monkey patches for ArgumentParser.
"""Compatibility Shim for ArgumentParser.
In order to support Python 3.8 while retaining the unit tests, we need to
backport the bugfix for [`BPO-29298`](https://bugs.python.org/issue29298).
Expand Down
19 changes: 19 additions & 0 deletions src/pydantic_argparse/compatibility/pydantic.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Compatibility Shim for Pydantic.
In order to support both Pydantic v1 and Pydantic v2, we need to make sure we
import the module correctly:
* For `pydantic~=2.0` there is a working `v1` module.
* For `pydantic==1.10.15` there is a broken `v1` module (with no `fields`).
* For `pydantic<1.10.14` there is no `v1` module.
"""


# Pydantic Shim
# There is a bit of fiddling around here to accomodate for the cases outlined
# above, as well as to keep the type-checker and language-server happy.
try: # pragma: no cover
from pydantic import v1 as pydantic
pydantic.fields # noqa: B018
except (ImportError, AttributeError): # pragma: no cover
import pydantic # type: ignore[no-redef]
8 changes: 2 additions & 6 deletions src/pydantic_argparse/parsers/boolean.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,14 @@
"""


# Standard
import argparse

# Third-Party
import pydantic

# Typing
from typing import Optional

# Local
from pydantic_argparse import utils
from pydantic_argparse.argparse import actions
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic


def should_parse(field: pydantic.fields.ModelField) -> bool:
Expand Down
8 changes: 2 additions & 6 deletions src/pydantic_argparse/parsers/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,13 @@
"""


# Standard
import argparse

# Third-Party
import pydantic

# Typing
from typing import Optional

# Local
from pydantic_argparse import utils
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic


def should_parse(field: pydantic.fields.ModelField) -> bool:
Expand Down
6 changes: 2 additions & 4 deletions src/pydantic_argparse/parsers/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@


# Standard
import argparse
import collections.abc
import enum

# Third-Party
import pydantic

# Typing
from typing import Optional

# Local
from pydantic_argparse import utils
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic


def should_parse(field: pydantic.fields.ModelField) -> bool:
Expand Down
6 changes: 2 additions & 4 deletions src/pydantic_argparse/parsers/enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,12 @@


# Standard
import argparse
import enum

# Third-Party
import pydantic

# Local
from pydantic_argparse import utils
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic

# Typing
from typing import Optional, Type
Expand Down
8 changes: 2 additions & 6 deletions src/pydantic_argparse/parsers/literal.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,10 @@
"""


# Standard
import argparse

# Third-Party
import pydantic

# Local
from pydantic_argparse import utils
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic

# Typing
from typing import Optional, Literal, get_args
Expand Down
6 changes: 2 additions & 4 deletions src/pydantic_argparse/parsers/mapping.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,16 @@


# Standard
import argparse
import ast
import collections.abc

# Third-Party
import pydantic

# Typing
from typing import Optional

# Local
from pydantic_argparse import utils
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic


def should_parse(field: pydantic.fields.ModelField) -> bool:
Expand Down
8 changes: 2 additions & 6 deletions src/pydantic_argparse/parsers/standard.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,13 @@
"""


# Standard
import argparse

# Third-Party
import pydantic

# Typing
from typing import Optional

# Local
from pydantic_argparse import utils
from pydantic_argparse.compatibility import argparse
from pydantic_argparse.compatibility import pydantic


def parse_field(
Expand Down
4 changes: 2 additions & 2 deletions src/pydantic_argparse/utils/arguments.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"""


# Third-Party
import pydantic
# Local
from pydantic_argparse.compatibility import pydantic


def name(field: pydantic.fields.ModelField, invert: bool = False) -> str:
Expand Down
4 changes: 2 additions & 2 deletions src/pydantic_argparse/utils/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"""


# Third-Party
import pydantic
# Local
from pydantic_argparse.compatibility import pydantic

# Typing
from typing import Union
Expand Down
4 changes: 2 additions & 2 deletions src/pydantic_argparse/utils/namespaces.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"""


# Standard
import argparse
# Local
from pydantic_argparse.compatibility import argparse

# Typing
from typing import Any, Dict
Expand Down
4 changes: 2 additions & 2 deletions src/pydantic_argparse/utils/pydantic.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@
# Standard
import contextlib

# Third-Party
import pydantic
# Local
from pydantic_argparse.compatibility import pydantic

# Typing
from typing import Any, Callable, Dict, Optional, Type, TypeVar, Union
Expand Down
4 changes: 2 additions & 2 deletions src/pydantic_argparse/utils/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"""


# Third-Party
import pydantic
# Local
from pydantic_argparse.compatibility import pydantic

# Typing
from typing import Any, Tuple, Union, get_origin
Expand Down
4 changes: 1 addition & 3 deletions tests/argparse/test_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,12 @@ class by testing the expected nested namespace functionality.
"""


# Standard
import argparse

# Third-Party
import pytest

# Local
from pydantic_argparse.argparse import actions
from pydantic_argparse.compatibility import argparse
from tests import conftest as conf


Expand Down
Loading

0 comments on commit 61e979d

Please sign in to comment.