Skip to content

Commit

Permalink
Merge pull request #26 from workfloworchestrator/remove-stack-trace
Browse files Browse the repository at this point in the history
Removed stack trace and fix build
  • Loading branch information
pboers1988 authored Oct 22, 2024
2 parents 9da00f1 + 9342a38 commit 196f368
Show file tree
Hide file tree
Showing 20 changed files with 302 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 1.1.0
current_version = 1.1.1rc1
commit = False
tag = False
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)((\-rc)(?P<build>\d+))?
Expand Down
2 changes: 1 addition & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
[run]
include = pydantic_forms
omit = tests/
omit = tests/
2 changes: 1 addition & 1 deletion .github/workflows/run-unit-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,4 @@ jobs:
with:
token: ${{ secrets.CODECOV_TOKEN }}
fail_ci_if_error: true
files: ./coverage.xml
files: ./coverage.xml
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,3 +96,14 @@ bumpversion patch --new-version 0.0.1

Note: specifying it like this, instead of relying on bumpversion itself to increase the version, allows you to
set a "RC1" version if needed.

# Debugging Form behaviour

If you want/need the traceback of pydantic in a Form response you can add an env variable:

`
PYDANTIC_FORMS_LOGLEVEL=DEBUG
`

This will add the traceback to the `JSONResponse`. If the loglevel is set to DEBUG the library will also add the
traceback to the logger.
2 changes: 1 addition & 1 deletion codecov.yml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
ignore:
- "tests"
- "tests"
2 changes: 1 addition & 1 deletion pydantic_forms/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,4 @@

"""Pydantic-forms engine."""

__version__ = "1.1.0"
__version__ = "1.1.1rc1"
51 changes: 30 additions & 21 deletions pydantic_forms/exception_handlers/fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,46 +10,55 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import os

# TODO Decide how to expose this so pydantic-forms can be framework agnostic

from http import HTTPStatus

import structlog
from fastapi.requests import Request
from fastapi.responses import JSONResponse

from pydantic_forms.exceptions import FormException, FormNotCompleteError, FormValidationError, show_ex
from pydantic_forms.utils.json import json_dumps, json_loads

logger = structlog.get_logger(__name__)


async def form_error_handler(request: Request, exc: FormException) -> JSONResponse:
PYDANTIC_FORMS_LOGLEVEL = "DEBUG" if os.getenv("PYDANTIC_FORMS_LOGLEVEL", "INFO").upper() == "DEBUG" else "INFO"
if isinstance(exc, FormValidationError):
result = {
"type": type(exc).__name__,
"detail": str(exc),
"title": "Form not valid",
# We need to make sure there is nothing the default json.dumps cannot handle
"validation_errors": json_loads(json_dumps(exc.errors)),
"status": HTTPStatus.BAD_REQUEST,
}
if PYDANTIC_FORMS_LOGLEVEL == "DEBUG":
result["traceback"] = show_ex(exc)
logger.debug("Form validation Response", result=result)
return JSONResponse(
{
"type": type(exc).__name__,
"detail": str(exc),
"traceback": show_ex(exc),
"title": "Form not valid",
# We need to make sure the is nothing the default json.dumps cannot handle
"validation_errors": json_loads(json_dumps(exc.errors)),
"status": HTTPStatus.BAD_REQUEST,
},
result,
status_code=HTTPStatus.BAD_REQUEST,
)

if isinstance(exc, FormNotCompleteError):
result = {
"type": type(exc).__name__,
"detail": str(exc),
# We need to make sure the is nothing the default json.dumps cannot handle
"form": json_loads(json_dumps(exc.form)),
"title": "Form not complete",
"status": HTTPStatus.NOT_EXTENDED,
"meta": getattr(exc, "meta", None),
}
if PYDANTIC_FORMS_LOGLEVEL == "DEBUG":
result["traceback"] = show_ex(exc)
logger.debug("Form validation Response", result=result)
return JSONResponse(
{
"type": type(exc).__name__,
"detail": str(exc),
"traceback": show_ex(exc),
# We need to make sure the is nothing the default json.dumps cannot handle
"form": json_loads(json_dumps(exc.form)),
"title": "Form not complete",
"status": HTTPStatus.NOT_EXTENDED,
"meta": getattr(exc, "meta", None),
},
result,
status_code=HTTPStatus.NOT_EXTENDED,
)

