diff --git a/README.md b/README.md
index 3c6823784..baf12c263 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
-
+
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 @@
-
+
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'))
+ ),
+ )