Skip to content

Commit

Permalink
[NHUB-598] fix(data): Include default values on service.create, exclu…
Browse files Browse the repository at this point in the history
…de `None` values

Also make sure `@dataclass` models inherit from `superdesk.core.resources.Dataclass` class
  • Loading branch information
MarkLark86 committed Nov 27, 2024
1 parent bd1015b commit 50cf7c9
Show file tree
Hide file tree
Showing 4 changed files with 54 additions and 9 deletions.
14 changes: 7 additions & 7 deletions content_api/items/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

from pydantic import Field, field_validator

from superdesk.core.resources import ResourceModel, dataclass, fields, validators, ModelWithVersions
from superdesk.core.resources import ResourceModel, dataclass, Dataclass, fields, validators, ModelWithVersions
from superdesk.utc import utcnow


Expand All @@ -29,23 +29,23 @@


@dataclass
class CVItem:
class CVItem(Dataclass):
qcode: fields.Keyword
name: fields.Keyword
scheme: fields.Keyword | None = None
schema: fields.Keyword | None = None


@dataclass
class CVItemWithCode:
class CVItemWithCode(Dataclass):
code: fields.Keyword
name: fields.Keyword
schema: fields.Keyword | None = None
scheme: fields.Keyword | None = None


@dataclass
class Place:
class Place(Dataclass):
scheme: fields.Keyword | None = None
qcode: fields.Keyword | None = None
code: fields.Keyword | None = None
Expand All @@ -64,14 +64,14 @@ class Place:


@dataclass
class Annotation:
class Annotation(Dataclass):
id: int
type: fields.Keyword
body: fields.Keyword


@dataclass
class ContentAuthor:
class ContentAuthor(Dataclass):
uri: fields.Keyword | None = None
parent: fields.Keyword | None = None
name: fields.TextWithKeyword | None = None
Expand All @@ -83,7 +83,7 @@ class ContentAuthor:


@dataclass
class ContentReference:
class ContentReference(Dataclass):
id: Annotated[fields.Keyword, Field(alias="_id")]
key: fields.Keyword | None = None
uri: fields.Keyword | None = None
Expand Down
9 changes: 7 additions & 2 deletions superdesk/core/resources/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@

from superdesk.core.types import SortListParam, ProjectedFieldArg, BaseModel
from superdesk.core.utils import generate_guid, GUID_NEWSML

from .utils import get_model_aliased_fields
from .fields import ObjectId


Expand All @@ -65,12 +67,15 @@ def dataclass(*args, **kwargs):

class Dataclass:
@model_serializer(mode="wrap")
def ser_model(self, nxt: SerializerFunctionWrapHandler) -> dict[str, Any]:
def ser_model(self, nxt: SerializerFunctionWrapHandler):
aliased_fields = get_model_aliased_fields(self.__class__)
result = nxt(self)

# Include extra fields that were not part of the schema for this class
for key, value in self.__dict__.items():
if key not in result:
if key not in result and key not in aliased_fields:
# If this key is not already in the result dictionary, and is not an aliased field
# then add it to the key, as an unknown field
result[key] = value

return result
Expand Down
4 changes: 4 additions & 0 deletions superdesk/core/resources/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,6 +360,10 @@ async def create(self, _docs: Sequence[ResourceModelType | dict[str, Any]]) -> L
versioned_model.current_version = 1
doc_dict = doc.to_dict(
context={"use_objectid": True} if not self.config.query_objectid_as_string else {},
# Include default values, but exclude ``None`` on creation
exclude_none=True,
exclude_unset=False,
exclude_defaults=False,
)
doc.etag = doc_dict["_etag"] = self.generate_etag(doc_dict, self.config.etag_ignore_fields)
response = await self.mongo_async.insert_one(doc_dict)
Expand Down
36 changes: 36 additions & 0 deletions superdesk/core/resources/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
# AUTHORS and LICENSE files distributed with this source code, or
# at https://www.sourcefabric.org/superdesk/license

from inspect import get_annotations

from pydantic.fields import FieldInfo
from quart_babel import gettext

from superdesk.core import json
Expand Down Expand Up @@ -61,3 +64,36 @@ def get_projection_from_request(req: SearchRequest) -> tuple[bool, list[str]] |
)

raise SuperdeskApiError.badRequestError(gettext("invalid projection type"))


def get_model_aliased_fields(class_type: type) -> set[str]:
"""Returns the list of fields that are aliased
For example::
from superdesk.core.resources import dataclass, Dataclass
@dataclass
class TopicCreatedFilters(Dataclass):
created_from: Annotated[str | None, Field(alias="from")] = None
created_to: Annotated[str | None, Field(alias="to")] = None
date_filter: str | None = None
assert get_model_aliased_fields(TopicCreatedFilters) == set("created_from", "created_to")
"""

annotations = get_annotations(class_type)
aliased_fields: set[str] = set()

for field_name, annotation in annotations.items():
field_info: FieldInfo | None = next(
(
field_metadata
for field_metadata in getattr(annotation, "__metadata__", [])
if isinstance(field_metadata, FieldInfo)
),
None,
)
if field_info is not None and field_info.alias:
aliased_fields.add(field_name)

return aliased_fields

0 comments on commit 50cf7c9

Please sign in to comment.