mirror of
				https://github.com/arcan1s/ahriman.git
				synced 2025-11-04 07:43:42 +00:00 
			
		
		
		
	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:
		@ -116,6 +116,14 @@ ahriman.core.database.migrations.m013\_dependencies module
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :show-inheritance:
 | 
			
		||||
 | 
			
		||||
ahriman.core.database.migrations.m014\_auditlog module
 | 
			
		||||
------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
.. automodule:: ahriman.core.database.migrations.m014_auditlog
 | 
			
		||||
   :members:
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :show-inheritance:
 | 
			
		||||
 | 
			
		||||
Module contents
 | 
			
		||||
---------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -36,6 +36,14 @@ ahriman.core.database.operations.dependencies\_operations module
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :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
 | 
			
		||||
--------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -68,6 +68,14 @@ ahriman.models.dependencies module
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :show-inheritance:
 | 
			
		||||
 | 
			
		||||
ahriman.models.event module
 | 
			
		||||
---------------------------
 | 
			
		||||
 | 
			
		||||
.. automodule:: ahriman.models.event
 | 
			
		||||
   :members:
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :show-inheritance:
 | 
			
		||||
 | 
			
		||||
ahriman.models.filesystem\_package module
 | 
			
		||||
-----------------------------------------
 | 
			
		||||
 | 
			
		||||
@ -100,6 +108,14 @@ ahriman.models.log\_record\_id module
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :show-inheritance:
 | 
			
		||||
 | 
			
		||||
ahriman.models.metrics\_timer module
 | 
			
		||||
------------------------------------
 | 
			
		||||
 | 
			
		||||
.. automodule:: ahriman.models.metrics_timer
 | 
			
		||||
   :members:
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :show-inheritance:
 | 
			
		||||
 | 
			
		||||
ahriman.models.migration module
 | 
			
		||||
-------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -60,6 +60,22 @@ ahriman.web.schemas.error\_schema module
 | 
			
		||||
   :no-undoc-members:
 | 
			
		||||
   :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
 | 
			
		||||
---------------------------------------
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										21
									
								
								docs/ahriman.web.views.v1.auditlog.rst
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								docs/ahriman.web.views.v1.auditlog.rst
									
									
									
									
									
										Normal 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:
 | 
			
		||||
@ -7,6 +7,7 @@ Subpackages
 | 
			
		||||
.. toctree::
 | 
			
		||||
   :maxdepth: 4
 | 
			
		||||
 | 
			
		||||
   ahriman.web.views.v1.auditlog
 | 
			
		||||
   ahriman.web.views.v1.distributed
 | 
			
		||||
   ahriman.web.views.v1.packages
 | 
			
		||||
   ahriman.web.views.v1.service
 | 
			
		||||
 | 
			
		||||
