refactor: allow event to receive keyword arguments

This change also replaces the dataclass implementation of the class to
custom one
This commit is contained in:
Evgenii Alekseev 2024-08-27 15:12:26 +03:00
parent 23cd843e44
commit 31e59df2c8
11 changed files with 183 additions and 30 deletions

View File

@ -116,6 +116,14 @@ ahriman.core.database.migrations.m013\_dependencies module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.migrations.m014\_auditlog module
------------------------------------------------------
.. automodule:: ahriman.core.database.migrations.m014_auditlog
:members:
:no-undoc-members:
:show-inheritance:
Module contents Module contents
--------------- ---------------

View File

@ -36,6 +36,14 @@ ahriman.core.database.operations.dependencies\_operations module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.core.database.operations.event\_operations module
---------------------------------------------------------
.. automodule:: ahriman.core.database.operations.event_operations
:members:
:no-undoc-members:
:show-inheritance:
ahriman.core.database.operations.logs\_operations module ahriman.core.database.operations.logs\_operations module
-------------------------------------------------------- --------------------------------------------------------

View File

@ -68,6 +68,14 @@ ahriman.models.dependencies module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.event module
---------------------------
.. automodule:: ahriman.models.event
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.filesystem\_package module ahriman.models.filesystem\_package module
----------------------------------------- -----------------------------------------
@ -100,6 +108,14 @@ ahriman.models.log\_record\_id module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.models.metrics\_timer module
------------------------------------
.. automodule:: ahriman.models.metrics_timer
:members:
:no-undoc-members:
:show-inheritance:
ahriman.models.migration module ahriman.models.migration module
------------------------------- -------------------------------

View File

@ -60,6 +60,22 @@ ahriman.web.schemas.error\_schema module
:no-undoc-members: :no-undoc-members:
:show-inheritance: :show-inheritance:
ahriman.web.schemas.event\_schema module
----------------------------------------
.. automodule:: ahriman.web.schemas.event_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.event\_search\_schema module
------------------------------------------------
.. automodule:: ahriman.web.schemas.event_search_schema
:members:
:no-undoc-members:
:show-inheritance:
ahriman.web.schemas.file\_schema module ahriman.web.schemas.file\_schema module
--------------------------------------- ---------------------------------------

View File

@ -0,0 +1,21 @@
ahriman.web.views.v1.auditlog package
=====================================
Submodules
----------
ahriman.web.views.v1.auditlog.events module
-------------------------------------------
.. automodule:: ahriman.web.views.v1.auditlog.events
:members:
:no-undoc-members:
:show-inheritance:
Module contents
---------------
.. automodule:: ahriman.web.views.v1.auditlog
:members:
:no-undoc-members:
:show-inheritance:

View File

@ -7,6 +7,7 @@ Subpackages
.. toctree:: .. toctree::
:maxdepth: 4 :maxdepth: 4
ahriman.web.views.v1.auditlog
ahriman.web.views.v1.distributed ahriman.web.views.v1.distributed
ahriman.web.views.v1.packages ahriman.web.views.v1.packages
ahriman.web.views.v1.service ahriman.web.views.v1.service

View File

