bedger 0.0.6__py3-none-any.whl → 0.0.7__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.
Potentially problematic release.
This version of bedger might be problematic. Click here for more details.
- bedger/edge/client.py +104 -0
- bedger/edge/entities/__init__.py +2 -2
- bedger/edge/entities/{message.py → device_event.py} +20 -5
- bedger/edge/errors.py +104 -0
- {bedger-0.0.6.dist-info → bedger-0.0.7.dist-info}/METADATA +2 -3
- bedger-0.0.7.dist-info/RECORD +12 -0
- bedger/edge/connection.py +0 -65
- bedger-0.0.6.dist-info/RECORD +0 -12
- {bedger-0.0.6.dist-info → bedger-0.0.7.dist-info}/LICENSE +0 -0
- {bedger-0.0.6.dist-info → bedger-0.0.7.dist-info}/WHEEL +0 -0
bedger/edge/client.py
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import socket
|
|
4
|
+
import logging
|
|
5
|
+
from http.client import HTTPConnection
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
from .config import Config
|
|
9
|
+
from .entities.device_event import DeviceEvent
|
|
10
|
+
from . import entities
|
|
11
|
+
from .errors import (
|
|
12
|
+
ConnectionError,
|
|
13
|
+
SendError,
|
|
14
|
+
HTTPRequestError,
|
|
15
|
+
JSONSerializationError,
|
|
16
|
+
map_socket_error,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
logger = logging.getLogger("bedger.edge.client")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class UnixSocketHTTPConnection(HTTPConnection):
|
|
23
|
+
"""HTTPConnection subclass that communicates via UNIX domain sockets."""
|
|
24
|
+
|
|
25
|
+
def __init__(self, unix_socket_path: str):
|
|
26
|
+
super().__init__("localhost") # dummy host
|
|
27
|
+
self.unix_socket_path = unix_socket_path
|
|
28
|
+
|
|
29
|
+
def connect(self):
|
|
30
|
+
"""Override to connect to a UNIX socket instead of TCP."""
|
|
31
|
+
try:
|
|
32
|
+
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
33
|
+
self.sock.connect(self.unix_socket_path)
|
|
34
|
+
except Exception as e:
|
|
35
|
+
raise map_socket_error(e, self.unix_socket_path)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class Client:
|
|
39
|
+
"""
|
|
40
|
+
Bedger Edge Client that communicates with the local agent's HTTP API
|
|
41
|
+
over a UNIX socket. Matches BedgerConnection interface.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(self, config: Config = Config()):
|
|
45
|
+
self._config = config
|
|
46
|
+
self._connection: UnixSocketHTTPConnection | None = None
|
|
47
|
+
|
|
48
|
+
def __enter__(self) -> Client:
|
|
49
|
+
self._connect()
|
|
50
|
+
return self
|
|
51
|
+
|
|
52
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
53
|
+
self._disconnect()
|
|
54
|
+
|
|
55
|
+
def _connect(self) -> None:
|
|
56
|
+
socket_path = self._config.socket_path
|
|
57
|
+
logger.info(f"Connecting to Bedger Edge API at {socket_path}")
|
|
58
|
+
try:
|
|
59
|
+
self._connection = UnixSocketHTTPConnection(socket_path)
|
|
60
|
+
except Exception as e:
|
|
61
|
+
raise ConnectionError(socket_path, e)
|
|
62
|
+
|
|
63
|
+
def _disconnect(self) -> None:
|
|
64
|
+
if self._connection:
|
|
65
|
+
try:
|
|
66
|
+
self._connection.close()
|
|
67
|
+
logger.info("Connection closed")
|
|
68
|
+
except Exception as e:
|
|
69
|
+
logger.warning(f"Error closing connection: {e}")
|
|
70
|
+
finally:
|
|
71
|
+
self._connection = None
|
|
72
|
+
|
|
73
|
+
def send_event(
|
|
74
|
+
self, event_type: str, severity: entities.Severity, payload: dict[str, Any]
|
|
75
|
+
) -> None:
|
|
76
|
+
"""Send a DeviceEvent to /device/events."""
|
|
77
|
+
if not self._connection:
|
|
78
|
+
raise ConnectionError(self._config.socket_path)
|
|
79
|
+
|
|
80
|
+
try:
|
|
81
|
+
event = DeviceEvent(event_type=event_type, severity=severity, details=payload)
|
|
82
|
+
serialized = event.model_dump_json(by_alias=True).encode("utf-8")
|
|
83
|
+
except Exception as e:
|
|
84
|
+
raise JSONSerializationError(f"Failed to serialize DeviceEvent {event_type}") from e
|
|
85
|
+
|
|
86
|
+
try:
|
|
87
|
+
self._connection.request(
|
|
88
|
+
"POST",
|
|
89
|
+
"/device/events",
|
|
90
|
+
body=serialized,
|
|
91
|
+
headers={"Content-Type": "application/json"},
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
response = self._connection.getresponse()
|
|
95
|
+
response_data = response.read().decode("utf-8")
|
|
96
|
+
|
|
97
|
+
if response.status != 200:
|
|
98
|
+
raise HTTPRequestError(response.status, response_data)
|
|
99
|
+
|
|
100
|
+
logger.info(f"Event sent successfully: {response_data}")
|
|
101
|
+
except HTTPRequestError:
|
|
102
|
+
raise
|
|
103
|
+
except Exception as e:
|
|
104
|
+
raise SendError("Failed to send DeviceEvent", e)
|
bedger/edge/entities/__init__.py
CHANGED
|
@@ -1,10 +1,18 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from typing import Dict, Any, Annotated
|
|
2
6
|
from pydantic import BaseModel, Field, field_validator, field_serializer
|
|
3
7
|
from .severity import Severity
|
|
4
|
-
import json
|
|
5
8
|
|
|
6
9
|
|
|
7
|
-
class
|
|
10
|
+
class DeviceEvent(BaseModel):
|
|
11
|
+
"""
|
|
12
|
+
Represents a stored or transmitted device event.
|
|
13
|
+
This model is compatible with the local /device/events API.
|
|
14
|
+
"""
|
|
15
|
+
|
|
8
16
|
event_type: Annotated[
|
|
9
17
|
str,
|
|
10
18
|
Field(
|
|
@@ -13,11 +21,14 @@ class Message(BaseModel):
|
|
|
13
21
|
),
|
|
14
22
|
]
|
|
15
23
|
severity: Severity
|
|
16
|
-
details: Annotated[
|
|
24
|
+
details: Annotated[
|
|
25
|
+
Dict[str, Any],
|
|
26
|
+
Field(description="Event-specific details, must be JSON serializable"),
|
|
27
|
+
]
|
|
17
28
|
|
|
18
29
|
@field_validator("details")
|
|
19
30
|
@classmethod
|
|
20
|
-
def validate_details(cls, value: Dict) -> Dict:
|
|
31
|
+
def validate_details(cls, value: Dict[str, Any]) -> Dict[str, Any]:
|
|
21
32
|
try:
|
|
22
33
|
json.dumps(value)
|
|
23
34
|
except TypeError:
|
|
@@ -27,3 +38,7 @@ class Message(BaseModel):
|
|
|
27
38
|
@field_serializer("severity")
|
|
28
39
|
def serialize_severity(self, severity: Severity) -> str:
|
|
29
40
|
return severity.value
|
|
41
|
+
|
|
42
|
+
@field_serializer("timestamp")
|
|
43
|
+
def serialize_timestamp(self, timestamp: datetime) -> str:
|
|
44
|
+
return timestamp.isoformat()
|
bedger/edge/errors.py
CHANGED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BedgerError(Exception):
|
|
6
|
+
"""Base class for all Bedger-related exceptions."""
|
|
7
|
+
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# --- Configuration and Initialization ---
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class ConfigurationError(BedgerError):
|
|
14
|
+
"""Raised when configuration values are missing, invalid, or inconsistent."""
|
|
15
|
+
def __init__(self, message: str, path: str | None = None):
|
|
16
|
+
self.path = path
|
|
17
|
+
super().__init__(f"{message}{f' (path: {path})' if path else ''}")
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class EnvironmentError(BedgerError):
|
|
21
|
+
"""Raised when the environment or system setup prevents operation (permissions, missing dirs, etc.)."""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
# --- Networking / Socket Communication ---
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ConnectionError(BedgerError):
|
|
29
|
+
"""Raised when connection to a UNIX or network socket fails."""
|
|
30
|
+
def __init__(self, socket_path: str, original_error: Exception | None = None):
|
|
31
|
+
self.socket_path = socket_path
|
|
32
|
+
self.original_error = original_error
|
|
33
|
+
super().__init__(f"Failed to connect to socket at {socket_path}: {original_error}")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class SendError(BedgerError):
|
|
37
|
+
"""Raised when sending data to Bedger Edge fails."""
|
|
38
|
+
def __init__(self, message: str, original_error: Exception | None = None):
|
|
39
|
+
self.original_error = original_error
|
|
40
|
+
super().__init__(f"{message}{f' ({original_error})' if original_error else ''}")
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class ReceiveError(BedgerError):
|
|
44
|
+
"""Raised when receiving acknowledgment or response fails."""
|
|
45
|
+
def __init__(self, message: str, original_error: Exception | None = None):
|
|
46
|
+
self.original_error = original_error
|
|
47
|
+
super().__init__(f"{message}{f' ({original_error})' if original_error else ''}")
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
# --- HTTP API Errors ---
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class HTTPRequestError(BedgerError):
|
|
54
|
+
"""Raised for failed HTTP requests to the local Bedger Edge API."""
|
|
55
|
+
def __init__(self, status_code: int, response: str):
|
|
56
|
+
self.status_code = status_code
|
|
57
|
+
self.response = response
|
|
58
|
+
super().__init__(f"Bedger API returned HTTP {status_code}: {response}")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class JSONSerializationError(BedgerError):
|
|
62
|
+
"""Raised when JSON encoding or decoding fails."""
|
|
63
|
+
pass
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
# --- Certificates / Security ---
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class CaCertificateError(BedgerError):
|
|
70
|
+
"""Raised when fetching or validating a CA certificate fails."""
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
class AuthenticationError(BedgerError):
|
|
75
|
+
"""Raised when authentication or provisioning fails."""
|
|
76
|
+
pass
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
# --- Device Twin / State Sync ---
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DeviceTwinError(BedgerError):
|
|
83
|
+
"""Raised when device twin synchronization or persistence fails."""
|
|
84
|
+
pass
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class DeviceEventError(BedgerError):
|
|
88
|
+
"""Raised when processing or storing device events fails."""
|
|
89
|
+
pass
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# --- Utilities ---
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def map_socket_error(err: Exception, socket_path: str) -> BedgerError:
|
|
96
|
+
"""Map low-level socket errors to Bedger-friendly errors."""
|
|
97
|
+
if isinstance(err, FileNotFoundError):
|
|
98
|
+
return ConnectionError(socket_path, original_error=err)
|
|
99
|
+
elif isinstance(err, PermissionError):
|
|
100
|
+
return EnvironmentError(f"Permission denied on socket {socket_path}")
|
|
101
|
+
elif isinstance(err, socket.timeout):
|
|
102
|
+
return ConnectionError(socket_path, original_error=err)
|
|
103
|
+
else:
|
|
104
|
+
return BedgerError(str(err))
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: bedger
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.7
|
|
4
4
|
Summary:
|
|
5
5
|
Author: Henk van den Brink
|
|
6
|
-
Requires-Python: >=3.8.1
|
|
6
|
+
Requires-Python: >=3.8, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
|
|
7
7
|
Classifier: Programming Language :: Python :: 3
|
|
8
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
9
8
|
Classifier: Programming Language :: Python :: 3.10
|
|
10
9
|
Classifier: Programming Language :: Python :: 3.11
|
|
11
10
|
Classifier: Programming Language :: Python :: 3.12
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
bedger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
bedger/edge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
+
bedger/edge/client.py,sha256=R8jeGipxdfjPxn2SWRsNvH2UVx2-v7u2_xw-W62T7vc,3363
|
|
4
|
+
bedger/edge/config.py,sha256=0fUKThP0aJFk3WBF7JUtuKKhXlV07SX5VnqVOLNou-A,152
|
|
5
|
+
bedger/edge/entities/__init__.py,sha256=449LXPZNzoQS7_uxAc5GoT3tx2X-TmtJ-mvy080BNbM,104
|
|
6
|
+
bedger/edge/entities/device_event.py,sha256=E4x7Yt_T2FNW1rNV1FJaPPxDxKWzH5hWDkpkeunK9f0,1273
|
|
7
|
+
bedger/edge/entities/severity.py,sha256=tUN7eDSEN5cc5eAqhvKzVHD1pSEmrdwvNEGsmh4Pnfw,157
|
|
8
|
+
bedger/edge/errors.py,sha256=QOE1aI93w9dLb6WG9c2glAnFaLMy9gN1z4PBimB8bXc,3193
|
|
9
|
+
bedger-0.0.7.dist-info/LICENSE,sha256=lVrf6pfElYZ_o6ETq-XR91_7GHTzKGyeNWAKghvcNUE,1075
|
|
10
|
+
bedger-0.0.7.dist-info/METADATA,sha256=-uIPxzTTC63VMaSCF1fcuSMeswwsjLcLey0ZMGNrbPc,434
|
|
11
|
+
bedger-0.0.7.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
12
|
+
bedger-0.0.7.dist-info/RECORD,,
|
bedger/edge/connection.py
DELETED
|
@@ -1,65 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import json
|
|
4
|
-
import socket
|
|
5
|
-
from .config import Config
|
|
6
|
-
from . import entities
|
|
7
|
-
import logging
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
logger = logging.getLogger("bedger.edge.connection")
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
class BedgerConnection:
|
|
14
|
-
def __init__(self, config: Config = Config()):
|
|
15
|
-
self._config = config
|
|
16
|
-
|
|
17
|
-
self._socket = None
|
|
18
|
-
|
|
19
|
-
def __enter__(self) -> BedgerConnection:
|
|
20
|
-
self._connect()
|
|
21
|
-
return self
|
|
22
|
-
|
|
23
|
-
def _connect(self) -> None:
|
|
24
|
-
logger.info(f"Connecting to socket at {self._config.socket_path}")
|
|
25
|
-
try:
|
|
26
|
-
self._socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
27
|
-
self._socket.connect(self._config.socket_path)
|
|
28
|
-
logger.info("Connected to socket")
|
|
29
|
-
except Exception as e:
|
|
30
|
-
logger.error(f"Error connecting to socket: {e}")
|
|
31
|
-
raise e
|
|
32
|
-
|
|
33
|
-
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
|
34
|
-
self._socket.close()
|
|
35
|
-
|
|
36
|
-
def send_event(self, event_type, severity, payload):
|
|
37
|
-
message = entities.Message(
|
|
38
|
-
event_type=event_type,
|
|
39
|
-
severity=severity,
|
|
40
|
-
details=payload,
|
|
41
|
-
)
|
|
42
|
-
|
|
43
|
-
try:
|
|
44
|
-
logger.debug("Sending message")
|
|
45
|
-
self._socket.sendall(json.dumps(message).encode())
|
|
46
|
-
|
|
47
|
-
ack = self._socket.recv(1024)
|
|
48
|
-
logger.info(f"Received acknowledgment: {ack.decode()}")
|
|
49
|
-
except Exception as e:
|
|
50
|
-
logger.error(f"Error sending message: {e}")
|
|
51
|
-
raise e
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
# Example usage
|
|
55
|
-
if __name__ == "__main__":
|
|
56
|
-
import time
|
|
57
|
-
|
|
58
|
-
connection = BedgerConnection()
|
|
59
|
-
|
|
60
|
-
with connection:
|
|
61
|
-
for i in range(5):
|
|
62
|
-
connection.send_event(
|
|
63
|
-
event_type="TestEvent", severity=entities.Severity.INFO, payload={"message": f"Test message {i}"}
|
|
64
|
-
)
|
|
65
|
-
time.sleep(1)
|
bedger-0.0.6.dist-info/RECORD
DELETED
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
bedger/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
bedger/edge/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
bedger/edge/config.py,sha256=0fUKThP0aJFk3WBF7JUtuKKhXlV07SX5VnqVOLNou-A,152
|
|
4
|
-
bedger/edge/connection.py,sha256=j5ICwK-kxsOUK-_HvWEF9iDYafoLfX3U01TNAnwj_dI,1792
|
|
5
|
-
bedger/edge/entities/__init__.py,sha256=8pu4RyMB-VfEPLiZPfLkS4j5p4cmHwFTsryYoBlb03k,91
|
|
6
|
-
bedger/edge/entities/message.py,sha256=VWJSadZe510Rggu7b1M8_E1qFzmcKatSwHN-SZgXYVg,882
|
|
7
|
-
bedger/edge/entities/severity.py,sha256=tUN7eDSEN5cc5eAqhvKzVHD1pSEmrdwvNEGsmh4Pnfw,157
|
|
8
|
-
bedger/edge/errors.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
9
|
-
bedger-0.0.6.dist-info/LICENSE,sha256=lVrf6pfElYZ_o6ETq-XR91_7GHTzKGyeNWAKghvcNUE,1075
|
|
10
|
-
bedger-0.0.6.dist-info/METADATA,sha256=kbJkOoIQSrf0HkK8SH-iPpIG5ZlGAz-cgrnVF9INKHM,412
|
|
11
|
-
bedger-0.0.6.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
12
|
-
bedger-0.0.6.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|