Skip to content

Commit

Permalink
added: cmdtype tags (#223)
Browse files Browse the repository at this point in the history
* build: enable more ruff rules

* refactor: move code

* feat: make it possible to infer command

* test: move test

* feat: use entrydto and generic...
  • Loading branch information
kod-kristoff authored Feb 16, 2023
1 parent 8e5bfbc commit cc5ba9f
Show file tree
Hide file tree
Showing 16 changed files with 352 additions and 60 deletions.
1 change: 1 addition & 0 deletions karp-lex-core/mypy.ini
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ mypy_path = src
namespace_packages = True
explicit_package_bases = True
show_error_codes = True
plugins = pydantic.mypy

5 changes: 3 additions & 2 deletions karp-lex-core/ruff.toml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Enable flake8-bugbear (`B`) rules.
select = ["E", "F", "B", "I"]
select = ["E", "F", "B", "I", "RUF", "C90", "S", "YTT"]

# Never enforce `E501` (line length violations).
# ignore = ["E501"]
Expand All @@ -10,6 +10,7 @@ line-length = 97
unfixable = ["B"]

# Ignore `E402` (import violations) in all `__init__.py` files, and in `path/to/file.py`.
# [per-file-ignores]
[per-file-ignores]
"src/karp/lex_core/tests/*" = ["S101"]
# "__init__.py" = ["E402"]

4 changes: 2 additions & 2 deletions karp-lex-core/src/karp/lex_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@


from karp.lex_core import commands, value_objects
from karp.lex_core.dtos import EntryDto, EntryDtoDict
from karp.lex_core.dtos import EntryDto, GenericEntryDto

__all__ = ["EntryDto", "EntryDtoDict", "value_objects", "commands"]
__all__ = ["EntryDto", "GenericEntryDto", "value_objects", "commands"]
9 changes: 9 additions & 0 deletions karp-lex-core/src/karp/lex_core/alias_generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"""Alias generators."""


def to_lower_camel(s: str) -> str:
"""Transform snake_case to lowerCamelCase."""

return "".join(
word.capitalize() if i > 0 else word for i, word in enumerate(s.split("_"))
)
31 changes: 31 additions & 0 deletions karp-lex-core/src/karp/lex_core/commands/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
"""Commands top-level module."""

from typing import Union

from pydantic import BaseModel, Field

from .entry_commands import (
AddEntries,
AddEntriesInChunks,
Expand Down Expand Up @@ -37,3 +41,30 @@
"SetEntryRepoId",
"UpdateResource",
]


EntryCommandType = Union[AddEntry, DeleteEntry, UpdateEntry]


EntryRepoCommandType = Union[CreateEntryRepository, DeleteEntryRepository]
ResourceCommandType = Union[
CreateResource, DeleteResource, PublishResource, SetEntryRepoId, UpdateResource
]


class LexCommand(BaseModel):
command: Union[EntryCommandType, EntryRepoCommandType, ResourceCommandType] = Field(
..., discriminator="cmdtype"
)


class EntryCommand(BaseModel):
command: EntryCommandType = Field(..., discriminator="cmdtype")


class EntryRepoCommand(BaseModel):
command: EntryRepoCommandType = Field(..., discriminator="cmdtype")


class ResourceCommand(BaseModel):
command: ResourceCommandType = Field(..., discriminator="cmdtype")
13 changes: 13 additions & 0 deletions karp-lex-core/src/karp/lex_core/commands/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
"""Base class for lex commands"""

from datetime import datetime, timezone
from typing import Optional

import pydantic
from karp.lex_core import alias_generators


def utc_now() -> float:
Expand All @@ -13,6 +17,15 @@ def utc_now() -> float:

class Command(pydantic.BaseModel):
timestamp: float = pydantic.Field(default_factory=utc_now)
user: str
message: Optional[str]

class Config:
arbitrary_types_allowed = True
extra = "forbid"
alias_generator = alias_generators.to_lower_camel

def serialize(self) -> dict:
"""Export as dict with alias and without None:s."""

return self.dict(by_alias=True, exclude_none=True)
14 changes: 8 additions & 6 deletions karp-lex-core/src/karp/lex_core/commands/entry_commands.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
import typing

import pydantic
from karp.lex_core.value_objects import UniqueId, unique_id
from karp.lex_core.value_objects import UniqueIdStr, make_unique_id_str

from .base import Command


class AddEntry(Command):
entity_id: unique_id.UniqueId = pydantic.Field(
default_factory=unique_id.make_unique_id
)
entity_id: UniqueIdStr = pydantic.Field(default_factory=make_unique_id_str)
resource_id: str
entry: typing.Dict
user: str
message: str
cmdtype: typing.Literal["add_entry"] = "add_entry"


class AddEntries(Command):
Expand All @@ -30,9 +29,11 @@ class AddEntriesInChunks(AddEntries):

class DeleteEntry(Command):
resource_id: str
entity_id: UniqueId
entity_id: UniqueIdStr
version: int
user: str
message: typing.Optional[str] = None
cmdtype: typing.Literal["delete_entry"] = "delete_entry"


class ImportEntries(AddEntries):
Expand All @@ -45,11 +46,12 @@ class ImportEntriesInChunks(AddEntriesInChunks):

class UpdateEntry(Command):
resource_id: str
entity_id: UniqueId
entity_id: UniqueIdStr
# entry_id: str
version: int
entry: typing.Dict
user: str
message: str
resource_version: typing.Optional[int] = None
force: bool = False
cmdtype: typing.Literal["update_entry"] = "update_entry"
19 changes: 12 additions & 7 deletions karp-lex-core/src/karp/lex_core/commands/entry_repo_commands.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
from typing import Dict, Optional
from typing import Literal, Optional

import pydantic
from karp.lex_core.value_objects import UniqueId, make_unique_id
from karp.lex_core.value_objects import UniqueIdStr, make_unique_id_str

from .base import Command


class CreateEntryRepository(Command):
entity_id: UniqueId = pydantic.Field(default_factory=make_unique_id)
entity_id: UniqueIdStr = pydantic.Field(default_factory=make_unique_id_str)
repository_type: str
name: str
connection_str: Optional[str] = None
config: Dict
config: dict
message: str
user: str
cmdtype: Literal["create_entry_repository"] = "create_entry_repository"

@classmethod
def from_dict(cls, data: Dict, *, user: str, message: Optional[str] = None):
def from_dict(
cls, data: dict, *, user: str, message: Optional[str] = None
) -> "CreateEntryRepository":
return cls(
entity_id=make_unique_id(),
entity_id=make_unique_id_str(),
repository_type=data.pop("repository_type", "default"),
name=data.pop("resource_id"),
connection_str=data.pop("connection_str", None),
Expand All @@ -29,6 +32,8 @@ def from_dict(cls, data: Dict, *, user: str, message: Optional[str] = None):


class DeleteEntryRepository(Command):
entity_id: UniqueId
entity_id: UniqueIdStr
version: int
message: str
user: str
cmdtype: Literal["delete_entry_repository"] = "delete_entry_repository"
77 changes: 52 additions & 25 deletions karp-lex-core/src/karp/lex_core/commands/resource_commands.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,62 @@
import typing
from typing import Literal, Optional

import pydantic
from karp.lex_core.value_objects import unique_id

from .base import Command


class EntityOrResourceIdMixin(Command):
resource_id: Optional[str]
entity_id: Optional[unique_id.UniqueIdStr]

@pydantic.root_validator(pre=True)
def resource_or_entity_id(cls, values) -> dict:
entity_id = None
if "entityId" in values:
entity_id = values["entityId"]

resource_id = None
if "resourceId" in values:
resource_id = values["resourceId"]

if entity_id and resource_id:
raise ValueError("Can't give both 'entityId' and 'resourceId'")

if entity_id is None and resource_id is None:
raise ValueError("Must give either 'entityId' or 'resourceId'")

if resource_id:
return dict(values) | {
"entityId": None,
"resourceId": resource_id,
}

return dict(values) | {
"entityId": entity_id,
"resourceId": None,
}


class CreateResource(Command):
entity_id: unique_id.UniqueId = pydantic.Field(
default_factory=unique_id.make_unique_id
entity_id: unique_id.UniqueIdStr = pydantic.Field(
default_factory=unique_id.make_unique_id_str
)
resource_id: str
name: str
config: typing.Dict
message: str
user: str
entry_repo_id: unique_id.UniqueId
config: dict
entry_repo_id: unique_id.UniqueIdStr
cmdtype: Literal["create_resource"] = "create_resource"

@classmethod
def from_dict(
cls,
data: typing.Dict,
data: dict,
entry_repo_id: unique_id.UniqueIdPrimitive,
user: typing.Optional[str] = None,
message: typing.Optional[str] = None,
):
) -> "CreateResource":
try:
resource_id = data.pop("resource_id")
except KeyError as exc:
Expand All @@ -46,29 +78,24 @@ def from_dict(
)


class UpdateResource(Command):
resource_id: str
class UpdateResource(EntityOrResourceIdMixin, Command):
version: int
name: str
config: typing.Dict
message: str
user: str
config: dict
cmdtype: Literal["update_resource"] = "update_resource"


class PublishResource(Command):
resource_id: str
message: str
user: str
class PublishResource(EntityOrResourceIdMixin, Command):
version: int
cmdtype: Literal["publish_resource"] = "publish_resource"


class DeleteResource(Command):
resource_id: str
message: str
user: str
class DeleteResource(EntityOrResourceIdMixin, Command):
version: int
cmdtype: Literal["delete_resource"] = "delete_resource"


class SetEntryRepoId(Command):
resource_id: str
entry_repo_id: unique_id.UniqueId
user: str
class SetEntryRepoId(EntityOrResourceIdMixin, Command):
entry_repo_id: unique_id.UniqueIdStr
version: int
cmdtype: Literal["set_entry_repo_id"] = "set_entry_repo_id"
6 changes: 2 additions & 4 deletions karp-lex-core/src/karp/lex_core/dtos/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
from karp.lex_core.dtos.entry_dto import EntryDto
from karp.lex_core.dtos.entry_dto import EntryDto, GenericEntryDto

EntryDtoDict = EntryDto[dict]

__all__ = ["EntryDto", "EntryDtoDict"]
__all__ = ["EntryDto", "GenericEntryDto"]
15 changes: 7 additions & 8 deletions karp-lex-core/src/karp/lex_core/dtos/entry_dto.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
from datetime import datetime
from typing import Generic, Optional, TypeVar

from karp.lex_core import alias_generators
from karp.lex_core.value_objects import UniqueIdStr
from pydantic import root_validator
from pydantic.generics import GenericModel

T = TypeVar("T")


def to_lower_camel(s: str) -> str:
return "".join(
word.capitalize() if i > 0 else word for i, word in enumerate(s.split("_"))
)


class EntryDto(GenericModel, Generic[T]):
class GenericEntryDto(GenericModel, Generic[T]):
entry: T
last_modified_by: Optional[str]
last_modified: Optional[datetime]
Expand All @@ -26,7 +21,7 @@ class EntryDto(GenericModel, Generic[T]):

class Config:
extra = "forbid"
alias_generator = to_lower_camel
alias_generator = alias_generators.to_lower_camel

@root_validator(pre=True)
@classmethod
Expand All @@ -41,3 +36,7 @@ def allow_snake_case(cls, values):

def serialize(self):
return self.dict(by_alias=True, exclude_none=True)


class EntryDto(GenericEntryDto[dict]):
...
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
from karp.lex_core.dtos import EntryDtoDict
from karp.lex_core.dtos import EntryDto


def test_can_create_entry_dto():
entry_dto = EntryDtoDict(entry={"field": "value"})
entry_dto = EntryDto(entry={"field": "value"})

assert entry_dto.last_modified is None
assert entry_dto.last_modified_by is None


def test_can_create_entry_dto_with_last_modified_by():
entry_dto = EntryDtoDict(
entry_dto = EntryDto(
entry={"field": "value"}, lastModifiedBy="[email protected]"
)

Expand All @@ -31,7 +31,7 @@ def test_example_snake_case():
"last_modified": 1671443451.340828,
"last_modified_by": "local admin",
}
entry_dto = EntryDtoDict(**data)
entry_dto = EntryDto(**data)

assert entry_dto.entity_id == data["entity_id"]
assert entry_dto.resource == data["resource"]
Expand Down
Loading

0 comments on commit cc5ba9f

Please sign in to comment.