Expand Down
4 changes: 2 additions & 2 deletions pydantic_forms/utils/json.py
Original file line number Diff line number Diff line change
Expand Up @@ -173,7 +173,7 @@ def from_serializable(dct: dict[str, Any]) -> dict[str, Any]:


if IS_ORJSON:
print("Using orjson") # noqa
logger.debug("Using orjson")

def json_loads(s: Union[str, bytes, bytearray]) -> PY_JSON_TYPES:
o = orjson.loads(s)
Expand All @@ -192,7 +192,7 @@ def json_dumps(obj: PY_JSON_TYPES, default: Callable = to_serializable) -> str:
raise e

else:
print("Using stdlib json") # noqa
logger.debug("Using stdlib json")
json_loads = json.loads
json_dumps = partial(json.dumps, default=to_serializable)

Expand Down
1 change: 0 additions & 1 deletion pydantic_forms/validators/components/migration_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ class _MigrationSummary(BaseModel):

def create_json_extra_schema(data: SummaryData, schema: dict[str, Any]) -> None:
schema.update({"format": "summary", "type": "string", "uniforms": {"data": data}})
schema.pop("allOf") # This is needed, because otherwise Uniforms (3.8.1) is unable to render this schema


def migration_summary(data: SummaryData) -> type[MigrationSummary]:
Expand Down
2 changes: 0 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,6 @@ test = [
"ruff",
"types-Deprecated",
"types-certifi",
"types-itsdangerous",
"types-pkg_resources",
"types-python-dateutil",
"types-pytz",
"types-toml",
Expand Down
24 changes: 24 additions & 0 deletions tests/unit_tests/exception_handlers/test_fastapi.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,18 @@ async def test_form_not_complete():
body = response.body.decode()
assert "FormNotCompleteError" in body
assert "foobar" in body
assert "traceback" not in body


async def test_form_not_complete_with_stack_trace(monkeypatch):
monkeypatch.setenv("PYDANTIC_FORMS_LOGLEVEL", "DEBUG")
exception = FormNotCompleteError({"message": "foobar"})
response = await form_error_handler(mock.Mock(spec=Request), exception)
assert response.status_code == HTTPStatus.NOT_EXTENDED
body = response.body.decode()
assert "FormNotCompleteError" in body
assert "foobar" in body
assert "traceback" in body


@pytest.fixture
Expand All @@ -37,6 +49,18 @@ async def test_form_validation(example_form_error_invalid_int):
body = response.body.decode()
assert "FormValidationError" in body
assert "should be a valid integer" in body
assert "traceback" not in body


async def test_form_validation_with_stack_trace(example_form_error_invalid_int, monkeypatch):
monkeypatch.setenv("PYDANTIC_FORMS_LOGLEVEL", "DEBUG")
exception = FormValidationError("myvalidator", example_form_error_invalid_int)
response = await form_error_handler(mock.Mock(spec=Request), exception)
assert response.status_code == HTTPStatus.BAD_REQUEST
body = response.body.decode()
assert "FormValidationError" in body
assert "should be a valid integer" in body
assert "traceback" in body


async def test_overflow_error():
Expand Down
12 changes: 12 additions & 0 deletions tests/unit_tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
def assert_equal_ignore_key(expected, actual, ignore_keys):
def deep_remove_keys(d, keys_to_ignore):
if isinstance(d, dict):
return {k: deep_remove_keys(v, keys_to_ignore) for k, v in d.items() if k not in keys_to_ignore}
elif isinstance(d, list):
return [deep_remove_keys(i, keys_to_ignore) for i in d]
return d

stripped_expected = deep_remove_keys(expected, ignore_keys)
stripped_actual = deep_remove_keys(actual, ignore_keys)

assert stripped_expected == stripped_actual, f"Expected {stripped_expected}, but got {stripped_actual}"
4 changes: 2 additions & 2 deletions tests/unit_tests/test_accept.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from pydantic_forms.core import FormPage
from pydantic_forms.utils.json import json_loads
from pydantic_forms.validators import Accept, AcceptValues
from tests.unit_tests.helpers import assert_equal_ignore_key


def test_accept_ok():
Expand Down Expand Up @@ -93,7 +94,6 @@ class Form(FormPage):
"loc": ("accept",),
"msg": "Input should be 'ACCEPTED' or 'INCOMPLETE'",
"type": "enum",
"url": "https://errors.pydantic.dev/2.7/v/enum",
}
]
assert error_info.value.errors() == expected
assert_equal_ignore_key(expected, error_info.value.errors(), ["url"])
2 changes: 1 addition & 1 deletion tests/unit_tests/test_choice_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ def test_choice_list_constraint_at_least_one_item(Form):
{
"input": [],
"loc": ("choice",),
"msg": "List should have at least 1 item after validation, not 0",
"msg": "Value should have at least 1 item after validation, not 0",
"type": "too_short",
# "ctx": {"limit_value": 1},
}
Expand Down
4 changes: 2 additions & 2 deletions tests/unit_tests/test_constrained_list.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ def test_constrained_list_too_short():
# "ctx": {"error": ListMinLengthError(limit_value=1)},
"input": [],
"loc": ("v",),
"msg": "List should have at least 1 item after validation, not 0",
"msg": "Value should have at least 1 item after validation, not 0",
"type": "too_short",
}
]
Expand Down Expand Up @@ -115,7 +115,7 @@ class UniqueConListModel(FormPage):
{
"input": [],
"loc": ("v",),
"msg": "List should have at least 1 item after validation, not 0",
"msg": "Value should have at least 1 item after validation, not 0",
"type": "too_short",
# "ctx": {"limit_value": 1},
}
Expand Down
4 changes: 3 additions & 1 deletion tests/unit_tests/test_display_subscription.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ class Form(FormPage):
summary: Summary

