Skip to content
Draft
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
5 changes: 1 addition & 4 deletions .github/workflows/ci-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ on:
- "release/*"

env:
DEFAULT_PYTHON: 3.8
DEFAULT_PYTHON: 3.9
CI: "true"

concurrency:
Expand Down Expand Up @@ -118,9 +118,6 @@ jobs:
pip-cache: ~/Library/Caches/pip
- os: windows-latest
pip-cache: ~/AppData/Local/pip/Cache
exclude:
- python-version: "3.8"
pandas-version: "2.2.2"
- python-version: "3.11"
pandas-version: "1.5.3"
- python-version: "3.12"
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ repos:
rev: v3.19.1
hooks:
- id: pyupgrade
args: [--py38-plus, --keep-runtime-typing]
args: [--py39-plus, --keep-runtime-typing]

- repo: https://github.com/pycqa/pylint
rev: v3.2.7
Expand Down
6 changes: 2 additions & 4 deletions docs/source/dataframe_models.md
Original file line number Diff line number Diff line change
Expand Up @@ -339,10 +339,8 @@ to python 3.6.
✅ Good:

```{code-cell} python
try:
from typing import Annotated # python 3.9+
except ImportError:
from typing_extensions import Annotated
from typing import Annotated


class Schema(pa.DataFrameModel):
col: Series[Annotated[pd.DatetimeTZDtype, "ns", "est"]]
Expand Down
11 changes: 3 additions & 8 deletions docs/source/dtype_validation.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,10 +112,8 @@ express this same type with the class-based API, we need to use an
{py:class}`~typing.Annotated` type:

```{code-cell} python
try:
from typing import Annotated # python 3.9+
except ImportError:
from typing_extensions import Annotated
from typing import Annotated


class DateTimeModel(pa.DataFrameModel):
dt: Series[Annotated[pd.DatetimeTZDtype, "ns", "UTC"]]
Expand Down Expand Up @@ -266,10 +264,7 @@ And when using class-based API, you must specify actual types (string aliases
are not supported):

```{code-cell} python
try:
from typing import Annotated # python 3.9+
except ImportError:
from typing_extensions import Annotated
from typing import Annotated


class PyarrowModel(pa.DataFrameModel):
Expand Down
6 changes: 2 additions & 4 deletions docs/source/polars.md
Original file line number Diff line number Diff line change
Expand Up @@ -484,10 +484,8 @@ schema = pa.DataFrameSchema(
:::{tab-item} DataFrameModel (Annotated)

```{testcode} polars
try:
from typing import Annotated # python 3.9+
except ImportError:
from typing_extensions import Annotated
from typing import Annotated


class ModelWithAnnotated(pa.DataFrameModel):
list_col: Annotated[pl.List, pl.Int64()]
Expand Down
1 change: 0 additions & 1 deletion environment.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ dependencies:
- xdoctest
- nox
- setuptools # required in noxfile and not automatically provided by python >= 3.12
- importlib_metadata # required if python < 3.8

# fastapi testing
- uvicorn
Expand Down
21 changes: 10 additions & 11 deletions noxfile.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@
import re
import shutil
import sys
import tempfile
from typing import Dict, List
from typing import Optional

try:
import tomllib
Expand Down Expand Up @@ -53,7 +52,7 @@
LINE_LENGTH = 79


def _build_setup_requirements() -> Dict[str, List[str]]:
def _build_setup_requirements() -> dict[str, list[str]]:
"""Load requirments from setup.py."""
# read pyproject.toml to get optional dependencies

Expand All @@ -74,7 +73,7 @@ def _build_setup_requirements() -> Dict[str, List[str]]:
}


def _build_dev_requirements() -> List[str]:
def _build_dev_requirements() -> list[str]:
"""Load requirements from file."""
with open(REQUIREMENT_PATH, encoding="utf-8") as req_file:
reqs = []
Expand All @@ -83,11 +82,11 @@ def _build_dev_requirements() -> List[str]:
return reqs


SETUP_REQUIREMENTS: Dict[str, List[str]] = _build_setup_requirements()
DEV_REQUIREMENTS: List[str] = _build_dev_requirements()
SETUP_REQUIREMENTS: dict[str, list[str]] = _build_setup_requirements()
DEV_REQUIREMENTS: list[str] = _build_dev_requirements()


