etherlyzer 0.5.2__tar.gz → 0.5.4__tar.gz
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.
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/PKG-INFO +6 -6
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/README.md +2 -2
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/pyproject.toml +7 -7
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/exporter.py +5 -5
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/formatters.py +4 -4
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/ieee/catalog.py +1 -1
- etherlyzer-0.5.2/src/etherlyzer/ieee/registry.py → etherlyzer-0.5.4/src/etherlyzer/ieee/ieee_registry.py +9 -9
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/ieee/index.py +1 -1
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/cli.py +3 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/identify.py +1 -1
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/util/dirs.py +1 -1
- etherlyzer-0.5.4/src/etherlyzer/util/knobs.py +120 -0
- etherlyzer-0.5.2/src/etherlyzer/util/settings.py +0 -94
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/LICENSE +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/__init__.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4/src/etherlyzer}/etherlyzer.env +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/ieee/__init__.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/ieee/csv_parser.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/__init__.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/__init__.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/bulk.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/format.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/info.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/normalize.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/commands/update.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/terminal/utility.py +0 -0
- {etherlyzer-0.5.2 → etherlyzer-0.5.4}/src/etherlyzer/util/__init__.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: etherlyzer
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.4
|
|
4
4
|
Summary: Fast offline lookup and classification of Ethernet-related identifiers.
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
License-File: LICENSE
|
|
@@ -18,9 +18,9 @@ Classifier: Programming Language :: Python :: 3.14
|
|
|
18
18
|
Classifier: Topic :: System :: Networking
|
|
19
19
|
Classifier: Topic :: Security
|
|
20
20
|
Classifier: Typing :: Typed
|
|
21
|
-
Requires-Dist: pandas (>=3.0.
|
|
22
|
-
Requires-Dist: platformdirs (>=4.
|
|
23
|
-
Requires-Dist: python-dotenv (>=1.2.
|
|
21
|
+
Requires-Dist: pandas (>=3.0.6,<4.0.0)
|
|
22
|
+
Requires-Dist: platformdirs (>=4.12.2,<5.0.0)
|
|
23
|
+
Requires-Dist: python-dotenv (>=1.2.4,<2.0.0)
|
|
24
24
|
Requires-Dist: requests (>=2.34.2,<3.0.0)
|
|
25
25
|
Requires-Dist: tqdm (>=4.70.1,<5.0.0)
|
|
26
26
|
Project-URL: Homepage, https://github.com/blue-hexagon/EtherLyzer
|
|
@@ -62,7 +62,7 @@ Typical use cases include:
|
|
|
62
62
|
|
|
63
63
|
Etherlyzer automatically fetches the official IEEE registry listings once every 24 hours, ensuring that the local registry data remains up to date. IEEE states that its public listings are updated every 24 hours.
|
|
64
64
|
|
|
65
|
-
The synchronization interval can be adjusted to your preference in the shipped `etherlyzer.env` file.
|
|
65
|
+
The synchronization interval can be adjusted to your preference in the shipped `src/etherlyzer/etherlyzer.env` file.
|
|
66
66
|
|
|
67
67
|
## Showcasing
|
|
68
68
|
|
|
@@ -226,7 +226,7 @@ etherlyzer format l..iIdOØ1!23-4oo.. --fix-typos --casing "upper" --block-size
|
|
|
226
226
|
|
|
227
227
|
## Configuration
|
|
228
228
|
|
|
229
|
-
The included `etherlyzer.env` file provides sensible defaults and works out of the box but is configurable.
|
|
229
|
+
The included `src/etherlyzer/etherlyzer.env` file provides sensible defaults and works out of the box but is configurable.
|
|
230
230
|
|
|
231
231
|
|
|
232
232
|
```dotenv
|
|
@@ -32,7 +32,7 @@ Typical use cases include:
|
|
|
32
32
|
|
|
33
33
|
Etherlyzer automatically fetches the official IEEE registry listings once every 24 hours, ensuring that the local registry data remains up to date. IEEE states that its public listings are updated every 24 hours.
|
|
34
34
|
|
|
35
|
-
The synchronization interval can be adjusted to your preference in the shipped `etherlyzer.env` file.
|
|
35
|
+
The synchronization interval can be adjusted to your preference in the shipped `src/etherlyzer/etherlyzer.env` file.
|
|
36
36
|
|
|
37
37
|
## Showcasing
|
|
38
38
|
|
|
@@ -196,7 +196,7 @@ etherlyzer format l..iIdOØ1!23-4oo.. --fix-typos --casing "upper" --block-size
|
|
|
196
196
|
|
|
197
197
|
## Configuration
|
|
198
198
|
|
|
199
|
-
The included `etherlyzer.env` file provides sensible defaults and works out of the box but is configurable.
|
|
199
|
+
The included `src/etherlyzer/etherlyzer.env` file provides sensible defaults and works out of the box but is configurable.
|
|
200
200
|
|
|
201
201
|
|
|
202
202
|
```dotenv
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "etherlyzer"
|
|
3
|
-
version = "0.5.
|
|
3
|
+
version = "0.5.4"
|
|
4
4
|
description = "Fast offline lookup and classification of Ethernet-related identifiers."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.14"
|
|
@@ -38,9 +38,9 @@ classifiers = [
|
|
|
38
38
|
|
|
39
39
|
dependencies = [
|
|
40
40
|
"requests (>=2.34.2,<3.0.0)",
|
|
41
|
-
"python-dotenv>=1.2.
|
|
42
|
-
"platformdirs (>=4.
|
|
43
|
-
"pandas (>=3.0.
|
|
41
|
+
"python-dotenv>=1.2.4,<2.0.0",
|
|
42
|
+
"platformdirs (>=4.12.2,<5.0.0)",
|
|
43
|
+
"pandas (>=3.0.6,<4.0.0)",
|
|
44
44
|
"tqdm (>=4.70.1,<5.0.0)",
|
|
45
45
|
]
|
|
46
46
|
|
|
@@ -58,15 +58,15 @@ packages = [
|
|
|
58
58
|
{ include = "etherlyzer", from = "src" }
|
|
59
59
|
]
|
|
60
60
|
include = [
|
|
61
|
-
{ path = "etherlyzer.env", format = ["sdist", "wheel"] }
|
|
61
|
+
{ path = "src/etherlyzer/etherlyzer.env", format = ["sdist", "wheel"] }
|
|
62
62
|
]
|
|
63
63
|
|
|
64
64
|
|
|
65
65
|
[tool.poetry.group.dev.dependencies]
|
|
66
66
|
pytest = "^9.1.1"
|
|
67
67
|
pytest-cov = "^7.1.0"
|
|
68
|
-
mypy = "^2.
|
|
69
|
-
ruff = "^0.
|
|
68
|
+
mypy = "^2.4.0"
|
|
69
|
+
ruff = "^0.16.10"
|
|
70
70
|
|
|
71
71
|
|
|
72
72
|
[tool.pytest.ini_options]
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
from etherlyzer.util.
|
|
1
|
+
from etherlyzer.util.knobs import etherlyzer_knobs
|
|
2
2
|
from etherlyzer.formatters import MACFormatter
|
|
3
3
|
|
|
4
|
-
from etherlyzer.ieee.
|
|
4
|
+
from etherlyzer.ieee.ieee_registry import IEEEEntry
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class RegistryMatchExporter:
|
|
@@ -10,9 +10,9 @@ class RegistryMatchExporter:
|
|
|
10
10
|
for entry in entries:
|
|
11
11
|
try:
|
|
12
12
|
print(
|
|
13
|
-
f"{entry.registry}{
|
|
14
|
-
f"{MACFormatter.format_default(entry.assignment)}{
|
|
15
|
-
f"{entry.organization_name}{
|
|
13
|
+
f"{entry.registry}{etherlyzer_knobs.field_separator}"
|
|
14
|
+
f"{MACFormatter.format_default(entry.assignment)}{etherlyzer_knobs.field_separator}"
|
|
15
|
+
f"{entry.organization_name}{etherlyzer_knobs.field_separator}"
|
|
16
16
|
f"{entry.organization_address}"
|
|
17
17
|
f""
|
|
18
18
|
)
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
from enum import StrEnum
|
|
2
2
|
from typing import Literal
|
|
3
3
|
|
|
4
|
-
from etherlyzer.util.
|
|
4
|
+
from etherlyzer.util.knobs import etherlyzer_knobs
|
|
5
5
|
from string import punctuation
|
|
6
6
|
|
|
7
7
|
|
|
@@ -43,11 +43,11 @@ class MACFormatter:
|
|
|
43
43
|
@classmethod
|
|
44
44
|
def format_default(cls, mac: str) -> str:
|
|
45
45
|
mac = cls.normalize(mac)
|
|
46
|
-
mac =
|
|
47
|
-
mac[i: i +
|
|
46
|
+
mac = etherlyzer_knobs.mac_separator.join(
|
|
47
|
+
mac[i: i + etherlyzer_knobs.mac_block_size] for i in range(0, len(mac), etherlyzer_knobs.mac_block_size)
|
|
48
48
|
)
|
|
49
49
|
|
|
50
|
-
return getattr(mac,
|
|
50
|
+
return getattr(mac, etherlyzer_knobs.mac_case.value)()
|
|
51
51
|
|
|
52
52
|
@classmethod
|
|
53
53
|
def format_with_stuffed_hex(cls, mac: str, stuffed_hex: str) -> str:
|
|
@@ -8,8 +8,8 @@ from requests import RequestException
|
|
|
8
8
|
from tqdm import tqdm
|
|
9
9
|
|
|
10
10
|
from etherlyzer.ieee.csv_parser import IEEERegistryReader
|
|
11
|
-
from etherlyzer.util.dirs import
|
|
12
|
-
from etherlyzer.util.
|
|
11
|
+
from etherlyzer.util.dirs import DATA_PLATFORM_DIR
|
|
12
|
+
from etherlyzer.util.knobs import etherlyzer_knobs
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class RegCategory(StrEnum):
|
|
@@ -96,7 +96,7 @@ class IEEERegistry:
|
|
|
96
96
|
|
|
97
97
|
@property
|
|
98
98
|
def filepath(self) -> Path:
|
|
99
|
-
return
|
|
99
|
+
return DATA_PLATFORM_DIR / Path("".join([self.name, ".csv"]))
|
|
100
100
|
|
|
101
101
|
@property
|
|
102
102
|
def assignment_length(self) -> int:
|
|
@@ -123,11 +123,11 @@ class IEEERegistry:
|
|
|
123
123
|
now = datetime.datetime.now(datetime.UTC)
|
|
124
124
|
f_not_exists = not self.filepath.exists()
|
|
125
125
|
|
|
126
|
-
if f_not_exists and
|
|
126
|
+
if f_not_exists and etherlyzer_knobs.show_sync_messages:
|
|
127
127
|
print(f"Cache for {self.name} not found.")
|
|
128
|
-
elif expires <= now and
|
|
128
|
+
elif expires <= now and etherlyzer_knobs.show_sync_messages:
|
|
129
129
|
print(f"Cache for {self.name} has expired.")
|
|
130
|
-
elif
|
|
130
|
+
elif etherlyzer_knobs.show_sync_messages:
|
|
131
131
|
print(f"Cache for {self.name} expires at: {expires_humanized} UTC")
|
|
132
132
|
|
|
133
133
|
return f_not_exists or expires <= now
|
|
@@ -138,7 +138,7 @@ class IEEERegistry:
|
|
|
138
138
|
|
|
139
139
|
while attempts <= max_attempts:
|
|
140
140
|
try:
|
|
141
|
-
if
|
|
141
|
+
if etherlyzer_knobs.show_sync_messages:
|
|
142
142
|
print(f"Saving Registry: {self.name}")
|
|
143
143
|
|
|
144
144
|
res = requests.get(
|
|
@@ -158,7 +158,7 @@ class IEEERegistry:
|
|
|
158
158
|
unit_scale=True,
|
|
159
159
|
unit_divisor=1024,
|
|
160
160
|
desc=f"Saving {self.name}",
|
|
161
|
-
disable=not
|
|
161
|
+
disable=not etherlyzer_knobs.show_sync_messages,
|
|
162
162
|
) as progress:
|
|
163
163
|
for chunk in res.iter_content(chunk_size=8192):
|
|
164
164
|
if chunk:
|
|
@@ -168,7 +168,7 @@ class IEEERegistry:
|
|
|
168
168
|
return None
|
|
169
169
|
|
|
170
170
|
except RequestException as exc:
|
|
171
|
-
if
|
|
171
|
+
if etherlyzer_knobs.show_sync_messages:
|
|
172
172
|
print(
|
|
173
173
|
f"Request timed out on attempt "
|
|
174
174
|
f"{attempts} of {max_attempts}."
|
|
@@ -5,7 +5,7 @@ from pathlib import Path
|
|
|
5
5
|
from time import sleep
|
|
6
6
|
from typing import TypeVar
|
|
7
7
|
|
|
8
|
-
from etherlyzer.ieee.
|
|
8
|
+
from etherlyzer.ieee.ieee_registry import RegCategory, IEEEEntry, EtherTypeEntry, IEEERegistry
|
|
9
9
|
|
|
10
10
|
HEX_DIGITS = frozenset("0123456789ABCDEF")
|
|
11
11
|
|
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
from pathlib import Path
|
|
4
5
|
|
|
5
6
|
from etherlyzer.terminal.commands import info, format, normalize, identify, update, bulk
|
|
7
|
+
from etherlyzer.util.dirs import DATA_PLATFORM_DIR
|
|
8
|
+
from etherlyzer.util.knobs import etherlyzer_knobs
|
|
6
9
|
|
|
7
10
|
|
|
8
11
|
def build_parser() -> argparse.ArgumentParser:
|
|
@@ -3,7 +3,7 @@ import re
|
|
|
3
3
|
from etherlyzer.formatters import MACFormatter
|
|
4
4
|
from etherlyzer.ieee.index import IEEEIndex
|
|
5
5
|
from etherlyzer.ieee.catalog import Catalog
|
|
6
|
-
from etherlyzer.ieee.
|
|
6
|
+
from etherlyzer.ieee.ieee_registry import RegCategory, EtherTypeEntry
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def run(args):
|
|
@@ -3,4 +3,4 @@ from pathlib import Path
|
|
|
3
3
|
from platformdirs import user_cache_dir
|
|
4
4
|
|
|
5
5
|
PROJECT_ROOT = Path(__file__).parent.parent.parent
|
|
6
|
-
|
|
6
|
+
DATA_PLATFORM_DIR = Path(user_cache_dir(appname="etherlyzer", appauthor="Manjana", version="1.0", ensure_exists=True))
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
|
|
4
|
+
from dataclasses import MISSING, dataclass, fields
|
|
5
|
+
from enum import Enum, StrEnum
|
|
6
|
+
from importlib.resources import files
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
|
|
11
|
+
from etherlyzer.util.dirs import DATA_PLATFORM_DIR
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class MACCasing(StrEnum):
|
|
15
|
+
UPPER = "upper"
|
|
16
|
+
LOWER = "lower"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass(slots=True, frozen=True)
|
|
20
|
+
class EtherlyzerKnobs:
|
|
21
|
+
db_update_interval_hours: int = 24
|
|
22
|
+
|
|
23
|
+
field_separator: str = ";"
|
|
24
|
+
mac_separator: str = "."
|
|
25
|
+
mac_block_size: int = 4
|
|
26
|
+
mac_case: MACCasing = MACCasing.UPPER
|
|
27
|
+
|
|
28
|
+
show_sync_messages: bool = False
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _load_environment() -> None:
|
|
32
|
+
DATA_PLATFORM_DIR.mkdir(parents=True, exist_ok=True)
|
|
33
|
+
|
|
34
|
+
destination_env = DATA_PLATFORM_DIR / "etherlyzer.env"
|
|
35
|
+
|
|
36
|
+
if not destination_env.exists():
|
|
37
|
+
template_env = files("etherlyzer").joinpath("etherlyzer.env")
|
|
38
|
+
|
|
39
|
+
with template_env.open("rb") as src, destination_env.open("wb") as dst:
|
|
40
|
+
shutil.copyfileobj(src, dst)
|
|
41
|
+
|
|
42
|
+
load_dotenv(destination_env)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def _validate_knobs(etherlyzer_knobs: EtherlyzerKnobs) -> None:
|
|
46
|
+
if etherlyzer_knobs.db_update_interval_hours <= 0:
|
|
47
|
+
raise ValueError(
|
|
48
|
+
"DB_UPDATE_INTERVAL_HOURS must be greater than zero."
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
if len(etherlyzer_knobs.field_separator) != 1:
|
|
52
|
+
raise ValueError(
|
|
53
|
+
"FIELD_SEPARATOR must contain exactly one character."
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
if etherlyzer_knobs.mac_separator not in {"", ":", "-", "."}:
|
|
57
|
+
raise ValueError(
|
|
58
|
+
"MAC_SEPARATOR must be one of '', ':', '-', or '.'."
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
if etherlyzer_knobs.mac_block_size not in {2, 4, 12}:
|
|
62
|
+
raise ValueError(
|
|
63
|
+
"MAC_BLOCK_SIZE must be 2, 4, or 12."
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def knobs_builder(cls: type[EtherlyzerKnobs]) -> EtherlyzerKnobs:
|
|
68
|
+
values = {}
|
|
69
|
+
|
|
70
|
+
for field in fields(cls):
|
|
71
|
+
env_name = field.name.upper()
|
|
72
|
+
value = os.getenv(env_name)
|
|
73
|
+
|
|
74
|
+
if value is None:
|
|
75
|
+
if field.default is not MISSING:
|
|
76
|
+
values[field.name] = field.default
|
|
77
|
+
continue
|
|
78
|
+
|
|
79
|
+
if field.default_factory is not MISSING:
|
|
80
|
+
values[field.name] = field.default_factory()
|
|
81
|
+
continue
|
|
82
|
+
|
|
83
|
+
raise ValueError(
|
|
84
|
+
f"Missing required environment variable {env_name}"
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
type_ = field.type
|
|
88
|
+
|
|
89
|
+
if type_ is bool:
|
|
90
|
+
values[field.name] = value.lower() in {
|
|
91
|
+
"1", "true", "yes", "on"
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
elif type_ is int:
|
|
95
|
+
values[field.name] = int(value)
|
|
96
|
+
|
|
97
|
+
elif type_ is float:
|
|
98
|
+
values[field.name] = float(value)
|
|
99
|
+
|
|
100
|
+
elif type_ is str:
|
|
101
|
+
values[field.name] = value
|
|
102
|
+
|
|
103
|
+
elif type_ is Path:
|
|
104
|
+
values[field.name] = Path(value)
|
|
105
|
+
|
|
106
|
+
elif issubclass(type_, Enum):
|
|
107
|
+
values[field.name] = type_(value)
|
|
108
|
+
|
|
109
|
+
else:
|
|
110
|
+
raise TypeError(
|
|
111
|
+
f"Unsupported config type: {type_}"
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
return cls(**values)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
_load_environment()
|
|
118
|
+
|
|
119
|
+
etherlyzer_knobs = knobs_builder(EtherlyzerKnobs)
|
|
120
|
+
_validate_knobs(etherlyzer_knobs)
|
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from dataclasses import MISSING, dataclass, fields
|
|
3
|
-
from enum import Enum, StrEnum
|
|
4
|
-
from pathlib import Path
|
|
5
|
-
|
|
6
|
-
from dotenv import load_dotenv
|
|
7
|
-
|
|
8
|
-
load_dotenv("etherlyzer.env")
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class MACCase(StrEnum):
|
|
12
|
-
UPPER = "upper"
|
|
13
|
-
LOWER = "lower"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
@dataclass(slots=True, frozen=True)
|
|
17
|
-
class Settings:
|
|
18
|
-
db_update_interval_hours: int = 24
|
|
19
|
-
|
|
20
|
-
field_separator: str = ";"
|
|
21
|
-
mac_separator: str = "."
|
|
22
|
-
mac_block_size: int = 4
|
|
23
|
-
mac_case: MACCase = MACCase.UPPER
|
|
24
|
-
|
|
25
|
-
show_sync_messages: bool = False
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def validate_config(conf: Settings) -> None:
|
|
30
|
-
if conf.db_update_interval_hours <= 0:
|
|
31
|
-
raise ValueError(
|
|
32
|
-
"DB_UPDATE_INTERVAL_HOURS must be greater than zero."
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
if len(conf.field_separator) != 1:
|
|
36
|
-
raise ValueError(
|
|
37
|
-
"FIELD_SEPARATOR must contain exactly one character."
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
if conf.mac_separator not in {"", ":", "-", "."}:
|
|
41
|
-
raise ValueError(
|
|
42
|
-
"MAC_SEPARATOR must be one of '', ':', '-', or '.'."
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
if conf.mac_block_size not in {2, 4, 12}:
|
|
46
|
-
raise ValueError(
|
|
47
|
-
"MAC_BLOCK_SIZE must be 2, 4, or 12."
|
|
48
|
-
)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
def load_config(cls: type[Settings]) -> Settings:
|
|
52
|
-
values = {}
|
|
53
|
-
|
|
54
|
-
for field in fields(cls):
|
|
55
|
-
env_name = field.name.upper()
|
|
56
|
-
|
|
57
|
-
value = os.getenv(env_name)
|
|
58
|
-
|
|
59
|
-
if value is None:
|
|
60
|
-
if field.default is not MISSING:
|
|
61
|
-
values[field.name] = field.default
|
|
62
|
-
elif field.default_factory is not MISSING:
|
|
63
|
-
values[field.name] = field.default_factory()
|
|
64
|
-
else:
|
|
65
|
-
raise ValueError(
|
|
66
|
-
f"Missing required environment variable {env_name}"
|
|
67
|
-
)
|
|
68
|
-
|
|
69
|
-
type_ = field.type
|
|
70
|
-
|
|
71
|
-
if type_ is bool:
|
|
72
|
-
values[field.name] = value.lower() in {"1", "true", "yes", "on"}
|
|
73
|
-
|
|
74
|
-
elif type_ is int:
|
|
75
|
-
values[field.name] = int(value)
|
|
76
|
-
|
|
77
|
-
elif type_ is float:
|
|
78
|
-
values[field.name] = float(value)
|
|
79
|
-
|
|
80
|
-
elif type_ is str:
|
|
81
|
-
values[field.name] = value
|
|
82
|
-
elif type_ is Path:
|
|
83
|
-
values[field.name] = Path(value)
|
|
84
|
-
|
|
85
|
-
elif issubclass(type_, Enum):
|
|
86
|
-
values[field.name] = type_(value)
|
|
87
|
-
|
|
88
|
-
else:
|
|
89
|
-
raise TypeError(f"Unsupported config type: {type_}")
|
|
90
|
-
return cls(**values)
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
config = load_config(Settings)
|
|
94
|
-
validate_config(config)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|