expected = {
"$defs": {"MigrationSummaryValue": {"properties": {}, "title": "MigrationSummaryValue", "type": "object"}},
"additionalProperties": False,
"properties": {
"display_sub": {
Expand All @@ -52,8 +53,9 @@ class Form(FormPage):
"type": "string",
},
"summary": {
"format": "summary",
"$ref": "#/$defs/MigrationSummaryValue",
"default": None,
"format": "summary",
"type": "string",
"uniforms": {"data": {"headers": ["one"]}},
},
Expand Down
4 changes: 2 additions & 2 deletions tests/unit_tests/test_list_of_two.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ def test_list_of_two_min_items(Form):
{
"input": [1],
"loc": ("two",),
"msg": "List should have at least 2 items after validation, not 1",
"msg": "Value should have at least 2 items after validation, not 1",
"type": "too_short",
}
]
Expand All @@ -42,7 +42,7 @@ def test_list_of_two_max_items(Form):
{
"input": [1, 2, 3],
"loc": ("two",),
"msg": "List should have at most 2 items after validation, not 3",
"msg": "Value should have at most 2 items after validation, not 3",
"type": "too_long",
},
]
Expand Down
4 changes: 3 additions & 1 deletion tests/unit_tests/test_migration_summary.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ class Form(FormPage):
ms: Summary

expected = {
"$defs": {"MigrationSummaryValue": {"properties": {}, "title": "MigrationSummaryValue", "type": "object"}},
"additionalProperties": False,
"properties": {
"ms": {
"format": "summary",
"$ref": "#/$defs/MigrationSummaryValue",
"default": None,
"format": "summary",
"type": "string",
"uniforms": {
"data": {"headers": ["one"]},
Expand Down
Loading

0 comments on commit 196f368

Please sign in to comment.