@ -48,13 +48,8 @@ class EventOperations(Operations):
def run(connection: Connection) -> list[Event]: def run(connection: Connection) -> list[Event]:
return [ return [
Event( Event.from_json(row)
event=row["event"], for row in connection.execute(
object_id=row["object_id"],
message=row["message"],
data=row["data"],
created=row["created"],
) for row in connection.execute(
""" """
select created, event, object_id, message, data from auditlog select created, event, object_id, message, data from auditlog
where (:event is null or event = :event) where (:event is null or event = :event)

View File

@ -17,11 +17,10 @@
# You should have received a copy of the GNU General Public License # You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
from dataclasses import dataclass, field, fields
from enum import StrEnum from enum import StrEnum
from typing import Any, Self from typing import Any, Self
from ahriman.core.utils import dataclass_view, filter_json, utcnow from ahriman.core.utils import utcnow
class EventType(StrEnum): class EventType(StrEnum):
@ -41,7 +40,6 @@ class EventType(StrEnum):
PackageUpdated = "package-updated" PackageUpdated = "package-updated"
@dataclass(frozen=True)
class Event: class Event:
""" """
audit log event audit log event
@ -54,18 +52,24 @@ class Event:
object_id(str): object identifier object_id(str): object identifier
""" """
event: str | EventType def __init__(self, event: str | EventType, object_id: str, message: str | None = None, created: int | None = None,
object_id: str **kwargs: Any):
message: str | None = None """
data: dict[str, Any] = field(default_factory=dict) default constructor
created: int = field(default_factory=lambda: int(utcnow().timestamp()))
def __post_init__(self) -> None: Args:
event(str | EventType): event type
object_id(str): object identifier
message(str | None): event message if available
created(int | None, optional): event timestamp (Default value = None)
**kwargs(Any): event metadata
""" """
convert event type to enum if it is a well-known event type self.event = EventType(event) if event in EventType else event
""" self.object_id = object_id
if self.event in EventType: self.created = created or int(utcnow().timestamp())
object.__setattr__(self, "event", EventType(self.event))
self.message = message
self.data = kwargs
@classmethod @classmethod
def from_json(cls, dump: dict[str, Any]) -> Self: def from_json(cls, dump: dict[str, Any]) -> Self:
@ -78,9 +82,25 @@ class Event:
Returns: Returns:
Self: dependencies object Self: dependencies object
""" """
# filter to only known fields return cls(
known_fields = [pair.name for pair in fields(cls)] event=dump["event"],
return cls(**filter_json(dump, known_fields)) object_id=dump["object_id"],
message=dump.get("message"),
created=dump.get("created"),
**dump.get("data", {}),
)
def get(self, key: str) -> Any:
"""
get a property
Args:
key(str): key to lookup in data
Returns:
Any: metadata property if available or ``None`` otherwise
"""
return self.data.get(key)
def view(self) -> dict[str, Any]: def view(self) -> dict[str, Any]:
""" """
@ -89,4 +109,32 @@ class Event:
Returns: Returns:
dict[str, Any]: json-friendly dictionary dict[str, Any]: json-friendly dictionary
""" """
return dataclass_view(self) dump = {
"event": self.event,
"object_id": self.object_id,
"created": self.created,
}
if self.message is not None:
dump["message"] = self.message
if self.data:
dump["data"] = self.data
return dump
def __eq__(self, other: Any) -> bool:
"""
check if other is the same object
Args:
other(Any): other object instance
Returns:
bool: ``True`` if the other object is the same and ``False`` otherwise
"""
if not isinstance(other, Event):
return False
return self.event == other.event \
and self.object_id == other.object_id \
and self.message == other.message \
and self.created == other.created \
and self.data == other.data

View File

@ -8,7 +8,7 @@ def test_event_insert_get(database: SQLite, package_ahriman: Package) -> None:
""" """
must insert and get event must insert and get event
""" """
event = Event(EventType.PackageUpdated, package_ahriman.base, "Updated", {"key": "value"}) event = Event(EventType.PackageUpdated, package_ahriman.base, "Updated", key="value")
database.event_insert(event) database.event_insert(event)
assert database.event_get() == [event] assert database.event_get() == [event]

View File

@ -1,17 +1,57 @@
from ahriman.models.event import Event, EventType from ahriman.models.event import Event, EventType
def test_post_init() -> None: def test_init() -> None:
""" """
must replace event type for known types must replace event type for known types
""" """
assert Event("random", "") assert Event("random", "")
assert isinstance(Event(str(EventType.PackageUpdated), "").event, EventType) assert isinstance(Event(str(EventType.PackageUpdated), "").event, EventType)
assert Event("", "", key="value").data == {"key": "value"}
assert Event("", "").created > 0
def test_from_json_view() -> None: def test_from_json_view() -> None:
""" """
must construct and serialize event to json must construct and serialize event to json
""" """
event = Event("event", "object", "message", {"key": "value"}) event = Event("event", "object", "message", key="value")
assert Event.from_json(event.view()) == event assert Event.from_json(event.view()) == event
def test_get() -> None:
"""
must return property correctly
"""
assert Event("event", "object", "message", key="value").get("key") == "value"
assert Event("event", "object").get("key") is None
def test_view_empty() -> None:
"""
must skip empty fields during (de-)serialization
"""
event = Event("event", "object")
assert Event.from_json(event.view()) == event
assert "message" not in event.view()
assert "data" not in event.view()
def test_eq() -> None:
"""
must compare two events
"""
event1 = Event("1", "1", "1", 1, key="value")
assert event1 == event1
event2 = Event("2", "2", "2", 2, key="value")
assert event1 != event2
def test_eq_other() -> None:
"""
must return False in case if object is not an instance of event
"""
assert Event("1", "1") != 42

View File

@ -27,7 +27,7 @@ async def test_get(client: TestClient) -> None:
""" """
must return all events must return all events
""" """
event1 = Event("event1", "object1", "message", {"key": "value"}) event1 = Event("event1", "object1", "message", key="value")
event2 = Event("event2", "object2") event2 = Event("event2", "object2")
await client.post("/api/v1/events", json=event1.view()) await client.post("/api/v1/events", json=event1.view())
await client.post("/api/v1/events", json=event2.view()) await client.post("/api/v1/events", json=event2.view())
@ -46,7 +46,7 @@ async def test_get_with_pagination(client: TestClient) -> None:
""" """
must get events with pagination must get events with pagination
""" """
event1 = Event("event1", "object1", "message", {"key": "value"}) event1 = Event("event1", "object1", "message", key="value")
event2 = Event("event2", "object2") event2 = Event("event2", "object2")
await client.post("/api/v1/events", json=event1.view()) await client.post("/api/v1/events", json=event1.view())
await client.post("/api/v1/events", json=event2.view()) await client.post("/api/v1/events", json=event2.view())
@ -83,7 +83,7 @@ async def test_post(client: TestClient) -> None:
""" """
must create event must create event
""" """
event = Event("event1", "object1", "message", {"key": "value"}) event = Event("event1", "object1", "message", key="value")
request_schema = pytest.helpers.schema_request(EventsView.post) request_schema = pytest.helpers.schema_request(EventsView.post)
payload = event.view() payload = event.view()