def _build_requires() -> Dict[str, Dict[str, str]]:
def _build_requires() -> dict[str, list[str]]:
"""Return a dictionary of requirements {EXTRA_NAME: {PKG_NAME:PIP_SPECS}}.

Adds fake extras "core" and "all".
Expand Down Expand Up @@ -116,7 +115,7 @@ def _build_requires() -> Dict[str, Dict[str, str]]:
return requires


REQUIRES: Dict[str, Dict[str, str]] = _build_requires()
REQUIRES = _build_requires()

CONDA_ARGS = [
"--channel=conda-forge",
Expand Down Expand Up @@ -272,11 +271,11 @@ def requirements(session: Session) -> None: # pylint:disable=unused-argument

def _get_pinned_requirements(
session: Session, pandas: str, pydantic: str, extra: str
) -> None:
) -> list[str]:
_requirements = REQUIRES["all"]
_pinned_requirements = []
_pinned_requirements: list[str] = []

_numpy: str | None = None
_numpy: Optional[str] = None
if pandas != "2.2.2":
_numpy = "< 2"

Expand Down
25 changes: 11 additions & 14 deletions pandera/api/base/checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,13 @@
from typing import (
Any,
Callable,
Dict,
Iterable,
NamedTuple,
Optional,
Tuple,
Type,
TypeVar,
Union,
no_type_check,
)
from collections.abc import Iterable

import pandas as pd
from pandera.api.function_dispatch import Dispatcher
Expand All @@ -36,8 +33,8 @@ class CheckResult(NamedTuple):
pd.core.groupby.generic.DataFrameGroupBy,
]

SeriesCheckObj = Union[pd.Series, Dict[str, pd.Series]]
DataFrameCheckObj = Union[pd.DataFrame, Dict[str, pd.DataFrame]]
SeriesCheckObj = Union[pd.Series, dict[str, pd.Series]]
DataFrameCheckObj = Union[pd.DataFrame, dict[str, pd.DataFrame]]


_T = TypeVar("_T", bound="BaseCheck")
Expand All @@ -46,15 +43,15 @@ class CheckResult(NamedTuple):
class MetaCheck(type): # pragma: no cover
"""Check metaclass."""

BACKEND_REGISTRY: Dict[Tuple[Type, Type], Type[BaseCheckBackend]] = (
BACKEND_REGISTRY: dict[tuple[type, type], type[BaseCheckBackend]] = (
{}
) # noqa
"""Registry of check backends implemented for specific data objects."""

CHECK_FUNCTION_REGISTRY: Dict[str, Dispatcher] = {} # noqa
CHECK_FUNCTION_REGISTRY: dict[str, Dispatcher] = {} # noqa
"""Built-in check function registry."""

REGISTERED_CUSTOM_CHECKS: Dict[str, Callable] = {} # noqa
REGISTERED_CUSTOM_CHECKS: dict[str, Callable] = {} # noqa
"""User-defined custom checks."""

def __getattr__(cls, name: str) -> Any:
Expand Down Expand Up @@ -85,7 +82,7 @@ def __dir__(cls) -> Iterable[str]:
# see https://mypy.readthedocs.io/en/stable/metaclasses.html#gotchas-and-limitations-of-metaclass-support
# pylint: enable=line-too-long
@no_type_check
def __contains__(cls: Type[_T], item: Union[_T, str]) -> bool:
def __contains__(cls: type[_T], item: Union[_T, str]) -> bool:
"""Allow lookups for registered checks."""
if isinstance(item, cls):
name = item.name
Expand All @@ -102,7 +99,7 @@ def __init__(
self,
name: Optional[str] = None,
error: Optional[str] = None,
statistics: Optional[Dict[str, Any]] = None,
statistics: Optional[dict[str, Any]] = None,
):
self.name = name
self.error = error
Expand Down Expand Up @@ -136,7 +133,7 @@ def from_builtin_check_name(
name: str,
init_kwargs,
error: Union[str, Callable],
statistics: Optional[Dict[str, Any]] = None,
statistics: Optional[dict[str, Any]] = None,
**check_kwargs,
):
"""Create a Check object from a built-in check's name."""
Expand All @@ -156,13 +153,13 @@ def from_builtin_check_name(
)

@classmethod
def register_backend(cls, type_: Type, backend: Type[BaseCheckBackend]):
def register_backend(cls, type_: type, backend: type[BaseCheckBackend]):
"""Register a backend for the specified type."""
if (cls, type_) not in cls.BACKEND_REGISTRY:
cls.BACKEND_REGISTRY[(cls, type_)] = backend

@classmethod
def get_backend(cls, check_obj: Any) -> Type[BaseCheckBackend]:
def get_backend(cls, check_obj: Any) -> type[BaseCheckBackend]:
"""Get the backend associated with the type of ``check_obj`` ."""

check_obj_cls = type(check_obj)
Expand Down
14 changes: 7 additions & 7 deletions pandera/api/base/error_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

