bedger 0.0.9__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.
- bedger/__init__.py +0 -0
- bedger/edge/__init__.py +0 -0
- bedger/edge/client.py +204 -0
- bedger/edge/config.py +8 -0
- bedger/edge/entities/__init__.py +5 -0
- bedger/edge/entities/device.py +6 -0
- bedger/edge/entities/device_event.py +39 -0
- bedger/edge/entities/device_twin.py +104 -0
- bedger/edge/entities/enums.py +6 -0
- bedger/edge/entities/severity.py +9 -0
- bedger/edge/errors.py +116 -0
- bedger-0.0.9.dist-info/LICENSE +21 -0
- bedger-0.0.9.dist-info/METADATA +14 -0
- bedger-0.0.9.dist-info/RECORD +15 -0
- bedger-0.0.9.dist-info/WHEEL +4 -0
bedger/__init__.py
ADDED
|
File without changes
|
bedger/edge/__init__.py
ADDED
|
File without changes
|
bedger/edge/client.py
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
4
|
+
import json
|
|
5
|
+
import socket
|
|
6
|
+
import jsonpatch
|
|
7
|
+
import logging
|
|
8
|
+
from http.client import HTTPConnection
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from .config import Config
|
|
12
|
+
from .entities.device_event import DeviceEvent
|
|
13
|
+
from . import entities
|
|
14
|
+
from .errors import (
|
|
15
|
+
ConnectionError,
|
|
16
|
+
SendError,
|
|
17
|
+
HTTPRequestError,
|
|
18
|
+
map_socket_error,
|
|
19
|
+
)
|
|
20
|
+
from .entities.device_twin import DeviceTwin, DeviceTwinPatch
|
|
21
|
+
|
|
22
|
+
logger = logging.getLogger("bedger.edge.client")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class UnixSocketHTTPConnection(HTTPConnection):
|
|
26
|
+
"""HTTPConnection subclass that communicates via UNIX domain sockets."""
|
|
27
|
+
|
|
28
|
+
def __init__(self, unix_socket_path: str):
|
|
29
|
+
super().__init__("localhost") # dummy host
|
|
30
|
+
self.unix_socket_path = unix_socket_path
|
|
31
|
+
|
|
32
|
+
def connect(self):
|
|
33
|
+
"""Override to connect to a UNIX socket instead of TCP."""
|
|
34
|
+
try:
|
|
35
|
+
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
36
|
+
self.sock.connect(self.unix_socket_path)
|
|
37
|
+
except Exception as e:
|
|
38
|
+
raise map_socket_error(e, self.unix_socket_path)
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class Client:
|
|
42
|
+
"""
|
|
43
|
+
Bedger Edge Client that communicates with the local agent's HTTP API
|
|
44
|
+
over a UNIX socket. Matches BedgerConnection interface.
|
|
45
|
+
"""
|
|
46
|
+
|
|
47
|
+
def __init__(self, config: Config = Config()):
|
|
48
|
+
self._config = config
|
|
49
|
+
self._connection: UnixSocketHTTPConnection | None = None
|
|
50
|
+
|
|
51
|
+
def __enter__(self) -> Client:
|
|
52
|
+
self._connect()
|
|
53
|
+
return self
|
|
54
|
+
|
|
55
|
+
def __exit__(self, exc_type, exc_value, traceback):
|
|
56
|
+
self._disconnect()
|
|
57
|
+
|
|
58
|
+
def _connect(self) -> None:
|
|
59
|
+
socket_path = self._config.socket_path
|
|
60
|
+
logger.info(f"Connecting to Bedger Edge API at {socket_path}")
|
|
61
|
+
try:
|
|
62
|
+
self._connection = UnixSocketHTTPConnection(socket_path)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
raise ConnectionError(socket_path, e)
|
|
65
|
+
|
|
66
|
+
def _disconnect(self) -> None:
|
|
67
|
+
if self._connection:
|
|
68
|
+
try:
|
|
69
|
+
self._connection.close()
|
|
70
|
+
logger.info("Connection closed")
|
|
71
|
+
except Exception as e:
|
|
72
|
+
logger.warning(f"Error closing connection: {e}")
|
|
73
|
+
finally:
|
|
74
|
+
self._connection = None
|
|
75
|
+
|
|
76
|
+
def is_healthy(self) -> bool:
|
|
77
|
+
"""Ping the Bedger Edge API health endpoint."""
|
|
78
|
+
if not self._connection:
|
|
79
|
+
raise ConnectionError(self._config.socket_path)
|
|
80
|
+
|
|
81
|
+
try:
|
|
82
|
+
self._connection.request("GET", "/health")
|
|
83
|
+
response = self._connection.getresponse()
|
|
84
|
+
raw_data = response.read().decode("utf-8")
|
|
85
|
+
|
|
86
|
+
if response.status != 200:
|
|
87
|
+
return False
|
|
88
|
+
|
|
89
|
+
logger.debug(f"Health response: {raw_data}")
|
|
90
|
+
return True
|
|
91
|
+
except Exception as e:
|
|
92
|
+
return False
|
|
93
|
+
|
|
94
|
+
def send_event(self, event_type: str, severity: entities.Severity, payload: dict[str, Any]) -> None:
|
|
95
|
+
"""Send a DeviceEvent to /device/events."""
|
|
96
|
+
if not self._connection:
|
|
97
|
+
raise ConnectionError(self._config.socket_path)
|
|
98
|
+
|
|
99
|
+
event = DeviceEvent(event_type=event_type, severity=severity, details=payload)
|
|
100
|
+
serialized = event.model_dump_json(by_alias=True).encode("utf-8")
|
|
101
|
+
|
|
102
|
+
try:
|
|
103
|
+
self._connection.request(
|
|
104
|
+
"POST",
|
|
105
|
+
"/device/events",
|
|
106
|
+
body=serialized,
|
|
107
|
+
headers={"Content-Type": "application/json"},
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
response = self._connection.getresponse()
|
|
111
|
+
response_data = response.read().decode("utf-8")
|
|
112
|
+
|
|
113
|
+
if response.status != 200:
|
|
114
|
+
raise HTTPRequestError(response.status, response_data)
|
|
115
|
+
|
|
116
|
+
logger.info(f"Event sent successfully: {response_data}")
|
|
117
|
+
except HTTPRequestError:
|
|
118
|
+
raise
|
|
119
|
+
except Exception as e:
|
|
120
|
+
raise SendError("Failed to send DeviceEvent", e)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class DeviceTwinClient(Client):
|
|
124
|
+
"""Manages device twin synchronization over the Bedger Edge API."""
|
|
125
|
+
|
|
126
|
+
def __init__(self, config: Config = Config()) -> None:
|
|
127
|
+
super().__init__(config)
|
|
128
|
+
|
|
129
|
+
def __enter__(self) -> DeviceTwinClient:
|
|
130
|
+
super().__enter__()
|
|
131
|
+
self._twin = self._get_twin()
|
|
132
|
+
return self
|
|
133
|
+
|
|
134
|
+
def get_twin(self) -> DeviceTwin:
|
|
135
|
+
return copy.deepcopy(self._twin)
|
|
136
|
+
|
|
137
|
+
def _get_twin(self) -> DeviceTwin:
|
|
138
|
+
"""Fetch and parse the full DeviceTwin model from the Bedger Edge API."""
|
|
139
|
+
if not self._connection:
|
|
140
|
+
raise ConnectionError(self._config.socket_path)
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
self._connection.request("GET", "/device/twin")
|
|
144
|
+
response = self._connection.getresponse()
|
|
145
|
+
raw_data = response.read().decode("utf-8")
|
|
146
|
+
|
|
147
|
+
if response.status != 200:
|
|
148
|
+
raise HTTPRequestError(response.status, raw_data)
|
|
149
|
+
|
|
150
|
+
logger.debug(f"Fetched raw device twin: {raw_data}")
|
|
151
|
+
twin = DeviceTwin.model_validate_json(raw_data)
|
|
152
|
+
logger.info("✅ DeviceTwin loaded for device")
|
|
153
|
+
return twin
|
|
154
|
+
|
|
155
|
+
except HTTPRequestError:
|
|
156
|
+
raise
|
|
157
|
+
except Exception as e:
|
|
158
|
+
raise SendError("Failed to get device twin", e)
|
|
159
|
+
|
|
160
|
+
def patch(self, device_twin: DeviceTwin) -> DeviceTwin:
|
|
161
|
+
if not self._connection:
|
|
162
|
+
raise ConnectionError(self._config.socket_path)
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
current_reported = self._twin.properties_reported.model_dump(by_alias=True)
|
|
166
|
+
new_reported = device_twin.properties_reported.model_dump(by_alias=True)
|
|
167
|
+
|
|
168
|
+
patch_ops = jsonpatch.make_patch(current_reported, new_reported)
|
|
169
|
+
|
|
170
|
+
if not patch_ops:
|
|
171
|
+
logger.info("No changes detected in reported properties; skipping patch.")
|
|
172
|
+
return device_twin
|
|
173
|
+
|
|
174
|
+
twin_patch = DeviceTwinPatch(
|
|
175
|
+
patch=patch_ops,
|
|
176
|
+
from_version=self._twin.version,
|
|
177
|
+
to_version=self._twin.version + 1,
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
print(patch_ops)
|
|
181
|
+
|
|
182
|
+
payload = json.dumps(twin_patch.model_dump(by_alias=True), ensure_ascii=False).encode("utf-8")
|
|
183
|
+
|
|
184
|
+
self._connection.request(
|
|
185
|
+
"PATCH",
|
|
186
|
+
"/device/twin/reported",
|
|
187
|
+
body=payload,
|
|
188
|
+
headers={"Content-Type": "application/json"},
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
response = self._connection.getresponse()
|
|
192
|
+
raw_data = response.read().decode("utf-8")
|
|
193
|
+
|
|
194
|
+
if response.status != 200:
|
|
195
|
+
raise HTTPRequestError(response.status, raw_data)
|
|
196
|
+
|
|
197
|
+
twin = DeviceTwin.model_validate_json(raw_data)
|
|
198
|
+
self._last_twin = twin
|
|
199
|
+
return twin
|
|
200
|
+
|
|
201
|
+
except HTTPRequestError:
|
|
202
|
+
raise
|
|
203
|
+
except Exception as e:
|
|
204
|
+
raise SendError("Failed to patch reported properties", e)
|
bedger/edge/config.py
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from typing import Dict, Any, Annotated
|
|
5
|
+
from pydantic import BaseModel, Field, field_validator, field_serializer
|
|
6
|
+
from .severity import Severity
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class DeviceEvent(BaseModel):
|
|
10
|
+
"""
|
|
11
|
+
Represents a stored or transmitted device event.
|
|
12
|
+
This model is compatible with the local /device/events API.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
event_type: Annotated[
|
|
16
|
+
str,
|
|
17
|
+
Field(
|
|
18
|
+
description="Event type in PascalCase format",
|
|
19
|
+
pattern=r"^[A-Z][a-zA-Z0-9]*$",
|
|
20
|
+
),
|
|
21
|
+
]
|
|
22
|
+
severity: Severity
|
|
23
|
+
details: Annotated[
|
|
24
|
+
Dict[str, Any],
|
|
25
|
+
Field(description="Event-specific details, must be JSON serializable"),
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
@field_validator("details")
|
|
29
|
+
@classmethod
|
|
30
|
+
def validate_details(cls, value: Dict[str, Any]) -> Dict[str, Any]:
|
|
31
|
+
try:
|
|
32
|
+
json.dumps(value)
|
|
33
|
+
except TypeError:
|
|
34
|
+
raise ValueError("details must be a JSON serializable dictionary")
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
@field_serializer("severity")
|
|
38
|
+
def serialize_severity(self, severity: Severity) -> str:
|
|
39
|
+
return severity.value
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing import Any, Dict, List, Optional
|
|
5
|
+
from pydantic import BaseModel, Field, ConfigDict, field_serializer
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class PropertyMetaData(BaseModel):
|
|
9
|
+
"""Metadata describing when and how a property set was last updated."""
|
|
10
|
+
|
|
11
|
+
short_hash: Optional[str] = Field(
|
|
12
|
+
None,
|
|
13
|
+
alias="$short_hash",
|
|
14
|
+
description="Short 8-character hash representing the content state of the property set.",
|
|
15
|
+
)
|
|
16
|
+
last_updated: Optional[datetime] = Field(
|
|
17
|
+
None,
|
|
18
|
+
alias="$last_updated",
|
|
19
|
+
description="Timestamp when this property set was last updated.",
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
# Serialize datetimes to ISO 8601 for JSON compatibility
|
|
23
|
+
@field_serializer("last_updated")
|
|
24
|
+
def serialize_last_updated(self, value: Optional[datetime]) -> Optional[str]:
|
|
25
|
+
return value.isoformat() if value else None
|
|
26
|
+
|
|
27
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class Properties(BaseModel):
|
|
31
|
+
"""Represents either desired or reported property groups for device modules."""
|
|
32
|
+
|
|
33
|
+
modules: Optional[Dict[str, Any]] = Field(
|
|
34
|
+
default_factory=dict,
|
|
35
|
+
description="Arbitrary key/value structure for per-module properties.",
|
|
36
|
+
)
|
|
37
|
+
metadata: Optional[PropertyMetaData] = Field(
|
|
38
|
+
None,
|
|
39
|
+
alias="$metadata",
|
|
40
|
+
description="Metadata tracking changes for this property group.",
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
model_config = ConfigDict(extra="ignore", populate_by_name=True)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class DeviceTwin(BaseModel):
|
|
47
|
+
"""Data-only representation of a Device Twin document as received from the edge or cloud."""
|
|
48
|
+
|
|
49
|
+
version: Optional[int] = Field(
|
|
50
|
+
default=None,
|
|
51
|
+
description="Version number of this twin document.",
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
properties_desired: Optional[Properties] = Field(
|
|
55
|
+
None,
|
|
56
|
+
alias="properties.desired",
|
|
57
|
+
description="Desired properties defined by the cloud (target configuration).",
|
|
58
|
+
)
|
|
59
|
+
properties_reported: Optional[Properties] = Field(
|
|
60
|
+
None,
|
|
61
|
+
alias="properties.reported",
|
|
62
|
+
description="Reported properties coming from the device (actual state).",
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
metadata: Optional[PropertyMetaData] = Field(
|
|
66
|
+
None,
|
|
67
|
+
alias="$metadata",
|
|
68
|
+
description="Overall metadata for the combined twin.",
|
|
69
|
+
)
|
|
70
|
+
|
|
71
|
+
model_config = ConfigDict(
|
|
72
|
+
extra="ignore",
|
|
73
|
+
populate_by_name=True,
|
|
74
|
+
use_enum_values=True,
|
|
75
|
+
json_encoders={datetime: lambda v: v.isoformat()}, # ensure datetime → ISO string
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
def model_dump(self, *args, **kwargs):
|
|
79
|
+
"""Force by_alias=True unless explicitly overridden."""
|
|
80
|
+
kwargs.setdefault("by_alias", True)
|
|
81
|
+
return super().model_dump(*args, **kwargs)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class DeviceTwinPatch(BaseModel):
|
|
85
|
+
"""Data-only representation of a Device Twin patch (incremental update)."""
|
|
86
|
+
|
|
87
|
+
patch: List[Dict[str, Any]] = Field(
|
|
88
|
+
...,
|
|
89
|
+
description="JSON Patch operations to apply to the twin (RFC 6902).",
|
|
90
|
+
)
|
|
91
|
+
from_version: int = Field(
|
|
92
|
+
...,
|
|
93
|
+
description="Source version number before applying this patch.",
|
|
94
|
+
)
|
|
95
|
+
to_version: int = Field(
|
|
96
|
+
...,
|
|
97
|
+
description="Target version number after applying this patch.",
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
model_config = ConfigDict(
|
|
101
|
+
extra="ignore",
|
|
102
|
+
populate_by_name=True,
|
|
103
|
+
json_encoders={datetime: lambda v: v.isoformat()},
|
|
104
|
+
)
|
bedger/edge/errors.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import socket
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class BedgerError(Exception):
|
|
6
|
+
"""Base class for all Bedger-related exceptions."""
|
|
7
|
+
|
|
8
|
+
pass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# --- Configuration and Initialization ---
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ConfigurationError(BedgerError):
|
|
15
|
+
"""Raised when configuration values are missing, invalid, or inconsistent."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, message: str, path: str | None = None):
|
|
18
|
+
self.path = path
|
|
19
|
+
super().__init__(f"{message}{f' (path: {path})' if path else ''}")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class EnvironmentError(BedgerError):
|
|
23
|
+
"""Raised when the environment or system setup prevents operation (permissions, missing dirs, etc.)."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# --- Networking / Socket Communication ---
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class ConnectionError(BedgerError):
|
|
32
|
+
"""Raised when connection to a UNIX or network socket fails."""
|
|
33
|
+
|
|
34
|
+
def __init__(self, socket_path: str, original_error: Exception | None = None):
|
|
35
|
+
self.socket_path = socket_path
|
|
36
|
+
self.original_error = original_error
|
|
37
|
+
super().__init__(f"Failed to connect to socket at {socket_path}: {original_error}")
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
class SendError(BedgerError):
|
|
41
|
+
"""Raised when sending data to Bedger Edge fails."""
|
|
42
|
+
|
|
43
|
+
def __init__(self, message: str, original_error: Exception | None = None):
|
|
44
|
+
self.original_error = original_error
|
|
45
|
+
super().__init__(f"{message}{f' ({original_error})' if original_error else ''}")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ReceiveError(BedgerError):
|
|
49
|
+
"""Raised when receiving acknowledgment or response fails."""
|
|
50
|
+
|
|
51
|
+
def __init__(self, message: str, original_error: Exception | None = None):
|
|
52
|
+
self.original_error = original_error
|
|
53
|
+
super().__init__(f"{message}{f' ({original_error})' if original_error else ''}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# --- HTTP API Errors ---
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class HTTPRequestError(BedgerError):
|
|
60
|
+
"""Raised for failed HTTP requests to the local Bedger Edge API."""
|
|
61
|
+
|
|
62
|
+
def __init__(self, status_code: int, response: str):
|
|
63
|
+
self.status_code = status_code
|
|
64
|
+
self.response = response
|
|
65
|
+
super().__init__(f"Bedger API returned HTTP {status_code}: {response}")
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
class JSONSerializationError(BedgerError):
|
|
69
|
+
"""Raised when JSON encoding or decoding fails."""
|
|
70
|
+
|
|
71
|
+
pass
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
# --- Certificates / Security ---
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class CaCertificateError(BedgerError):
|
|
78
|
+
"""Raised when fetching or validating a CA certificate fails."""
|
|
79
|
+
|
|
80
|
+
pass
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
class AuthenticationError(BedgerError):
|
|
84
|
+
"""Raised when authentication or provisioning fails."""
|
|
85
|
+
|
|
86
|
+
pass
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# --- Device Twin / State Sync ---
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class DeviceTwinError(BedgerError):
|
|
93
|
+
"""Raised when device twin synchronization or persistence fails."""
|
|
94
|
+
|
|
95
|
+
pass
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DeviceEventError(BedgerError):
|
|
99
|
+
"""Raised when processing or storing device events fails."""
|
|
100
|
+
|
|
101
|
+
pass
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# --- Utilities ---
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def map_socket_error(err: Exception, socket_path: str) -> BedgerError:
|
|
108
|
+
"""Map low-level socket errors to Bedger-friendly errors."""
|
|
109
|
+
if isinstance(err, FileNotFoundError):
|
|
110
|
+
return ConnectionError(socket_path, original_error=err)
|
|
111
|
+
elif isinstance(err, PermissionError):
|
|
112
|
+
return EnvironmentError(f"Permission denied on socket {socket_path}")
|
|
113
|
+
elif isinstance(err, socket.timeout):
|
|
114
|
+
return ConnectionError(socket_path, original_error=err)
|
|
115
|
+
else:
|
|
116
|
+
return BedgerError(str(err))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Henk van den Brink
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: bedger
|
|
3
|
+
Version: 0.0.9
|
|
4
|
+
Summary:
|
|
5
|
+
Author: Henk van den Brink
|
|
6
|
+
Requires-Python: >=3.8, !=2.7.*, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*, !=3.6.*, !=3.7.*
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
9
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Requires-Dist: jsonpatch (>=1.33,<2.0)
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
|
|
14
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
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=lLNXjGCgSUVNB5kbmoaH9gow-gw94WypoQKb0ECQDPM,6716
|
|
4
|
+
bedger/edge/config.py,sha256=0fUKThP0aJFk3WBF7JUtuKKhXlV07SX5VnqVOLNou-A,152
|
|
5
|
+
bedger/edge/entities/__init__.py,sha256=BJhirUzpeOil_5cPUoSlkW0c9bXvoL9WItMfIivdF50,105
|
|
6
|
+
bedger/edge/entities/device.py,sha256=gcMHacgFE5W2LTAIDe7vGhWmrSA8lyNK8HJbiilId5U,90
|
|
7
|
+
bedger/edge/entities/device_event.py,sha256=BAgIpYve1s8f2PI2SZ_HxV5dz1Sotxhrp3ee4Dk9Fx0,1107
|
|
8
|
+
bedger/edge/entities/device_twin.py,sha256=rix8LjeEAS8vGN1McnBP3mpwcehIZ3n095soY93rv8U,3350
|
|
9
|
+
bedger/edge/entities/enums.py,sha256=Mudj_dxLOu6iCSPH583Nf-M0cNyxVgkekh-Pgq8gp4Y,105
|
|
10
|
+
bedger/edge/entities/severity.py,sha256=tUN7eDSEN5cc5eAqhvKzVHD1pSEmrdwvNEGsmh4Pnfw,157
|
|
11
|
+
bedger/edge/errors.py,sha256=Z3NCHj8s6oUZW0OvoFqr9fcLVaOMtTRDmPtnxQ4uDSQ,3205
|
|
12
|
+
bedger-0.0.9.dist-info/LICENSE,sha256=lVrf6pfElYZ_o6ETq-XR91_7GHTzKGyeNWAKghvcNUE,1075
|
|
13
|
+
bedger-0.0.9.dist-info/METADATA,sha256=rwemg9cPCXBhOXsCZN8HK7zrNm1cwk42rp5__Mp2vNQ,473
|
|
14
|
+
bedger-0.0.9.dist-info/WHEEL,sha256=sP946D7jFCHeNz5Iq4fL4Lu-PrWrFsgfLXbbkciIZwg,88
|
|
15
|
+
bedger-0.0.9.dist-info/RECORD,,
|