Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pydantic V2 full support + massive refactoring #88

Open
wants to merge 86 commits into
base: dev-3.x
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
d6d8563
Reuse validation func in sorting
mahenzon Jun 9, 2024
8b4c577
Create MetadataInstanceSearch helper
mahenzon Jun 9, 2024
6076c83
Create None checker util
mahenzon Jun 9, 2024
5d2ab66
create common helpers (maybe refactor later?)
mahenzon Jun 9, 2024
2b385cd
upgrade views utils
mahenzon Jun 9, 2024
275d578
update view response annotation
mahenzon Jun 9, 2024
6db5b66
update detail view response annotation
mahenzon Jun 9, 2024
9524763
upgrade schema utils for view building
mahenzon Jun 9, 2024
aa9d30b
upgrade view base to read schemas
mahenzon Jun 9, 2024
e2b89ff
Create Client Can Set ID type metadata
mahenzon Jun 9, 2024
d305940
Create Custom SQL Filter type metadata
mahenzon Jun 9, 2024
9eeb378
Create Relationship Info type metadata
mahenzon Jun 9, 2024
511a78a
Upgrade base data layer
mahenzon Jun 9, 2024
2b1e610
Upgrade sqla orm relationships reading
mahenzon Jun 9, 2024
ff3de37
update sqla sorting imports
mahenzon Jun 9, 2024
0b7caa1
refactor sqla filtering
mahenzon Jun 9, 2024
c78abdd
refactor query string manager
mahenzon Jun 9, 2024
806a54d
remove unused relationship info
mahenzon Jun 9, 2024
e9ae714
upgrade schema builder for new arch
mahenzon Jun 9, 2024
dff8108
upgrade signature helpers
mahenzon Jun 9, 2024
624b361
update validation utils
mahenzon Jun 9, 2024
e38ef55
upgrade data layer atomic methods
mahenzon Jun 9, 2024
678c8a1
upgrade atomic handler
mahenzon Jun 9, 2024
9be857d
update prepared atomic operation wrappers
mahenzon Jun 9, 2024
364fbdd
update operation item in schema annotations
mahenzon Jun 9, 2024
437a471
create contrib LowerEqualsFilterSQL
mahenzon Jun 9, 2024
d7cf4b9
update db fixtures for tests
mahenzon Jun 9, 2024
f05e0e9
update app imports in fixtures
mahenzon Jun 9, 2024
d989475
Move test schemas to separate modules
mahenzon Jun 9, 2024
c55537f
sqla db filepath helper
mahenzon Jun 9, 2024
c22f13e
update common user api test
mahenzon Jun 9, 2024
bba527f
update conftest
mahenzon Jun 9, 2024
1b92117
upgrade test for data layer sqla filtering
mahenzon Jun 9, 2024
8be948b
read object id through view helper
mahenzon Jun 9, 2024
e054320
upgrade schemas import for tests
mahenzon Jun 9, 2024
2be7a60
allowed actions as string for pydantic v2
mahenzon Jun 9, 2024
34dac4f
tests schemas imports refactorint
mahenzon Jun 9, 2024
0c9f61b
tests for includes refactoring
mahenzon Jun 9, 2024
b59275b
micro refactoring for current atomic operation
mahenzon Jun 9, 2024
12bd0d4
refactor test for new pydantic
mahenzon Jun 9, 2024
1839ec7
update schemas import
mahenzon Jun 9, 2024
bb9ec2a
add atomic_operation_actions_as_str fixture
mahenzon Jun 9, 2024
a0080bb
update atomic test
mahenzon Jun 9, 2024
954e927
update another atomic test
mahenzon Jun 9, 2024
1f77549
refactor tests for validators and mark as xfail
mahenzon Jun 9, 2024
50d7887
examples refactoring: models and schemas upgrade
mahenzon Jun 9, 2024
411bdbc
examples create app upgrade
mahenzon Jun 9, 2024
1aa2f43
update custom filter example
mahenzon Jun 9, 2024
4734d02
logical xor
mahenzon Jun 9, 2024
9b7bab7
upgrade pre-commit hooks, run ruff and black
mahenzon Jun 9, 2024
8caccb0
upgrade dependencies
mahenzon Jun 9, 2024
36c5350
massive refactoring
mahenzon Jun 9, 2024
6abc9ea
update ruff config
mahenzon Jun 9, 2024
881caa8
Update test (get rid of unused params)
mahenzon Jun 9, 2024
0928302
update imports sorting
mahenzon Jun 9, 2024
667f522
minor refactoring
mahenzon Jun 9, 2024
0520a24
refactor ruff config in pyproject.toml
mahenzon Jun 9, 2024
8ac0e44
Revert "refactor ruff config in pyproject.toml"
mahenzon Jun 9, 2024
888a439
Revert "minor refactoring"
mahenzon Jun 9, 2024
ce8b825
refactor ruff config in pyproject.toml
mahenzon Jun 9, 2024
8138fdf
update examples annotations
mahenzon Jun 9, 2024
e189454
update tests annotations
mahenzon Jun 9, 2024
878d8b7
micro refactor code style
mahenzon Jun 9, 2024
8a1f0a6
some annotations refactoring for tortoise
mahenzon Jun 9, 2024
3cea403
some micro refactoring for sqla filtering
mahenzon Jun 9, 2024
c90bb96
micro refactoring for annotations
mahenzon Jun 9, 2024
06abe7c
API module refactoring
mahenzon Jun 9, 2024
259f1a8
SQLA dl shared node
mahenzon Jun 15, 2024
28b0200
Refactoring schema builder: included schema DTO
mahenzon Jun 15, 2024
ae0d32e
tests user schemas refactoring
mahenzon Jun 15, 2024
3eb6c3e
Pydantic migration: replace schema.__fields__ w/ schema.model_fields
mahenzon Jun 16, 2024
53a48b5
non-strict models for json:api results
mahenzon Jun 16, 2024
a51995e
metadata instance search generic annotation
mahenzon Jun 16, 2024
e4af62c
pass through pydantic validators
mahenzon Jun 16, 2024
b84412e
pass through model validators in addition to field validators
mahenzon Jun 16, 2024
7ac63c8
upgrade tests for validators
mahenzon Jun 16, 2024
e18e96a
task ids validators updates
mahenzon Jun 16, 2024
dda7baa
create JSONB Contains Filter SQL
mahenzon Jun 16, 2024
c12c029
autoformat
mahenzon Aug 9, 2024
67ae5ac
upgrade config pyproject
mahenzon Aug 9, 2024
0c9fdee
update json dependency
mahenzon Aug 9, 2024
b3850ed
get rid of poetry lock
mahenzon Aug 9, 2024
fe451f2
get rid of pytest.ini
mahenzon Aug 9, 2024
b3f1e61
update testing and linting config
mahenzon Aug 9, 2024
ce85edd
Bump version to 3.0.0
mahenzon Aug 9, 2024
e82a28f
upd testing run config
mahenzon Aug 10, 2024
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
1 change: 0 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
concurrency = greenlet
branch = True
omit =
tests/*
examples/*
docs/*
# omit anything in a .local directory anywhere
Expand Down
38 changes: 17 additions & 21 deletions .github/workflows/testing.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,19 +31,18 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: |
python -m pip install --upgrade pip poetry==1.8.2 pre-commit
poetry config virtualenvs.create false --local
- name: Install build dependencies
run: python -m pip install --upgrade pip pre-commit hatch
- name: Install dependencies
run: poetry install --all-extras
run: hatch env create
- name: Lint code
run: pre-commit run --all-files

Expand All @@ -56,6 +55,7 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
db-url:
- "sqlite+aiosqlite:///./db.sqlite3"
- "postgresql+asyncpg://user:passwd@localhost:5432/app"
Expand All @@ -81,29 +81,25 @@ jobs:
# Maps tcp port 5432 on service container to the host
- 5432:5432
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Install poetry
run: |
python -m pip install --upgrade pip poetry==1.8.2 pre-commit
poetry config virtualenvs.create false --local
- name: Install dependencies
run: poetry install --all-extras
- name: Test with pytest
run: |
flags="-s -vv --cov=fastapi_jsonapi --cov-config .coveragerc --cov-report=xml"
pytest $flags tests/
- name: ♿ Install dependencies
run: python -m pip install --upgrade pip build "hatch==1.12.0"
- name: 🔨 Build package
run: python -m build
- name: Test with pytest and coverage through hatch
run: hatch run test.py${{ matrix.python-version }}:cov-xml -v
env:
TESTING_DB_URL: ${{ matrix.db-url }}

- name: Upload coverage data to Codecov
uses: codecov/codecov-action@v3
uses: codecov/codecov-action@v4
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.xml
files: coverage/${{ matrix.python-version }}/coverage.xml
flags: unittests
name: py-${{ matrix.python-version }}-db-${{ startsWith(matrix.db-url, 'sqlite') && 'sqlite' || startsWith(matrix.db-url, 'postgres') && 'postgres' || 'unknown' }}
fail_ci_if_error: true
24 changes: 15 additions & 9 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,21 +1,27 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: "v3.2.0"
rev: "v4.6.0"
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-toml
- id: check-json
- id: check-added-large-files
- id: mixed-line-ending
- id: requirements-txt-fixer
- id: check-case-conflict

- repo: https://github.com/psf/black
rev: "23.3.0"
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.4.8
hooks:
- id: black
# Run the linter.
- id: ruff
args:
- "--fix"
# Run the formatter.
- id: ruff-format

- repo: https://github.com/charliermarsh/ruff-pre-commit
rev: "v0.1.8"
- repo: https://github.com/psf/black
rev: "24.4.2"
hooks:
- id: ruff
args: [--fix, --exit-non-zero-on-fix, --unsafe-fixes]
- id: black
3 changes: 2 additions & 1 deletion codecov.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,5 @@ comment:
require_changes: no
branches:
- main
after_n_builds: 6 # 3 python versions by 2 dbs
- dev-3.x
after_n_builds: 8 # 4 python versions by 2 dbs
4 changes: 2 additions & 2 deletions docs/http_snippets/update_snippets_with_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from http import HTTPStatus

import requests
import simplejson
import json
import argparse

parser = argparse.ArgumentParser()
Expand Down Expand Up @@ -83,7 +83,7 @@ def run_request_for_module(module_name: str):
if response.content:
# TODO: handle non-json response?
http_response_text.append(
simplejson.dumps(
json.dumps(
response.json(),
sort_keys=SORT_KEYS_ON_DUMP,
indent=2,
Expand Down
9 changes: 7 additions & 2 deletions docs/python_snippets/client_generated_id/schematic_example.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import sys
from pathlib import Path
from typing import ClassVar
from typing import (
ClassVar,
Annotated,
)

import uvicorn
from fastapi import APIRouter, Depends, FastAPI

from fastapi_jsonapi.types_metadata import ClientCanSetId
from pydantic import ConfigDict
from fastapi_jsonapi.schema_base import Field, BaseModel as PydanticBaseModel
from sqlalchemy import Column, Integer, Text
Expand Down Expand Up @@ -52,7 +57,7 @@ class UserPatchSchema(UserAttributesBaseSchema):
class UserInSchema(UserAttributesBaseSchema):
"""User input schema."""

id: int = Field(json_schema_extra={"client_can_set_id": True})
id: Annotated[int, ClientCanSetId()]


async def get_session():
Expand Down
10 changes: 6 additions & 4 deletions docs/python_snippets/relationships/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@
from sqlalchemy.orm import relationship

from examples.api_for_sqlalchemy.extensions.sqlalchemy import Base
from examples.api_for_sqlalchemy.utils.sqlalchemy.base_model_mixin import BaseModelMixin
from examples.api_for_sqlalchemy.utils.sqlalchemy.timestamps_mixin import TimestampsMixin

# TODO: sqla 2.0

class User(Base, BaseModelMixin):

class User(Base, TimestampsMixin):
__tablename__ = "users"
id = Column(Integer, primary_key=True, autoincrement=True)
name: str = Column(String)
Expand All @@ -15,7 +17,7 @@ class User(Base, BaseModelMixin):
computers = relationship("Computer", back_populates="user", uselist=True)


class Computer(Base, BaseModelMixin):
class Computer(Base, TimestampsMixin):
__tablename__ = "computers"

id = Column(Integer, primary_key=True, autoincrement=True)
Expand All @@ -24,7 +26,7 @@ class Computer(Base, BaseModelMixin):
user = relationship("User", back_populates="computers")


class UserBio(Base, BaseModelMixin):
class UserBio(Base, TimestampsMixin):
__tablename__ = "user_bio"
id = Column(Integer, primary_key=True, autoincrement=True)
birth_city: str = Column(String, nullable=False, default="", server_default="")
Expand Down
64 changes: 31 additions & 33 deletions docs/python_snippets/relationships/relationships_info_example.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
from typing import Optional
from __future__ import annotations

from typing import Annotated

from pydantic import BaseModel as PydanticBaseModel, ConfigDict

from fastapi_jsonapi.schema_base import Field, RelationshipInfo
from fastapi_jsonapi.types_metadata import RelationshipInfo


class BaseModel(PydanticBaseModel):
Expand All @@ -12,49 +14,45 @@ class BaseModel(PydanticBaseModel):
class UserBaseSchema(BaseModel):
id: int
name: str
bio: Optional["UserBioSchema"] = Field(
json_schema_extra={
"relationship": RelationshipInfo(
resource_type="user_bio",
),
},
)
computers: Optional["ComputerSchema"] = Field(
json_schema_extra={
"relationship": RelationshipInfo(
resource_type="computer",
many=True,
),
},
)
bio: Annotated[
UserBioSchema | None,
RelationshipInfo(
resource_type="user_bio",
),
]
computers: Annotated[
ComputerSchema | None,
RelationshipInfo(
resource_type="computer",
many=True,
),
]


class UserSchema(BaseModel):
id: int
name: str


class UserBioBaseSchema(BaseModel):
class UserBioSchema(BaseModel):
birth_city: str
favourite_movies: str
# keys_to_ids_list: Optional[dict[str, list[int]]] = None

user: "UserSchema" = Field(
json_schema_extra={
"relationship": RelationshipInfo(
resource_type="user",
),
},
)
user: Annotated[
UserSchema | None,
RelationshipInfo(
resource_type="user",
),
]


class ComputerBaseSchema(BaseModel):
class ComputerSchema(BaseModel):
id: int
name: str
user: Optional["UserSchema"] = Field(
json_schema_extra={
"relationship": RelationshipInfo(
resource_type="user",
),
},
)
user: Annotated[
UserSchema | None,
RelationshipInfo(
resource_type="user",
),
]
8 changes: 4 additions & 4 deletions docs/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
fastapi<0.100.0
pydantic<2
simplejson>=3.17.6
fastapi>=0.100.0
pydantic>=2
orjson>=3.10.7
sphinx
sphinx_rtd_theme
sqlalchemy<2
sqlalchemy>=2
tortoise-orm>=0.19.3
4 changes: 2 additions & 2 deletions examples/api_for_sqlalchemy/api/views_base.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import ClassVar, Dict
from typing import ClassVar

from fastapi import Depends
from pydantic import BaseModel, ConfigDict
Expand All @@ -17,7 +17,7 @@ class SessionDependency(BaseModel):
model_config = ConfigDict(arbitrary_types_allowed=True)


def handler(view: ViewBase, dto: SessionDependency) -> Dict:
def handler(view: ViewBase, dto: SessionDependency) -> dict:
return {"session": dto.session}


Expand Down
4 changes: 3 additions & 1 deletion examples/api_for_sqlalchemy/asgi.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@

from examples.api_for_sqlalchemy.main import create_app

app = create_app()
app = create_app(
create_custom_static_urls=True,
)
12 changes: 8 additions & 4 deletions examples/api_for_sqlalchemy/extensions/sqlalchemy.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
from sqlalchemy.engine import make_url
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
from sqlalchemy.orm import DeclarativeBase, sessionmaker
from sqlalchemy.orm import (
DeclarativeBase,
Mapped,
mapped_column,
sessionmaker,
)

from examples.api_for_sqlalchemy import config


class Base(DeclarativeBase):
pass
id: Mapped[int] = mapped_column(primary_key=True)


def async_session() -> sessionmaker:
engine = create_async_engine(url=make_url(config.SQLA_URI), echo=config.SQLA_ECHO)
_async_session = sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)
return _async_session
return sessionmaker(bind=engine, class_=AsyncSession, expire_on_commit=False)


class Connector:
Expand Down
Loading
Loading