@ -48,13 +48,8 @@ class EventOperations(Operations):
 | 
			
		||||
 | 
			
		||||
        def run(connection: Connection) -> list[Event]:
 | 
			
		||||
            return [
 | 
			
		||||
                Event(
 | 
			
		||||
                    event=row["event"],
 | 
			
		||||
                    object_id=row["object_id"],
 | 
			
		||||
                    message=row["message"],
 | 
			
		||||
                    data=row["data"],
 | 
			
		||||
                    created=row["created"],
 | 
			
		||||
                ) for row in connection.execute(
 | 
			
		||||
                Event.from_json(row)
 | 
			
		||||
                for row in connection.execute(
 | 
			
		||||
                    """
 | 
			
		||||
                    select created, event, object_id, message, data from auditlog
 | 
			
		||||
                    where (:event is null or event = :event)
 | 
			
		||||
 | 
			
		||||
@ -17,11 +17,10 @@
 | 
			
		||||
# You should have received a copy of the GNU General Public License
 | 
			
		||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
 | 
			
		||||
#
 | 
			
		||||
from dataclasses import dataclass, field, fields
 | 
			
		||||
from enum import StrEnum
 | 
			
		||||
from typing import Any, Self
 | 
			
		||||
 | 
			
		||||
from ahriman.core.utils import dataclass_view, filter_json, utcnow
 | 
			
		||||
from ahriman.core.utils import utcnow
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class EventType(StrEnum):
 | 
			
		||||
@ -41,7 +40,6 @@ class EventType(StrEnum):
 | 
			
		||||
    PackageUpdated = "package-updated"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@dataclass(frozen=True)
 | 
			
		||||
class Event:
 | 
			
		||||
    """
 | 
			
		||||
    audit log event
 | 
			
		||||
@ -54,18 +52,24 @@ class Event:
 | 
			
		||||
        object_id(str): object identifier
 | 
			
		||||
    """
 | 
			
		||||
 | 
			
		||||
    event: str | EventType
 | 
			
		||||
    object_id: str
 | 
			
		||||
    message: str | None = None
 | 
			
		||||
    data: dict[str, Any] = field(default_factory=dict)
 | 
			
		||||
    created: int = field(default_factory=lambda: int(utcnow().timestamp()))
 | 
			
		||||
    def __init__(self, event: str | EventType, object_id: str, message: str | None = None, created: int | None = None,
 | 
			
		||||
                 **kwargs: Any):
 | 
			
		||||
        """
 | 
			
		||||
        default constructor
 | 
			
		||||
 | 
			
		||||
    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
 | 
			
		||||
        """
 | 
			
		||||
        if self.event in EventType:
 | 
			
		||||
            object.__setattr__(self, "event", EventType(self.event))
 | 
			
		||||
        self.event = EventType(event) if event in EventType else event
 | 
			
		||||
        self.object_id = object_id
 | 
			
		||||
        self.created = created or int(utcnow().timestamp())
 | 
			
		||||
 | 
			
		||||
        self.message = message
 | 
			
		||||
        self.data = kwargs
 | 
			
		||||
 | 
			
		||||
    @classmethod
 | 
			
		||||
    def from_json(cls, dump: dict[str, Any]) -> Self:
 | 
			
		||||
@ -78,9 +82,25 @@ class Event:
 | 
			
		||||
        Returns:
 | 
			
		||||
            Self: dependencies object
 | 
			
		||||
        """
 | 
			
		||||
        # filter to only known fields
 | 
			
		||||
        known_fields = [pair.name for pair in fields(cls)]
 | 
			
		||||
        return cls(**filter_json(dump, known_fields))
 | 
			
		||||
        return cls(
 | 
			
		||||
            event=dump["event"],
 | 
			
		||||
            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]:
 | 
			
		||||
        """
 | 
			
		||||
@ -89,4 +109,32 @@ class Event:
 | 
			
		||||
        Returns:
 | 
			
		||||
            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
 | 
			
		||||
 | 
			
		||||
@ -8,7 +8,7 @@ def test_event_insert_get(database: SQLite, package_ahriman: Package) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    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)
 | 
			
		||||
    assert database.event_get() == [event]
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,17 +1,57 @@
 | 
			
		||||
from ahriman.models.event import Event, EventType
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def test_post_init() -> None:
 | 
			
		||||
def test_init() -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must replace event type for known types
 | 
			
		||||
    """
 | 
			
		||||
    assert Event("random", "")
 | 
			
		||||
    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:
 | 
			
		||||
    """
 | 
			
		||||
    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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@ -27,7 +27,7 @@ async def test_get(client: TestClient) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    must return all events
 | 
			
		||||
    """
 | 
			
		||||
    event1 = Event("event1", "object1", "message", {"key": "value"})
 | 
			
		||||
    event1 = Event("event1", "object1", "message", key="value")
 | 
			
		||||
    event2 = Event("event2", "object2")
 | 
			
		||||
    await client.post("/api/v1/events", json=event1.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
 | 
			
		||||
    """
 | 
			
		||||
    event1 = Event("event1", "object1", "message", {"key": "value"})
 | 
			
		||||
    event1 = Event("event1", "object1", "message", key="value")
 | 
			
		||||
    event2 = Event("event2", "object2")
 | 
			
		||||
    await client.post("/api/v1/events", json=event1.view())
 | 
			
		||||
    await client.post("/api/v1/events", json=event2.view())
 | 
			
		||||
@ -83,7 +83,7 @@ async def test_post(client: TestClient) -> None:
 | 
			
		||||
    """
 | 
			
		||||
    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)
 | 
			
		||||
 | 
			
		||||
    payload = event.view()
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user