xocto 4.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
xocto/__init__.py ADDED
@@ -0,0 +1,4 @@
1
+ import importlib.metadata
2
+
3
+
4
+ __version__ = importlib.metadata.version("xocto")
@@ -0,0 +1,2 @@
1
+ from .core import * # noqa
2
+ from .utils import * # noqa
xocto/events/core.py ADDED
@@ -0,0 +1,88 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Protocol
4
+
5
+ import structlog
6
+ from django import http
7
+ from django.conf import settings
8
+
9
+
10
+ logger = structlog.get_logger("events")
11
+
12
+ __all__ = ["publish"]
13
+
14
+ METADATA = {
15
+ "release": "GIT_SHA",
16
+ "aws_private_ip": "AWS_LOCAL_IP",
17
+ "aws_instance_id": "AWS_INSTANCE_ID",
18
+ "aws_availability_zone": "AWS_AVAILABILITY_ZONE",
19
+ "aws_auto_scaling_group": "AWS_AUTO_SCALING_GROUP",
20
+ }
21
+
22
+
23
+ class Account(Protocol):
24
+ number: str
25
+
26
+
27
+ def publish(
28
+ event: str,
29
+ params: dict[str, Any] | None = None,
30
+ meta: dict[str, Any] | None = None,
31
+ account: Account | None = None,
32
+ request: http.HttpRequest | None = None,
33
+ ) -> None:
34
+ """
35
+ Publish an event.
36
+
37
+ - `params` are values that were used to create the event (eg the path of a
38
+ request)
39
+ - `meta` are contextual values around the event (eg the IP address of the
40
+ person making the request)
41
+
42
+ Note, structlog will add a timestamp.
43
+ """
44
+ payload: dict[str, Any] = {"event": event}
45
+ if params is not None:
46
+ payload["params"] = params
47
+ if meta is None:
48
+ meta = {}
49
+
50
+ # If the event relates to a single account, we include its number so it's easy to filter down
51
+ # to events that affect one account in Loggly.
52
+ if account is not None:
53
+ payload["account"] = account.number
54
+
55
+ # Add static metadata from settings.
56
+ for key, setting in METADATA.items():
57
+ value = getattr(settings, setting, "")
58
+ if value:
59
+ meta[key] = value
60
+
61
+ # Add metadata from request
62
+ if request is not None:
63
+ meta.update(_request_meta(request))
64
+
65
+ payload["meta"] = meta
66
+
67
+ _log(payload)
68
+
69
+
70
+ def _log(event: dict[str, Any]) -> None:
71
+ """
72
+ Log the event.
73
+ """
74
+ event_ = event.copy()
75
+ name = event_.pop("event")
76
+ logger.info(name, **event_)
77
+
78
+
79
+ def _request_meta(request: http.HttpRequest) -> dict[str, Any]:
80
+ """
81
+ Extract relevant meta information from a request instance.
82
+ """
83
+ meta = {"ip_address": request.META["REMOTE_ADDR"]}
84
+ if "HTTP_USER_AGENT" in request.META:
85
+ meta["user_agent"] = request.META["HTTP_USER_AGENT"]
86
+ if hasattr(request, "session") and request.session.session_key:
87
+ meta["session"] = request.session.session_key
88
+ return meta
xocto/events/utils.py ADDED
@@ -0,0 +1,19 @@
1
+ import time
2
+ from typing import Any
3
+
4
+
5
+ __all__ = ["Timer"]
6
+
7
+
8
+ class Timer(object):
9
+ """
10
+ Context manager to allow easy timing of events.
11
+ """
12
+
13
+ def __enter__(self) -> "Timer":
14
+ self.start = time.time()
15
+ return self
16
+
17
+ def __exit__(self, *args: Any) -> None:
18
+ self.end = time.time()
19
+ self.duration_in_ms = (self.end - self.start) * 1000
xocto/exceptions.py ADDED
@@ -0,0 +1,4 @@
1
+ class SettlementPeriodError(Exception):
2
+ """
3
+ Basic exception raised while converting tz-aware datetime.
4
+ """
xocto/health.py ADDED
@@ -0,0 +1,28 @@
1
+ from django.db import DEFAULT_DB_ALIAS, connections
2
+ from django.db.migrations.loader import MigrationLoader
3
+
4
+
5
+ def check_migrations() -> bool:
6
+ """
7
+ Check if there are any migrations that haven't been applied yet.
8
+ """
9
+ return _num_unapplied_migrations() == 0
10
+
11
+
12
+ def _num_unapplied_migrations() -> int:
13
+ """
14
+ Return the number of unapplied migrations.
15
+ """
16
+ connection = connections[DEFAULT_DB_ALIAS]
17
+ loader = MigrationLoader(connection)
18
+ graph = loader.graph
19
+
20
+ # Count unapplied migrations
21
+ num_unapplied_migrations = 0
22
+ for app_name in loader.migrated_apps:
23
+ for node in graph.leaf_nodes(app_name):
24
+ for plan_node in graph.forwards_plan(node):
25
+ if plan_node not in loader.applied_migrations:
26
+ num_unapplied_migrations += 1
27
+
28
+ return num_unapplied_migrations