Skip to content

Commit

Permalink
Fix invalid serialization and parsing (#201)
Browse files Browse the repository at this point in the history
  • Loading branch information
zmievsa authored Jul 11, 2024
1 parent 18f2606 commit 836fc87
Show file tree
Hide file tree
Showing 12 changed files with 177 additions and 97 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
name: CI
on:
push:
branches: [main]
branches: [main, 3.x.x]
paths:
- "**.py"
- "**.toml"
- "**.lock"
pull_request:
branches: [main]
branches: [main, 3.x.x]
types: [opened, synchronize]
paths:
- "**.py"
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/validate_links.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@ on:
push:
branches:
- main
- 3.x.x
paths:
- "**.md"
pull_request:
branches: [main]
branches: [main, 3.x.x]
types: [opened, synchronize]
paths:
- "**.md"
Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ Please follow [the Keep a Changelog standard](https://keepachangelog.com/en/1.0.

## [Unreleased]

## [3.15.8]

### Fixed

* Invalid unparseable JSON response without quotes when the response was a raw string JSONResponse
* An exception raised during codegen if a pydantic model or its parent were created within some indent such as classes defined under if statements

## [3.15.7]

### Fixed
Expand Down
6 changes: 3 additions & 3 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@ test:
--cov-fail-under=${FAIL_UNDER};

ci_supertest:
poetry run pip install 'pydantic==1.10.13' && \
poetry run pip install 'pydantic==1.10.17' && \
make test && \
poetry run pip install 'pydantic==2.5.3' && \
poetry run pip install 'pydantic==2.8.2' && \
make test FAIL_UNDER=100;

supertest:
poetry install --all-extras
make ci_supertest || rm coverage.xml .coverage && exit 1; \
poetry run pip install 'pydantic==2.5.3' && \
poetry run pip install 'pydantic==2.8.2' && \
rm coverage.xml .coverage;
1 change: 0 additions & 1 deletion cadwyn/_asts.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,6 @@ def transform_other(value: Any) -> Any:
def _get_lambda_source_from_default_factory(source: str) -> str:
found_lambdas: list[ast.Lambda] = []

ast.parse(source)
for node in ast.walk(ast.parse(source)):
if isinstance(node, ast.keyword) and node.arg == "default_factory" and isinstance(node.value, ast.Lambda):
found_lambdas.append(node.value)
Expand Down
3 changes: 2 additions & 1 deletion cadwyn/codegen/_common.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import ast
import dataclasses
import inspect
import textwrap
from dataclasses import dataclass
from enum import Enum
from functools import cache
Expand Down Expand Up @@ -89,7 +90,7 @@ def get_fields_and_validators_from_model(
{},
)
else:
cls_ast = cast(ast.ClassDef, ast.parse(source).body[0])
cls_ast = cast(ast.ClassDef, ast.parse(textwrap.dedent(source)).body[0])
validators: dict[str, _ValidatorWrapper] = {}

validators_and_nones = (
Expand Down
3 changes: 2 additions & 1 deletion cadwyn/structure/modules.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import ast
import dataclasses
import textwrap
from dataclasses import InitVar, dataclass
from types import ModuleType

Expand All @@ -13,7 +14,7 @@ class AlterModuleInstruction:
import_: ast.Import | ast.ImportFrom = dataclasses.field(init=False)

def __post_init__(self, raw_import: str):
parsed_body = ast.parse(raw_import).body
parsed_body = ast.parse(textwrap.dedent(raw_import)).body
if len(parsed_body) > 1:
raise CadwynStructureError(
f"You have specified more than just a single import. This is prohibited. "
Expand Down
5 changes: 4 additions & 1 deletion cadwyn/structure/versions.py
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,10 @@ async def _convert_endpoint_response_to_version( # noqa: C901
# that do not have it. We don't support it too.
if response_info.body is not None and hasattr(response_info._response, "body"):
# TODO (https://github.com/zmievsa/cadwyn/issues/51): Only do this if there are migrations
if isinstance(response_info.body, str):
if (
isinstance(response_info.body, str)
and response_info._response.headers.get("content-type") != "application/json"
):
response_info._response.body = response_info.body.encode(response_info._response.charset)
else:
response_info._response.body = json.dumps(
Expand Down
196 changes: 110 additions & 86 deletions poetry.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "cadwyn"
version = "3.15.7"
version = "3.15.8"
description = "Production-ready community-driven modern Stripe-like API versioning in FastAPI"
authors = ["Stanislav Zmiev <[email protected]>"]
license = "MIT"
Expand Down
20 changes: 20 additions & 0 deletions tests/codegen/test_interactions.py
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,26 @@ class MyClass(BaseModel):
)


def test__codegen__with_indented_classes(
create_local_simple_versioned_packages: CreateLocalSimpleVersionedPackages,
head_module_for: HeadModuleFor,
) -> None:
latest = head_module_for(
"""
from pydantic import BaseModel
if True:
class ConfigMixin(BaseModel):
pass
class MyClass(ConfigMixin):
foo: str
""",
)
v1 = create_local_simple_versioned_packages(schema(latest.MyClass).field("bar").existed_as(type=str))
assert inspect.getsource(v1.MyClass) == ("class MyClass(ConfigMixin):\n" " foo: str\n bar: str\n")


def test__codegen_preserves_arbitrary_expressions(
create_local_simple_versioned_packages: CreateLocalSimpleVersionedPackages,
head_module_for: HeadModuleFor,
Expand Down
24 changes: 24 additions & 0 deletions tests/test_data_migrations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,6 +1224,30 @@ def response_converter(response: ResponseInfo):
assert resp_2001.json() == 83


def test__response_migrations__with_manual_string_json_response_and_migration(
create_versioned_clients: CreateVersionedClients,
head_module,
router: VersionedAPIRouter,
):
@router.post("/test")
async def endpoint():
return JSONResponse(content="My content")

@convert_response_to_previous_version_for("/test", ["POST"])
def response_converter(response: ResponseInfo):
pass

clients = create_versioned_clients(version_change(resp=response_converter))

resp_2000 = clients[date(2000, 1, 1)].post("/test")
assert resp_2000.status_code == 200
assert resp_2000.json() == "My content"

resp_2001 = clients[date(2001, 1, 1)].post("/test")
assert resp_2001.status_code == 200
assert resp_2001.json() == "My content"


@pytest.mark.parametrize(("path", "method"), [("/NOT_test", "POST"), ("/test", "PUT")])
def test__request_by_path_migration__for_nonexistent_endpoint_path__should_raise_error(
create_versioned_clients: CreateVersionedClients,
Expand Down

0 comments on commit 836fc87

Please sign in to comment.