From 70951b28ea414fb43d8f9e55755524aabde3781f Mon Sep 17 00:00:00 2001 From: Robert Craigie Date: Sun, 30 Apr 2023 20:45:34 +0100 Subject: [PATCH] feat(prisma): upgrade to v4.13.0 (#740) ## Change Summary Please summarise the changes this pull request is making here. ## Checklist - [ ] Unit tests for the changes exist - [ ] Tests pass without significant drop in coverage - [ ] Documentation reflects changes where applicable - [ ] Test snapshots have been [updated](https://prisma-client-py.readthedocs.io/en/latest/contributing/contributing/#snapshot-tests) if applicable ## Agreement By submitting this pull request, I confirm that you can use, modify, copy and redistribute this contribution, under the terms of your choice. --- README.md | 2 +- databases/tests/test_errors.py | 20 ++++++++++++++++++-- databases/tests/test_find_many.py | 13 ++++++++++--- docs/index.md | 2 +- docs/reference/binaries.md | 2 +- docs/reference/config.md | 4 ++-- src/prisma/__init__.py | 1 + src/prisma/_config.py | 4 ++-- src/prisma/engine/utils.py | 21 +++++++++++++++++++-- src/prisma/errors.py | 29 ++++++++++++++++++++++++++++- 10 files changed, 83 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 3c6823784..baf12c263 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ Chat on Discord - Supported Prisma version is 4.11.0 + Supported Prisma version is 4.13.0 Code style: blue diff --git a/databases/tests/test_errors.py b/databases/tests/test_errors.py index af15363fb..1f7457e59 100644 --- a/databases/tests/test_errors.py +++ b/databases/tests/test_errors.py @@ -1,6 +1,7 @@ import re import pytest +import prisma from prisma import Prisma from prisma.errors import FieldNotFoundError, ForeignKeyViolationError @@ -13,7 +14,7 @@ async def test_field_not_found_error(client: Prisma) -> None: with pytest.raises(FieldNotFoundError, match='bad_field'): await client.post.find_first(where={'bad_field': 'foo'}) # type: ignore - with pytest.raises(FieldNotFoundError, match='foo'): + with pytest.raises(FieldNotFoundError, match='data.foo'): await client.post.create( data={ 'title': 'foo', @@ -22,7 +23,7 @@ async def test_field_not_found_error(client: Prisma) -> None: } ) - with pytest.raises(FieldNotFoundError, match='foo'): + with pytest.raises(FieldNotFoundError, match='where.author.is.foo'): await client.post.find_first( where={ 'author': { @@ -34,6 +35,21 @@ async def test_field_not_found_error(client: Prisma) -> None: ) +@pytest.mark.asyncio +@pytest.mark.prisma +async def test_field_not_found_error_selection() -> None: + """The FieldNotFoundError is raised when an unknown field is passed to selections.""" + + class CustomPost(prisma.bases.BasePost): + foo_field: str + + with pytest.raises( + FieldNotFoundError, + match=r'Field \'foo_field\' not found in enclosing type \'Post\'', + ): + await CustomPost.prisma().find_first() + + @pytest.mark.asyncio async def test_foreign_key_violation_error(client: Prisma) -> None: """The ForeignKeyViolationError is raised when a foreign key is invalid.""" diff --git a/databases/tests/test_find_many.py b/databases/tests/test_find_many.py index 20c16a2f0..6326cef68 100644 --- a/databases/tests/test_find_many.py +++ b/databases/tests/test_find_many.py @@ -281,6 +281,13 @@ async def test_ordering(client: Prisma) -> None: assert found[1].published is False assert found[2].published is False + +@pytest.mark.asyncio +@pytest.mark.skip( + reason='incorrect error is raised here - requires an overhaul of the error system' +) +async def test_too_many_fields_error(client: Prisma) -> None: + """Passing in multiple fields in order is not supported""" with pytest.raises(prisma.errors.DataError) as exc: await client.post.find_many( where={ @@ -302,11 +309,11 @@ async def test_ordering(client: Prisma) -> None: @pytest.mark.asyncio async def test_order_field_not_nullable(client: Prisma) -> None: """Order by fields, if present, cannot be None""" - with pytest.raises(prisma.errors.FieldNotFoundError) as exc: + with pytest.raises( + prisma.errors.FieldNotFoundError, match=r'orderBy.desc' + ): await client.post.find_many(order={'desc': None}) # type: ignore - assert exc.match(r'desc') - @pytest.mark.asyncio async def test_distinct(client: Prisma) -> None: diff --git a/docs/index.md b/docs/index.md index ada1f243a..b92bc5991 100644 --- a/docs/index.md +++ b/docs/index.md @@ -8,7 +8,7 @@ Chat on Discord - Supported Prisma version is 4.11.0 + Supported Prisma version is 4.13.0 Code style: blue diff --git a/docs/reference/binaries.md b/docs/reference/binaries.md index ff494f81f..65d3a5fac 100644 --- a/docs/reference/binaries.md +++ b/docs/reference/binaries.md @@ -9,7 +9,7 @@ Prisma Client Python _should_ automatically download the correct binaries for yo - Clone the prisma-engines repository at the current version that the python client supports: ``` -git clone https://github.com/prisma/prisma-engines --branch=4.11.0 +git clone https://github.com/prisma/prisma-engines --branch=4.13.0 ``` - Build the binaries following the steps found [here](https://github.com/prisma/prisma-engines#building-prisma-engines) diff --git a/docs/reference/config.md b/docs/reference/config.md index 85b6f4aad..f62d0149a 100644 --- a/docs/reference/config.md +++ b/docs/reference/config.md @@ -220,7 +220,7 @@ This option controls the version of Prisma to use. It should be noted that this | Option | Environment Variable | Default | | ---------------- | --------------------- | -------- | -| `prisma_version` | `PRISMA_VERSION` | `4.11.0` | +| `prisma_version` | `PRISMA_VERSION` | `4.13.0` | ### Expected Engine Version @@ -228,7 +228,7 @@ This is an internal option that is here as a safeguard for the `prisma_version` | Option | Environment Variable | Default | | ------------------------- | -------------------------------- | ------------------------------------------ | -| `expected_engine_version` | `PRISMA_EXPECTED_ENGINE_VERSION` | `8fde8fef4033376662cad983758335009d522acb` | +| `expected_engine_version` | `PRISMA_EXPECTED_ENGINE_VERSION` | `1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a` | ### Binary Platform diff --git a/src/prisma/__init__.py b/src/prisma/__init__.py index 3448b406e..0ea607a4c 100644 --- a/src/prisma/__init__.py +++ b/src/prisma/__init__.py @@ -22,6 +22,7 @@ models as models, partials as partials, types as types, + bases as bases, ) except ModuleNotFoundError: # code has not been generated yet diff --git a/src/prisma/_config.py b/src/prisma/_config.py index 6ece117b4..2e1a59afe 100644 --- a/src/prisma/_config.py +++ b/src/prisma/_config.py @@ -18,13 +18,13 @@ class DefaultConfig(BaseSettings): # doesn't change then the CLI is incorrectly cached prisma_version: str = Field( env='PRISMA_VERSION', - default='4.11.0', + default='4.13.0', ) # Engine binary versions can be found under https://github.com/prisma/prisma-engine/commits/main expected_engine_version: str = Field( env='PRISMA_EXPECTED_ENGINE_VERSION', - default='8fde8fef4033376662cad983758335009d522acb', + default='1e7af066ee9cb95cf3a403c78d9aab3e6b04f37a', ) # Home directory, used to build the `binary_cache_dir` option by default, useful in multi-user diff --git a/src/prisma/engine/utils.py b/src/prisma/engine/utils.py index e804ba4dd..c12228939 100644 --- a/src/prisma/engine/utils.py +++ b/src/prisma/engine/utils.py @@ -31,6 +31,12 @@ 'P2025': prisma_errors.RecordNotFoundError, } +META_ERROR_MAPPING: dict[str, type[Exception]] = { + 'UnknownArgument': prisma_errors.FieldNotFoundError, + 'UnknownInputField': prisma_errors.FieldNotFoundError, + 'UnknownSelectionField': prisma_errors.FieldNotFoundError, +} + def query_engine_name() -> str: return f'prisma-query-engine-{platform.check_for_extension(platform.binary_platform())}' @@ -164,10 +170,21 @@ def handle_response_errors(resp: AbstractResponse[Any], data: Any) -> NoReturn: if 'A value is required but not set' in message: raise prisma_errors.MissingRequiredValueError(error) - exc = ERROR_MAPPING.get(code) + exc: type[Exception] | None = None + + kind = user_facing.get('meta', {}).get('kind') + if kind is not None: + exc = META_ERROR_MAPPING.get(kind) + + if exc is None: + exc = ERROR_MAPPING.get(code) + if exc is not None: raise exc(error) - except (KeyError, TypeError): + except (KeyError, TypeError) as err: + log.debug( + 'Ignoring error while constructing specialized error %s', err + ) continue try: diff --git a/src/prisma/errors.py b/src/prisma/errors.py index 589f8143b..fd064634f 100644 --- a/src/prisma/errors.py +++ b/src/prisma/errors.py @@ -1,3 +1,5 @@ +from __future__ import annotations + from typing import Any, Optional @@ -104,7 +106,21 @@ class FieldNotFoundError(DataError): # currently we cannot easily resolve the erroneous field as Prisma # returns different results for unknown fields in different situations # e.g. root query, nested query and mutation queries - ... + def __init__(self, data: Any, *, message: str | None = None) -> None: + if message is None: + meta = data.get('user_facing_error', {}).get('meta', {}) + if meta.get('kind') == 'Union': + error = _pick_union_error(meta.get('errors', [])) + else: + error = meta + + argument_path = error.get('argumentPath') + selection_path = error.get('selectionPath') + + if argument_path: + message = f'Could not find field at `{".".join(selection_path)}.{".".join(argument_path)}`' + + super().__init__(data, message=message) class RecordNotFoundError(DataError): @@ -160,3 +176,14 @@ class PrismaWarning(Warning): # Note: this is currently unused but not worth removing class UnsupportedSubclassWarning(PrismaWarning): pass + + +# TODO: proper types +def _pick_union_error(errors: list[Any]) -> Any: + # Note: uses the same heuristic as the TS client + return max( + errors, + key=lambda e: ( + len(e.get('argumentPath', [])) + len(e.get('selectionPath')) + ), + )