oe-python-template 0.16.3__py3-none-any.whl → 0.16.5__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.
@@ -6,9 +6,12 @@ import platform
6
6
  import pwd
7
7
  import sys
8
8
  import time
9
- from typing import Any
9
+ from socket import AF_INET, SOCK_DGRAM, socket
10
+ from typing import Any, NotRequired, TypedDict, cast
11
+ from urllib.error import HTTPError
10
12
 
11
13
  from pydantic_settings import BaseSettings
14
+ from requests import get
12
15
  from uptime import boottime, uptime
13
16
 
14
17
  from ..utils import ( # noqa: TID252
@@ -27,7 +30,28 @@ from ..utils import ( # noqa: TID252
27
30
  )
28
31
  from ._settings import Settings
29
32
 
30
- logger = get_logger(__name__)
33
+ log = get_logger(__name__)
34
+
35
+
36
+ class RuntimeDict(TypedDict, total=False):
37
+ """Type for runtime information dictionary."""
38
+
39
+ environment: str
40
+ username: str
41
+ process: dict[str, Any]
42
+ host: dict[str, Any]
43
+ python: dict[str, Any]
44
+ environ: dict[str, str]
45
+
46
+
47
+ class InfoDict(TypedDict, total=False):
48
+ """Type for the info dictionary."""
49
+
50
+ package: dict[str, Any]
51
+ runtime: RuntimeDict
52
+ settings: dict[str, Any]
53
+ # Allow additional string keys with any values for service info
54
+ __extra__: NotRequired[dict[str, Any]]
31
55
 
32
56
 
33
57
  class Service(BaseService):
@@ -74,12 +98,47 @@ class Service(BaseService):
74
98
  Returns:
75
99
  bool: True if the token is valid, False otherwise.
76
100
  """
77
- logger.info(token)
101
+ log.info(token)
78
102
  if not self._settings.token:
79
- logger.warning("Token is not set in settings.")
103
+ log.warning("Token is not set in settings.")
80
104
  return False
81
105
  return token == self._settings.token.get_secret_value()
82
106
 
107
+ @staticmethod
108
+ def _get_public_ipv4(timeout: int = 5) -> str | None:
109
+ """Get the public IPv4 address of the system.
110
+
111
+ Args:
112
+ timeout (int): Timeout for the request in seconds.
113
+
114
+ Returns:
115
+ str: The public IPv4 address.
116
+ """
117
+ try:
118
+ response = get(url="https://api.ipify.org", timeout=timeout)
119
+ response.raise_for_status()
120
+ return response.text
121
+ except HTTPError as e:
122
+ message = f"Failed to get public IP: {e}"
123
+ log.exception(message)
124
+ return None
125
+
126
+ @staticmethod
127
+ def _get_local_ipv4() -> str | None:
128
+ """Get the local IPv4 address of the system.
129
+
130
+ Returns:
131
+ str: The local IPv4 address.
132
+ """
133
+ try:
134
+ with socket(AF_INET, SOCK_DGRAM) as connection:
135
+ connection.connect(("8.8.8.8", 80))
136
+ return str(connection.getsockname()[0])
137
+ except Exception as e:
138
+ message = f"Failed to get local IP: {e}"
139
+ log.exception(message)
140
+ return None
141
+
83
142
  @staticmethod
84
143
  def info(include_environ: bool = False, filter_secrets: bool = True) -> dict[str, Any]:
85
144
  """
@@ -95,7 +154,7 @@ class Service(BaseService):
95
154
  dict[str, Any]: Service configuration.
96
155
  """
97
156
  bootdatetime = boottime()
98
- rtn = {
157
+ rtn: InfoDict = {
99
158
  "package": {
100
159
  "version": __version__,
101
160
  "name": __project_name__,
@@ -104,35 +163,49 @@ class Service(BaseService):
104
163
  },
105
164
  "runtime": {
106
165
  "environment": __env__,
166
+ "username": pwd.getpwuid(os.getuid())[0],
167
+ "process": {
168
+ "command_line": " ".join(sys.argv),
169
+ "entry_point": sys.argv[0] if sys.argv else None,
170
+ "process_info": json.loads(get_process_info().model_dump_json()),
171
+ },
172
+ "host": {
173
+ "os": {
174
+ "platform": platform.platform(),
175
+ "system": platform.system(),
176
+ "release": platform.release(),
177
+ "version": platform.version(),
178
+ },
179
+ "machine": {
180
+ "arch": platform.machine(),
181
+ "processor": platform.processor(),
182
+ "cpu_count": os.cpu_count(),
183
+ },
184
+ "network": {
185
+ "hostname": platform.node(),
186
+ "local_ipv4": Service._get_local_ipv4(),
187
+ "public_ipv4": Service._get_public_ipv4(),
188
+ },
189
+ "uptime": {
190
+ "seconds": uptime(),
191
+ "boottime": bootdatetime.isoformat() if bootdatetime else None,
192
+ },
193
+ },
107
194
  "python": {
108
195
  "version": platform.python_version(),
109
196
  "compiler": platform.python_compiler(),
110
197
  "implementation": platform.python_implementation(),
111
198
  "sys.path": sys.path,
112
- },
113
- "interpreter_path": sys.executable,
114
- "command_line": " ".join(sys.argv),
115
- "entry_point": sys.argv[0] if sys.argv else None,
116
- "process_info": json.loads(get_process_info().model_dump_json()),
117
- "username": pwd.getpwuid(os.getuid())[0],
118
- "host": {
119
- "system": platform.system(),
120
- "release": platform.release(),
121
- "version": platform.version(),
122
- "machine": platform.machine(),
123
- "processor": platform.processor(),
124
- "hostname": platform.node(),
125
- "ip_address": platform.uname().node,
126
- "cpu_count": os.cpu_count(),
127
- "uptime": uptime(),
128
- "boottime": bootdatetime.isoformat() if bootdatetime else None,
199
+ "interpreter_path": sys.executable,
129
200
  },
130
201
  },
202
+ "settings": {},
131
203
  }
132
204
 
205
+ runtime = cast("RuntimeDict", rtn["runtime"])
133
206
  if include_environ:
134
207
  if filter_secrets:
135
- rtn["runtime"]["environ"] = {
208
+ runtime["environ"] = {
136
209
  k: v
137
210
  for k, v in os.environ.items()
138
211
  if not (
@@ -144,9 +217,9 @@ class Service(BaseService):
144
217
  )
145
218
  }
146
219
  else:
147
- rtn["runtime"]["environ"] = dict(os.environ)
220
+ runtime["environ"] = dict(os.environ)
148
221
 
149
- settings = {}
222
+ settings: dict[str, Any] = {}
150
223
  for settings_class in locate_subclasses(BaseSettings):
151
224
  settings_instance = load_settings(settings_class)
152
225
  env_prefix = settings_instance.model_config.get("env_prefix", "")
@@ -158,12 +231,16 @@ class Service(BaseService):
158
231
  settings[flat_key] = value
159
232
  rtn["settings"] = settings
160
233
 
234
+ # Convert the TypedDict to a regular dict before adding dynamic service keys
235
+ result_dict: dict[str, Any] = dict(rtn)
236
+
161
237
  for service_class in locate_subclasses(BaseService):
162
238
  if service_class is not Service:
163
239
  service = service_class()
164
- rtn[service.key()] = service.info()
240
+ result_dict[service.key()] = service.info()
165
241
 
166
- return rtn
242
+ log.info("Service info: %s", result_dict)
243
+ return result_dict
167
244
 
168
245
  @staticmethod
169
246
  def div_by_zero() -> float:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oe-python-template
3
- Version: 0.16.3
3
+ Version: 0.16.5
4
4
  Summary: 🧠 Copier template to scaffold Python projects compliant with best practices and modern tooling.
5
5
  Project-URL: Homepage, https://oe-python-template.readthedocs.io/en/latest/
6
6
  Project-URL: Documentation, https://oe-python-template.readthedocs.io/en/latest/
@@ -14,7 +14,7 @@ oe_python_template/system/__init__.py,sha256=7e2z8HATzy3dAIBXy5PM9rlCC7Rbu8m8Nap
14
14
  oe_python_template/system/_api.py,sha256=pBBCL1gXM7W5dgJqBLKuNFWES0pQF8mbUgQ7H7oHwh8,3714
15
15
  oe_python_template/system/_cli.py,sha256=fVO_TSIyiaMJ2ACeQ4gPy4QQZjmeBVnsxxsViuNGe8I,6990
16
16
  oe_python_template/system/_gui.py,sha256=9U4QMkl1mVJargg_gQ7U8tMlvec4b1cqkTgRcmmyTTc,678
17
- oe_python_template/system/_service.py,sha256=f-EtWEAanajo4S2KeGm26K9_RezYgPza1qVqPRBOFvY,6376
17
+ oe_python_template/system/_service.py,sha256=2Vkbn28ipWRMKRt4T9O_CR_uk9pX1mM4jb21DMOvpus,8858
18
18
  oe_python_template/system/_settings.py,sha256=MwMAJYifJ6jGImeSh4e9shmIXmiUSuQGHXz_Ts0mSdk,901
19
19
  oe_python_template/utils/__init__.py,sha256=oM8v1ozMOeBQG-EbBtEGbnuGk52mYNDAmNoR7vNQ7MY,2050
20
20
  oe_python_template/utils/_api.py,sha256=GSI-HPtSdTFv0qP5aTDPa2WS3ewVa1slusG6dvxSu0Y,2474
@@ -33,8 +33,8 @@ oe_python_template/utils/_service.py,sha256=atHAejvBucKXjzhsMSdOBBFa7rRD74zcV70P
33
33
  oe_python_template/utils/_settings.py,sha256=owFoaHEzJnVD3EVyOWF4rfIY7g6eLnU6rN0m4VHhCbA,2464
34
34
  oe_python_template/utils/boot.py,sha256=Qgq1sjT8d3fcfibnMyx5CVUJ_KKXgFI3ozZUIJbhh4I,2921
35
35
  oe_python_template/utils/.vendored/bottle.py,sha256=kZAZmh3nRzCUf-9IKGpv0yqlMciZMA_DNaaMDdcQmt0,175437
36
- oe_python_template-0.16.3.dist-info/METADATA,sha256=nfLB-yYgO1iTl-qPqsvxFBziS-u_jjIZHVryBrAlkvs,34606
37
- oe_python_template-0.16.3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
- oe_python_template-0.16.3.dist-info/entry_points.txt,sha256=IroSSWhLGxus9rxcashkYQda39TTvf7LbUMYtOKXUBE,66
39
- oe_python_template-0.16.3.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
40
- oe_python_template-0.16.3.dist-info/RECORD,,
36
+ oe_python_template-0.16.5.dist-info/METADATA,sha256=Xf16hpl90E6xVhp0p72rrJ2sfE5d8XGwWTftJryPks0,34606
37
+ oe_python_template-0.16.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
+ oe_python_template-0.16.5.dist-info/entry_points.txt,sha256=IroSSWhLGxus9rxcashkYQda39TTvf7LbUMYtOKXUBE,66
39
+ oe_python_template-0.16.5.dist-info/licenses/LICENSE,sha256=5H409K6xzz9U5eUaoAHQExNkoWJRlU0LEj6wL2QJ34s,1113
40
+ oe_python_template-0.16.5.dist-info/RECORD,,