from collections import defaultdict
from enum import Enum
from typing import Any, Dict, List, Union
from typing import Any, Union

from pandera.api.checks import Check
from pandera.config import ValidationDepth, get_config_context
Expand All @@ -28,8 +28,8 @@ def __init__(self, lazy: bool = True) -> None:
Defaults to True.
"""
self._lazy = lazy
self._collected_errors: List[Dict[str, Any]] = []
self._schema_errors: List[SchemaError] = []
self._collected_errors: list[dict[str, Any]] = []
self._schema_errors: list[SchemaError] = []
self._summarized_errors = defaultdict(lambda: defaultdict(list)) # type: ignore

@property
Expand Down Expand Up @@ -86,7 +86,7 @@ def collect_error(

def collect_errors(
self,
schema_errors: List[SchemaError],
schema_errors: list[SchemaError],
original_exc: Union[BaseException, None] = None,
):
"""Collect schema errors from a SchemaErrors exception.
Expand All @@ -104,19 +104,19 @@ def collect_errors(
)

@property
def collected_errors(self) -> List[Dict[str, Any]]:
def collected_errors(self) -> list[dict[str, Any]]:
"""Retrieve error objects collected during lazy validation."""
return self._collected_errors

@collected_errors.setter
def collected_errors(self, value: List[Dict[str, Any]]):
def collected_errors(self, value: list[dict[str, Any]]):
"""Set the list of collected errors."""
if not isinstance(value, list):
raise ValueError("collected_errors must be a list")
self._collected_errors = value

@property
def schema_errors(self) -> List[SchemaError]:
def schema_errors(self) -> list[SchemaError]:
"""Retrieve SchemaError objects collected during lazy validation."""
return self._schema_errors

Expand Down
37 changes: 14 additions & 23 deletions pandera/api/base/model.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,8 @@
"""Base classes for model api."""

import os
from typing import (
Any,
Dict,
List,
Mapping,
Optional,
Tuple,
Type,
TypeVar,
Union,
)
from typing import Any, Optional, TypeVar, Union
from collections.abc import Mapping

from pandera.api.base.model_components import BaseFieldInfo
from pandera.api.base.model_config import BaseModelConfig
Expand All @@ -34,24 +25,24 @@ def __str__(cls):
class BaseModel(metaclass=MetaModel):
"""Base class for a Data Object Model."""

Config: Type[BaseModelConfig] = BaseModelConfig
__extras__: Optional[Dict[str, Any]] = None
Config: type[BaseModelConfig] = BaseModelConfig
__extras__: Optional[dict[str, Any]] = None
__schema__: Optional[Any] = None
__config__: Optional[Type[BaseModelConfig]] = None
__config__: Optional[type[BaseModelConfig]] = None

#: Key according to `FieldInfo.name`
__fields__: Mapping[str, Tuple[AnnotationInfo, BaseFieldInfo]] = {}
__checks__: Dict[str, List[Check]] = {}
__root_checks__: List[Check] = []
__fields__: Mapping[str, tuple[AnnotationInfo, BaseFieldInfo]] = {}
__checks__: dict[str, list[Check]] = {}
__root_checks__: list[Check] = []

# This is syntantic sugar that delegates to the validate method
def __new__(cls, *args, **kwargs) -> Any:
raise NotImplementedError

def __class_getitem__(
cls: Type[TBaseModel],
params: Union[Type[Any], Tuple[Type[Any], ...]],
) -> Type[TBaseModel]:
cls: type[TBaseModel],
params: Union[type[Any], tuple[type[Any], ...]],
) -> type[TBaseModel]:
"""
Parameterize the class's generic arguments with the specified types.

Expand All @@ -71,7 +62,7 @@ def to_yaml(cls, stream: Optional[os.PathLike] = None):

@classmethod
def validate(
cls: Type[TBaseModel],
cls: type[TBaseModel],
check_obj: Any,
head: Optional[int] = None,
tail: Optional[int] = None,
Expand All @@ -84,12 +75,12 @@ def validate(
raise NotImplementedError

@classmethod
def strategy(cls: Type[TBaseModel], *, size: Optional[int] = None):
def strategy(cls: type[TBaseModel], *, size: Optional[int] = None):
"""Create a data synthesis strategy."""
raise NotImplementedError

@classmethod
def example(cls: Type[TBaseModel], *, size: Optional[int] = None) -> Any:
def example(cls: type[TBaseModel], *, size: Optional[int] = None) -> Any:
"""Generate an example of this data model specification."""
raise NotImplementedError

Expand Down
Loading
Loading