From 4ba816f56f6749b3570526372ce02391f2d4b3f6 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Thu, 8 Oct 2026 16:10:53 +0100 Subject: [PATCH 1/3] . --- CHANGELOG.md | 1 + core/util/typing_util.py | 5 +++++ 2 files changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index c8e09f9..76b410d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Added +- [MINOR] Added `UNDEFINED` and `UpdateValue[T]` to `core.util.typing_util` for partial updates: `UpdateValue[T]` is `T` or the `UNDEFINED` sentinel (pydantic's `MISSING`), so an update can tell "not provided" (`UNDEFINED`) apart from an explicit `None` - [MINOR] Added `core.store.entity_repository.EntityRepository`, which reads and writes rows of a table as pydantic models: `create`, `update`, `upsert`, `upsert_many` (postgres and sqlite), `delete`, `list_many`, `get_first`, `get`, `get_one` and `get_one_or_none`. It exposes UUID columns as strings and datetimes as aware utc datetimes, dumps pydantic values in JSON columns, generates ids for UUID primary keys and stamps `createdDate`/`updatedDate` when the table has them; subclasses can override `_convert_value_from_db`/`_convert_value_to_db` for other columns - [MINOR] Added `UUIDFieldFilter` (accepts `uuid.UUID` or `str` values) and module-level `apply_orders`, `apply_field_filters` and `get_field_filters_conditions` (conditions usable on selects, updates and deletes) to `core.store.retriever`; `Retriever` now uses them - [PATCH] Updated `DateFieldFilter` to convert values to utc for the column (naive for columns without a timezone, aware otherwise) so aware datetimes in any timezone compare correctly diff --git a/core/util/typing_util.py b/core/util/typing_util.py index 063b7c0..96d2940 100644 --- a/core/util/typing_util.py +++ b/core/util/typing_util.py @@ -1,7 +1,12 @@ from collections.abc import Mapping from collections.abc import Sequence +from pydantic.experimental.missing_sentinel import MISSING + JsonBaseType = str | int | float | bool | None type Json = dict[str, 'Json'] | Mapping[str, 'Json'] | list['Json'] | Sequence['Json'] | JsonBaseType type JsonObject = dict[str, 'Json'] type JsonList = list['Json'] + +UNDEFINED = MISSING +type UpdateValue[T] = T | MISSING # type: ignore[valid-type] From d02fb33b84638be8beac3f3b802e3264c77c733e Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Thu, 8 Oct 2026 16:14:42 +0100 Subject: [PATCH 2/3] . --- core/util/typing_util.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/core/util/typing_util.py b/core/util/typing_util.py index 96d2940..2421c32 100644 --- a/core/util/typing_util.py +++ b/core/util/typing_util.py @@ -1,12 +1,28 @@ +import enum +import typing from collections.abc import Mapping from collections.abc import Sequence -from pydantic.experimental.missing_sentinel import MISSING +from pydantic import GetCoreSchemaHandler +from pydantic_core import core_schema JsonBaseType = str | int | float | bool | None type Json = dict[str, 'Json'] | Mapping[str, 'Json'] | list['Json'] | Sequence['Json'] | JsonBaseType type JsonObject = dict[str, 'Json'] type JsonList = list['Json'] -UNDEFINED = MISSING -type UpdateValue[T] = T | MISSING # type: ignore[valid-type] + +class Undefined(enum.Enum): + UNDEFINED = 'UNDEFINED' + + def __repr__(self) -> str: + return 'UNDEFINED' + + @classmethod + def __get_pydantic_core_schema__(cls, sourceType: typing.Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: # type: ignore[explicit-any] # noqa: ARG003 + # NOTE(krishan711): only the default can be UNDEFINED, input values (e.g. "UNDEFINED" in json) must never validate to it + return core_schema.is_instance_schema(cls) + + +UNDEFINED: typing.Final = Undefined.UNDEFINED +type UpdateValue[T] = T | Undefined From b797479936570d128de3c443db03540f72c4cdf3 Mon Sep 17 00:00:00 2001 From: Krishan Patel Date: Thu, 8 Oct 2026 16:16:05 +0100 Subject: [PATCH 3/3] . --- CHANGELOG.md | 2 +- core/util/typing_util.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 76b410d..23e3567 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,7 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) ## [Unreleased] ### Added -- [MINOR] Added `UNDEFINED` and `UpdateValue[T]` to `core.util.typing_util` for partial updates: `UpdateValue[T]` is `T` or the `UNDEFINED` sentinel (pydantic's `MISSING`), so an update can tell "not provided" (`UNDEFINED`) apart from an explicit `None` +- [MINOR] Added `UNDEFINED`, `Undefined` and `UpdateValue[T]` to `core.util.typing_util` for partial updates: `UpdateValue[T]` is `T` or the `UNDEFINED` sentinel, so an update can tell "not provided" apart from an explicit `None`. Use it as a pydantic field default (`title: UpdateValue[str | None] = UNDEFINED`); request input never validates to `UNDEFINED` (a JSON `"UNDEFINED"` stays a string), and `x is not UNDEFINED` narrows `x` to `T` for type checkers - [MINOR] Added `core.store.entity_repository.EntityRepository`, which reads and writes rows of a table as pydantic models: `create`, `update`, `upsert`, `upsert_many` (postgres and sqlite), `delete`, `list_many`, `get_first`, `get`, `get_one` and `get_one_or_none`. It exposes UUID columns as strings and datetimes as aware utc datetimes, dumps pydantic values in JSON columns, generates ids for UUID primary keys and stamps `createdDate`/`updatedDate` when the table has them; subclasses can override `_convert_value_from_db`/`_convert_value_to_db` for other columns - [MINOR] Added `UUIDFieldFilter` (accepts `uuid.UUID` or `str` values) and module-level `apply_orders`, `apply_field_filters` and `get_field_filters_conditions` (conditions usable on selects, updates and deletes) to `core.store.retriever`; `Retriever` now uses them - [PATCH] Updated `DateFieldFilter` to convert values to utc for the column (naive for columns without a timezone, aware otherwise) so aware datetimes in any timezone compare correctly diff --git a/core/util/typing_util.py b/core/util/typing_util.py index 2421c32..de7092f 100644 --- a/core/util/typing_util.py +++ b/core/util/typing_util.py @@ -19,7 +19,7 @@ def __repr__(self) -> str: return 'UNDEFINED' @classmethod - def __get_pydantic_core_schema__(cls, sourceType: typing.Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: # type: ignore[explicit-any] # noqa: ARG003 + def __get_pydantic_core_schema__(cls, sourceType: typing.Any, handler: GetCoreSchemaHandler) -> core_schema.CoreSchema: # type: ignore[explicit-any] # NOTE(krishan711): only the default can be UNDEFINED, input values (e.g. "UNDEFINED" in json) must never validate to it return core_schema.is_instance_schema(cls)