pmd-net-addr 0.0.1__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.
@@ -0,0 +1,235 @@
1
+ ################################################################################
2
+ ## ##
3
+ ## PyTCP - Python TCP/IP stack ##
4
+ ## Copyright (C) 2020-present Sebastian Majewski ##
5
+ ## ##
6
+ ## This program is free software: you can redistribute it and/or modify ##
7
+ ## it under the terms of the GNU General Public License as published by ##
8
+ ## the Free Software Foundation, either version 3 of the License, or ##
9
+ ## (at your option) any later version. ##
10
+ ## ##
11
+ ## This program is distributed in the hope that it will be useful, ##
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
14
+ ## GNU General Public License for more details. ##
15
+ ## ##
16
+ ## You should have received a copy of the GNU General Public License ##
17
+ ## along with this program. If not, see <https://www.gnu.org/licenses/>. ##
18
+ ## ##
19
+ ## Author's email: ccie18643@gmail.com ##
20
+ ## Github repository: https://github.com/ccie18643/PyTCP ##
21
+ ## ##
22
+ ################################################################################
23
+
24
+
25
+ """
26
+ This package contains classes used to represent network addresses.
27
+
28
+ pmd_net_addr/__init__.py
29
+
30
+ ver 3.0.7
31
+ """
32
+
33
+ from typing import TYPE_CHECKING
34
+
35
+ from pmd_net_addr.errors import (
36
+ IfAddrError,
37
+ IfAddrFormatError,
38
+ IfAddrSanityError,
39
+ Ip4AddressError,
40
+ Ip4AddressFormatError,
41
+ Ip4AddressSanityError,
42
+ Ip4IfAddrError,
43
+ Ip4IfAddrFormatError,
44
+ Ip4IfAddrSanityError,
45
+ Ip4MaskError,
46
+ Ip4MaskFormatError,
47
+ Ip4NetworkError,
48
+ Ip4NetworkFormatError,
49
+ Ip4NetworkSanityError,
50
+ Ip4WildcardError,
51
+ Ip4WildcardFormatError,
52
+ Ip6AddressError,
53
+ Ip6AddressFormatError,
54
+ Ip6AddressSanityError,
55
+ Ip6IfAddrError,
56
+ Ip6IfAddrFormatError,
57
+ Ip6IfAddrSanityError,
58
+ Ip6MaskError,
59
+ Ip6MaskFormatError,
60
+ Ip6NetworkError,
61
+ Ip6NetworkFormatError,
62
+ Ip6NetworkSanityError,
63
+ Ip6WildcardError,
64
+ Ip6WildcardFormatError,
65
+ IpAddressError,
66
+ IpAddressFormatError,
67
+ IpAddressSanityError,
68
+ IpMaskError,
69
+ IpMaskFormatError,
70
+ IpNetworkError,
71
+ IpNetworkFormatError,
72
+ IpNetworkSanityError,
73
+ IpWildcardError,
74
+ IpWildcardFormatError,
75
+ MacAddressError,
76
+ MacAddressFormatError,
77
+ MacAddressSanityError,
78
+ NetAddrError,
79
+ )
80
+ from pmd_net_addr.ip4_address import IP4__ADDRESS_LEN, Ip4Address
81
+ from pmd_net_addr.ip4_ifaddr import Ip4IfAddr
82
+ from pmd_net_addr.ip4_mask import Ip4Mask
83
+ from pmd_net_addr.ip4_network import Ip4Network
84
+ from pmd_net_addr.ip4_wildcard import Ip4Wildcard
85
+ from pmd_net_addr.ip6_address import IP6__ADDRESS_LEN, Ip6Address
86
+ from pmd_net_addr.ip6_ifaddr import Ip6IfAddr
87
+ from pmd_net_addr.ip6_mask import Ip6Mask
88
+ from pmd_net_addr.ip6_network import Ip6Network
89
+ from pmd_net_addr.ip6_wildcard import Ip6Wildcard
90
+ from pmd_net_addr.ip_address import IpAddress
91
+ from pmd_net_addr.ip_ifaddr import IfAddr
92
+ from pmd_net_addr.ip_mask import IpMask
93
+ from pmd_net_addr.ip_network import IpNetwork
94
+ from pmd_net_addr.ip_version import IpVersion
95
+ from pmd_net_addr.ip_wildcard import IpWildcard
96
+ from pmd_net_addr.mac_address import MAC__ADDRESS_LEN, MacAddress
97
+
98
+ # The 'click'-typed CLI helpers are an opt-in extra: importing
99
+ # 'pmd_net_addr' (or any value type) must not drag in 'click'. The
100
+ # names below are re-exported lazily via the module '__getattr__'
101
+ # so 'from pmd_net_addr import ClickTypeIp4Address' still works but
102
+ # imports 'click' only on first access. The TYPE_CHECKING block
103
+ # gives static checkers (mypy strict, with no_implicit_reexport)
104
+ # the real bindings, and listing the names in '__all__' marks
105
+ # them as the explicit public surface.
106
+ if TYPE_CHECKING:
107
+ from pmd_net_addr.click_types import (
108
+ ClickTypeIfAddr,
109
+ ClickTypeIp4Address,
110
+ ClickTypeIp4IfAddr,
111
+ ClickTypeIp4Network,
112
+ ClickTypeIp6Address,
113
+ ClickTypeIp6IfAddr,
114
+ ClickTypeIp6Network,
115
+ ClickTypeIpAddress,
116
+ ClickTypeIpNetwork,
117
+ ClickTypeMacAddress,
118
+ )
119
+
120
+ _LAZY_CLICK_TYPES: frozenset[str] = frozenset(
121
+ {
122
+ "ClickTypeIfAddr",
123
+ "ClickTypeIp4Address",
124
+ "ClickTypeIp4IfAddr",
125
+ "ClickTypeIp4Network",
126
+ "ClickTypeIp6Address",
127
+ "ClickTypeIp6IfAddr",
128
+ "ClickTypeIp6Network",
129
+ "ClickTypeIpAddress",
130
+ "ClickTypeIpNetwork",
131
+ "ClickTypeMacAddress",
132
+ }
133
+ )
134
+
135
+
136
+ def __getattr__(name: str, /) -> object:
137
+ """
138
+ Lazily resolve the opt-in 'click'-typed CLI helpers so the
139
+ 'click' import is deferred to first access.
140
+ """
141
+
142
+ if name in _LAZY_CLICK_TYPES:
143
+ from pmd_net_addr import click_types
144
+
145
+ return getattr(click_types, name)
146
+
147
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
148
+
149
+
150
+ def __dir__() -> list[str]:
151
+ """
152
+ List the public names, including the lazily-exposed CLI
153
+ helpers.
154
+ """
155
+
156
+ return sorted(__all__)
157
+
158
+
159
+ __version__: str = "0.0.1"
160
+
161
+ __all__ = [
162
+ "ClickTypeIfAddr",
163
+ "ClickTypeIp4Address",
164
+ "ClickTypeIp4IfAddr",
165
+ "ClickTypeIp4Network",
166
+ "ClickTypeIp6Address",
167
+ "ClickTypeIp6IfAddr",
168
+ "ClickTypeIp6Network",
169
+ "ClickTypeIpAddress",
170
+ "ClickTypeIpNetwork",
171
+ "ClickTypeMacAddress",
172
+ "IP4__ADDRESS_LEN",
173
+ "IP6__ADDRESS_LEN",
174
+ "IfAddr",
175
+ "IfAddrError",
176
+ "IfAddrFormatError",
177
+ "IfAddrSanityError",
178
+ "Ip4Address",
179
+ "Ip4AddressError",
180
+ "Ip4AddressFormatError",
181
+ "Ip4AddressSanityError",
182
+ "Ip4IfAddr",
183
+ "Ip4IfAddrError",
184
+ "Ip4IfAddrFormatError",
185
+ "Ip4IfAddrSanityError",
186
+ "Ip4Mask",
187
+ "Ip4MaskError",
188
+ "Ip4MaskFormatError",
189
+ "Ip4Network",
190
+ "Ip4NetworkError",
191
+ "Ip4NetworkFormatError",
192
+ "Ip4NetworkSanityError",
193
+ "Ip4Wildcard",
194
+ "Ip4WildcardError",
195
+ "Ip4WildcardFormatError",
196
+ "Ip6Address",
197
+ "Ip6AddressError",
198
+ "Ip6AddressFormatError",
199
+ "Ip6AddressSanityError",
200
+ "Ip6IfAddr",
201
+ "Ip6IfAddrError",
202
+ "Ip6IfAddrFormatError",
203
+ "Ip6IfAddrSanityError",
204
+ "Ip6Mask",
205
+ "Ip6MaskError",
206
+ "Ip6MaskFormatError",
207
+ "Ip6Network",
208
+ "Ip6NetworkError",
209
+ "Ip6NetworkFormatError",
210
+ "Ip6NetworkSanityError",
211
+ "Ip6Wildcard",
212
+ "Ip6WildcardError",
213
+ "Ip6WildcardFormatError",
214
+ "IpAddress",
215
+ "IpAddressError",
216
+ "IpAddressFormatError",
217
+ "IpAddressSanityError",
218
+ "IpMask",
219
+ "IpMaskError",
220
+ "IpMaskFormatError",
221
+ "IpNetwork",
222
+ "IpNetworkError",
223
+ "IpNetworkFormatError",
224
+ "IpNetworkSanityError",
225
+ "IpVersion",
226
+ "IpWildcard",
227
+ "IpWildcardError",
228
+ "IpWildcardFormatError",
229
+ "MAC__ADDRESS_LEN",
230
+ "MacAddress",
231
+ "MacAddressError",
232
+ "MacAddressFormatError",
233
+ "MacAddressSanityError",
234
+ "NetAddrError",
235
+ ]
@@ -0,0 +1,293 @@
1
+ ################################################################################
2
+ ## ##
3
+ ## PyTCP - Python TCP/IP stack ##
4
+ ## Copyright (C) 2020-present Sebastian Majewski ##
5
+ ## ##
6
+ ## This program is free software: you can redistribute it and/or modify ##
7
+ ## it under the terms of the GNU General Public License as published by ##
8
+ ## the Free Software Foundation, either version 3 of the License, or ##
9
+ ## (at your option) any later version. ##
10
+ ## ##
11
+ ## This program is distributed in the hope that it will be useful, ##
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
14
+ ## GNU General Public License for more details. ##
15
+ ## ##
16
+ ## You should have received a copy of the GNU General Public License ##
17
+ ## along with this program. If not, see <https://www.gnu.org/licenses/>. ##
18
+ ## ##
19
+ ## Author's email: ccie18643@gmail.com ##
20
+ ## Github repository: https://github.com/ccie18643/PyTCP ##
21
+ ## ##
22
+ ################################################################################
23
+
24
+
25
+ """
26
+ This module contains network address base class.
27
+
28
+ pmd_net_addr/address.py
29
+
30
+ ver 3.0.7
31
+ """
32
+
33
+ from abc import ABC, abstractmethod
34
+ from typing import ClassVar, Self, override
35
+
36
+ from pmd_net_addr.base import Base
37
+ from pmd_net_addr.errors import IpAddressSanityError, NetAddrError
38
+
39
+
40
+ class Address(Base, ABC):
41
+ """
42
+ Network address support base class.
43
+ """
44
+
45
+ __slots__ = ("_address",)
46
+
47
+ _address: int
48
+
49
+ # The address-family width in bytes, bound once per concrete
50
+ # leaf (class-level constant, same pattern as '_version').
51
+ # Hot paths ('_with_offset', '__format__', 'max_prefixlen')
52
+ # read this instead of 'len(memoryview(self))', which would
53
+ # allocate a fresh bytearray + memoryview on every call.
54
+ _address_len: ClassVar[int]
55
+
56
+ # The concrete value type's free-message sanity error,
57
+ # raised for operation-precondition / invalid-argument
58
+ # failures (pmd_net_addr raises only NetAddrError subclasses).
59
+ # Every concrete subclass overrides this with its specific
60
+ # Sanity error; the default is a NetAddrError-subclass
61
+ # safety net so a subclass that omits the override still
62
+ # honours the §7.1 "no bare builtin escapes" contract
63
+ # rather than raising AttributeError.
64
+ _sanity_error: ClassVar[type[NetAddrError]] = IpAddressSanityError
65
+
66
+ @abstractmethod
67
+ def __init__(
68
+ self,
69
+ address: Self | str | bytes | bytearray | memoryview | int | None = None,
70
+ /,
71
+ ) -> None:
72
+ """
73
+ Initialize the network address object. Concrete
74
+ subclasses bind the accepted input forms.
75
+ """
76
+
77
+ raise NotImplementedError
78
+
79
+ def __int__(self) -> int:
80
+ """
81
+ Get the network address as integer.
82
+ """
83
+
84
+ return self._address
85
+
86
+ def _format_alt(self, format_spec: str, /) -> str | None:
87
+ """
88
+ Render a type-specific textual format code, or None if
89
+ the code is not recognised by this address type. The
90
+ base type recognises none.
91
+ """
92
+
93
+ return None
94
+
95
+ @override
96
+ def __format__(self, format_spec: str, /) -> str:
97
+ """
98
+ Format the address. A type-specific text code ('ex'
99
+ for the expanded IP form, 'hy' / 'ci' for MAC
100
+ notations) is rendered by '_format_alt'; 'b' / 'x' /
101
+ 'X' yield the integer zero-padded to the
102
+ address-family bit width, accepting the '#' (radix
103
+ prefix) and '_' (4-digit grouping) modifiers; 'd'
104
+ (plain decimal) and 'n' (locale-aware decimal)
105
+ delegate verbatim to the stdlib integer formatter and
106
+ take no modifiers. Any other spec — including an empty
107
+ spec or a bare fill / align / width / precision with
108
+ no presentation code — is delegated to str(self), so
109
+ the canonical text form supports the full string
110
+ mini-language without a trailing 's'.
111
+ """
112
+
113
+ if not format_spec or format_spec[-1] == "s":
114
+ return format(str(self), format_spec)
115
+
116
+ alt = self._format_alt(format_spec)
117
+ if alt is not None:
118
+ return alt
119
+
120
+ code = format_spec[-1]
121
+ flags = format_spec[:-1]
122
+
123
+ if code in {"b", "x", "X", "d", "n"}:
124
+ # 'b' / 'x' / 'X' are the address-family-width
125
+ # zero-padded radix forms ('#' radix-prefix and '_'
126
+ # 4-digit grouping modifiers apply); 'd' / 'n'
127
+ # delegate verbatim to the stdlib integer formatter
128
+ # ('d' plain decimal, 'n' locale-aware decimal) and
129
+ # take no modifiers.
130
+ if not ((code in {"b", "x", "X"} and not (set(flags) - {"#", "_"})) or (code in {"d", "n"} and not flags)):
131
+ raise type(self)._sanity_error(
132
+ f"Unknown format code {format_spec!r} for object of type {type(self).__name__!r}"
133
+ )
134
+
135
+ if code in {"d", "n"}:
136
+ return format(self._address, code)
137
+
138
+ bits = self._address_len * 8
139
+
140
+ digit_width = bits if code == "b" else bits // 4
141
+ digits = format(self._address, f"0{digit_width}{code}")
142
+
143
+ if "_" in flags:
144
+ digits = "_".join(digits[i : i + 4] for i in range(0, len(digits), 4))
145
+
146
+ if "#" in flags:
147
+ digits = ("0b" if code == "b" else "0x") + digits
148
+
149
+ return digits
150
+
151
+ # No recognised custom code: the spec is a
152
+ # string-presentation spec (bare fill / align / width /
153
+ # precision, no trailing 's' required). Delegate to
154
+ # str(self), converting the builtin ValueError that an
155
+ # unknown presentation code raises into this type's
156
+ # sanity error (pmd_net_addr.md §7.1 — no bare builtin
157
+ # escapes; chain the cause so the offending code is
158
+ # greppable in the traceback).
159
+ try:
160
+ return format(str(self), format_spec)
161
+ except ValueError as error:
162
+ raise type(self)._sanity_error(
163
+ f"Unknown format code {format_spec!r} for object of type {type(self).__name__!r}"
164
+ ) from error
165
+
166
+ @abstractmethod
167
+ def __buffer__(self, _: int) -> memoryview:
168
+ """
169
+ Get the network address as a memoryview.
170
+ """
171
+
172
+ raise NotImplementedError
173
+
174
+ @override
175
+ def __eq__(self, other: object, /) -> bool:
176
+ """
177
+ Compare the network address with another object.
178
+ """
179
+
180
+ return other is self or (isinstance(other, type(self)) and self._address == other._address)
181
+
182
+ @override
183
+ def __hash__(self) -> int:
184
+ """
185
+ Get the network address hash value.
186
+ """
187
+
188
+ return hash((type(self), self._address))
189
+
190
+ def __lt__(self, other: object, /) -> bool:
191
+ """
192
+ Order the network address by its integer value. Ordering
193
+ across address types (e.g. IPv4 vs IPv6) is undefined and
194
+ raises TypeError.
195
+ """
196
+
197
+ if not isinstance(other, type(self)):
198
+ return NotImplemented
199
+
200
+ return self._address < other._address
201
+
202
+ def __le__(self, other: object, /) -> bool:
203
+ """
204
+ Order the network address by its integer value. Ordering
205
+ across address types (e.g. IPv4 vs IPv6) is undefined and
206
+ raises TypeError.
207
+ """
208
+
209
+ if not isinstance(other, type(self)):
210
+ return NotImplemented
211
+
212
+ return self._address <= other._address
213
+
214
+ def __gt__(self, other: object, /) -> bool:
215
+ """
216
+ Order the network address by its integer value. Ordering
217
+ across address types (e.g. IPv4 vs IPv6) is undefined and
218
+ raises TypeError.
219
+ """
220
+
221
+ if not isinstance(other, type(self)):
222
+ return NotImplemented
223
+
224
+ return self._address > other._address
225
+
226
+ def __ge__(self, other: object, /) -> bool:
227
+ """
228
+ Order the network address by its integer value. Ordering
229
+ across address types (e.g. IPv4 vs IPv6) is undefined and
230
+ raises TypeError.
231
+ """
232
+
233
+ if not isinstance(other, type(self)):
234
+ return NotImplemented
235
+
236
+ return self._address >= other._address
237
+
238
+ def _with_offset(self, delta: int, /) -> Self:
239
+ """
240
+ Get this address shifted by an integer offset. An
241
+ out-of-range result is an invalid-operation outcome, not
242
+ a malformed literal, so it raises the address type's
243
+ sanity error naming the operation (pmd_net_addr.md §7.2).
244
+ """
245
+
246
+ result = self._address + delta
247
+
248
+ if not 0 <= result <= (1 << (self._address_len * 8)) - 1:
249
+ raise type(self)._sanity_error(
250
+ f"{type(self).__name__} offset out of range: " f"{self} {'+' if delta >= 0 else '-'} {abs(delta)}"
251
+ )
252
+
253
+ return type(self)(result)
254
+
255
+ def __add__(self, other: object, /) -> Self:
256
+ """
257
+ Get the network address advanced by an integer offset.
258
+ An out-of-range result raises the address-type sanity
259
+ error.
260
+ """
261
+
262
+ if not isinstance(other, int):
263
+ return NotImplemented
264
+
265
+ return self._with_offset(other)
266
+
267
+ def __sub__(self, other: object, /) -> Self:
268
+ """
269
+ Get the network address retreated by an integer offset.
270
+ An out-of-range result raises the address-type sanity
271
+ error.
272
+ """
273
+
274
+ if not isinstance(other, int):
275
+ return NotImplemented
276
+
277
+ return self._with_offset(-other)
278
+
279
+ @property
280
+ def unspecified(self) -> Self:
281
+ """
282
+ Get the unspecified network address.
283
+ """
284
+
285
+ return type(self)()
286
+
287
+ @property
288
+ def is_unspecified(self) -> bool:
289
+ """
290
+ Check if the network address is unspecified.
291
+ """
292
+
293
+ return self._address == 0
pmd_net_addr/base.py ADDED
@@ -0,0 +1,79 @@
1
+ ################################################################################
2
+ ## ##
3
+ ## PyTCP - Python TCP/IP stack ##
4
+ ## Copyright (C) 2020-present Sebastian Majewski ##
5
+ ## ##
6
+ ## This program is free software: you can redistribute it and/or modify ##
7
+ ## it under the terms of the GNU General Public License as published by ##
8
+ ## the Free Software Foundation, either version 3 of the License, or ##
9
+ ## (at your option) any later version. ##
10
+ ## ##
11
+ ## This program is distributed in the hope that it will be useful, ##
12
+ ## but WITHOUT ANY WARRANTY; without even the implied warranty of ##
13
+ ## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the ##
14
+ ## GNU General Public License for more details. ##
15
+ ## ##
16
+ ## You should have received a copy of the GNU General Public License ##
17
+ ## along with this program. If not, see <https://www.gnu.org/licenses/>. ##
18
+ ## ##
19
+ ## Author's email: ccie18643@gmail.com ##
20
+ ## Github repository: https://github.com/ccie18643/PyTCP ##
21
+ ## ##
22
+ ################################################################################
23
+
24
+
25
+ """
26
+ This module contains the base class for all NetAddr objects.
27
+
28
+ pmd_net_addr/base.py
29
+
30
+ ver 3.0.7
31
+ """
32
+
33
+ from abc import ABC, abstractmethod
34
+ from typing import override
35
+
36
+
37
+ class Base(ABC):
38
+ """
39
+ NetAddr base class.
40
+ """
41
+
42
+ __slots__ = ()
43
+
44
+ @override
45
+ @abstractmethod
46
+ def __str__(self) -> str:
47
+ """
48
+ Get the network object log string.
49
+ """
50
+
51
+ raise NotImplementedError
52
+
53
+ @override
54
+ def __repr__(self) -> str:
55
+ """
56
+ Get the network object representation string.
57
+ """
58
+
59
+ return f"{type(self).__name__}({str(self)!r})"
60
+
61
+ @override
62
+ @abstractmethod
63
+ def __eq__(self, other: object, /) -> bool:
64
+ """
65
+ Check if two network objects are equal.
66
+ """
67
+
68
+ raise NotImplementedError
69
+
70
+ @override
71
+ @abstractmethod
72
+ def __hash__(self) -> int:
73
+ """
74
+ Get the network object hash value. Concrete value types
75
+ define this consistently with their own '__eq__' (every
76
+ subclass overrides it).
77
+ """
78
+
79
+ raise NotImplementedError