lockss-pybasic 0.3.0.dev5__tar.gz → 0.3.0.dev7__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lockss-pybasic
3
- Version: 0.3.0.dev5
3
+ Version: 0.3.0.dev7
4
4
  Summary: Basic Python utilities
5
5
  License: BSD-3-Clause
6
6
  License-File: LICENSE
@@ -26,7 +26,7 @@ Description-Content-Type: text/x-rst
26
26
  lockss-pybasic
27
27
  ==============
28
28
 
29
- .. |RELEASE| replace:: 0.3.0-dev5
29
+ .. |RELEASE| replace:: 0.3.0-dev7
30
30
  .. |RELEASE_DATE| replace:: NOT YET RELEASED
31
31
 
32
32
  **Latest release:** |RELEASE| (|RELEASE_DATE|)
@@ -68,3 +68,9 @@ Release Notes
68
68
 
69
69
  See `<CHANGELOG.rst>`_.
70
70
 
71
+ ----------
72
+ Unit Tests
73
+ ----------
74
+
75
+ Run ``python -m unittest discover tests`` at the root of the project tree (where there is a directory called ``tests``).
76
+
@@ -2,7 +2,7 @@
2
2
  lockss-pybasic
3
3
  ==============
4
4
 
5
- .. |RELEASE| replace:: 0.3.0-dev5
5
+ .. |RELEASE| replace:: 0.3.0-dev7
6
6
  .. |RELEASE_DATE| replace:: NOT YET RELEASED
7
7
 
8
8
  **Latest release:** |RELEASE| (|RELEASE_DATE|)
@@ -43,3 +43,9 @@ Release Notes
43
43
  -------------
44
44
 
45
45
  See `<CHANGELOG.rst>`_.
46
+
47
+ ----------
48
+ Unit Tests
49
+ ----------
50
+
51
+ Run ``python -m unittest discover tests`` at the root of the project tree (where there is a directory called ``tests``).
@@ -28,7 +28,7 @@
28
28
 
29
29
  [project]
30
30
  name = "lockss-pybasic"
31
- version = "0.3.0-dev5" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
31
+ version = "0.3.0-dev7" # Always change in __init__.py, and at release time in README.rst and CHANGELOG.rst
32
32
  description = "Basic Python utilities"
33
33
  license = { text = "BSD-3-Clause" }
34
34
  readme = "README.rst"
@@ -36,4 +36,4 @@ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
36
36
  POSSIBILITY OF SUCH DAMAGE.
37
37
  '''.strip()
38
38
 
39
- __version__ = '0.3.0-dev5'
39
+ __version__ = '0.3.0-dev7'
@@ -35,14 +35,14 @@ LOCKSS node utilities.
35
35
  from enum import Enum
36
36
  from re import Match, Pattern
37
37
  import re
38
- from typing import Annotated, Any, ClassVar, Literal, Optional, Union
38
+ from typing import Annotated, Any, ClassVar, Literal, Optional, TypeAlias, Union
39
39
 
40
40
  from annotated_types import Ge, Le
41
41
  from pydantic import BaseModel, BeforeValidator, Field, TypeAdapter
42
42
 
43
43
 
44
44
  #: An annotated type for port numbers (0-65535)
45
- PortNumber = Annotated[int, Ge(0), Le(65535)]
45
+ PortNumber: TypeAlias = Annotated[int, Ge(0), Le(65535)]
46
46
 
47
47
 
48
48
  class NodeTypeEnum(Enum):
@@ -61,6 +61,12 @@ class NodeProtocolEnum(Enum):
61
61
  HTTPS = 'https'
62
62
 
63
63
 
64
+ NodeSpecKind: TypeAlias = Literal['NodeSpec']
65
+
66
+
67
+ NodeIdentifier: TypeAlias = str
68
+
69
+
64
70
  class BaseNodeSpec(BaseModel):
65
71
 
66
72
  DEFAULT_PROTOCOL: ClassVar[NodeProtocolEnum] = NodeProtocolEnum.HTTP
@@ -68,6 +74,13 @@ class BaseNodeSpec(BaseModel):
68
74
  TYPE_FIELD: ClassVar[dict[str, str]] = dict(title='Type',
69
75
  description="The node's type")
70
76
 
77
+ kind: NodeSpecKind = Field(title='Kind',
78
+ description="This object's kind")
79
+
80
+ id: NodeIdentifier = Field(default='',
81
+ title='Node Identifier',
82
+ description='An identifier for the node')
83
+
71
84
  protocol: NodeProtocolEnum = Field(default=DEFAULT_PROTOCOL,
72
85
  title='Protocol',
73
86
  description="The protocol for reaching the node")
@@ -86,6 +99,9 @@ class NodeSpec1(BaseNodeSpec):
86
99
  title='UI Port',
87
100
  description="The LOCKSS 1.x node's Web user interface port")
88
101
 
102
+ def __str__(self) -> str:
103
+ return f'{self.protocol.value}://{self.host}:{self.ui}'
104
+
89
105
 
90
106
  class NodeSpec2(BaseNodeSpec):
91
107
 
@@ -127,19 +143,22 @@ class NodeSpec2(BaseNodeSpec):
127
143
  title='SOAP Port',
128
144
  description="The node's SOAP Compatibility Service REST API Port")
129
145
 
146
+ def __str__(self) -> str:
147
+ return f'{self.protocol.value}://{self.host}:{self.repository}:{self.configuration}:{self.poller}:{self.crawler}:{self.metadata}:{self.soap}'
148
+
130
149
 
131
- _RE_NODE_REFERENCE: Pattern[str] = re.compile(r'((?P<protocol>https?)://)?(?P<host>[^:]+)(:(?P<repository>\d+|(?=:))(:(?P<configuration>\d+|(?=:))(:(?P<poller>\d+|(?=:))(:(?P<crawler>\d+|(?=:))(:(?P<metadata>\d+|(?=:))(:(?P<soap>\d+))?)?)?)?)?)?')
150
+ _RE_COMPACT_NODE_SPEC: Pattern[str] = re.compile(r'((?P<protocol>https?)://)?(?P<host>[^:]+)(:(?P<repository>\d+|(?=:))(:(?P<configuration>\d+|(?=:))(:(?P<poller>\d+|(?=:))(:(?P<crawler>\d+|(?=:))(:(?P<metadata>\d+|(?=:))(:(?P<soap>\d+))?)?)?)?)?)?')
132
151
 
133
152
 
134
153
  #: A type for LOCKSS node specification strings.
135
- NodeSpecStr = str
154
+ CompactNodeSpec: TypeAlias = str
136
155
 
137
156
 
138
- def _parse_node_spec_string(node_spec_string: NodeSpecStr) -> dict[str, str]:
139
- mat: Optional[Match[str]] = _RE_NODE_REFERENCE.fullmatch(node_spec_string)
157
+ def _parse_compact_node_spec(compact_node_spec: CompactNodeSpec) -> dict[str, str]:
158
+ mat: Optional[Match[str]] = _RE_COMPACT_NODE_SPEC.fullmatch(compact_node_spec)
140
159
  if mat is None:
141
- raise ValueError(f'Invalid node specification string: {node_spec_string}')
142
- d = dict(host=mat.group('host'))
160
+ raise ValueError(f'Invalid compact node specification: {compact_node_spec}')
161
+ d = dict(kind='NodeSpec', host=mat.group('host'))
143
162
  if prot := mat.group('protocol'):
144
163
  d['protocol'] = prot
145
164
  five = ('configuration', 'poller', 'crawler', 'metadata', 'soap')
@@ -147,33 +166,33 @@ def _parse_node_spec_string(node_spec_string: NodeSpecStr) -> dict[str, str]:
147
166
  if any(mat.group(x) for x in five) or len(repo_or_ui) >= 5:
148
167
  # 10000 or larger: assume V2
149
168
  d['type'] = NodeTypeEnum.V2.value
150
- d['repository'] = repo_or_ui
169
+ d['repository'] = repo_or_ui # all these strings will be coerced to int
151
170
  elif len(repo_or_ui) == 4:
152
171
  # 1000 through 9999: assume V1
153
172
  d['type'] = NodeTypeEnum.V1.value
154
173
  d['ui'] = repo_or_ui
155
174
  else:
156
- raise ValueError(f'Invalid repository/UI port in node specification string: {repo_or_ui}')
175
+ raise ValueError(f'Invalid repository/UI port in compact node specification: {repo_or_ui}')
157
176
  else:
158
177
  # Assume V2
159
178
  d['type'] = NodeTypeEnum.V2.value
160
179
  for k in five:
161
180
  if p := mat.group(k):
162
- d[k] = p # string okay, will be coerced to int
181
+ d[k] = p
163
182
  return d
164
183
 
165
184
 
166
- def _maybe_deserialize_node_spec_string(value: Any) -> Any:
167
- if isinstance(value, NodeSpecStr) and not value.startswith('{'):
168
- return _parse_node_spec_string(value)
185
+ def _maybe_deserialize_compact_node_spec(value: Any) -> Any:
186
+ if isinstance(value, CompactNodeSpec) and not value.startswith('{'):
187
+ return _parse_compact_node_spec(value)
169
188
  return value
170
189
 
171
190
 
172
191
  #: A type for LOCKSS node specifications, that also accepts a compact LOCKSS
173
- #: node specification string.
174
- NodeSpec = Annotated[
192
+ #: node specification.
193
+ NodeSpec: TypeAlias = Annotated[
175
194
  Annotated[Union[NodeSpec1, NodeSpec2], Field(discriminator='type')],
176
- BeforeValidator(_maybe_deserialize_node_spec_string)
195
+ BeforeValidator(_maybe_deserialize_compact_node_spec)
177
196
  ]
178
197
 
179
198
 
@@ -191,13 +210,10 @@ def get_node_spec_adapter() -> TypeAdapter[NodeSpec]:
191
210
  return _node_spec_adapter
192
211
 
193
212
 
194
- NodeSetKind = Literal['NodeSet']
195
-
196
-
197
- NodeSetIdentifier = str
213
+ NodeSetKind: TypeAlias = Literal['NodeSet']
198
214
 
199
215
 
200
- NodeIdentifier = str
216
+ NodeSetIdentifier: TypeAlias = str
201
217
 
202
218
 
203
219
  class NodeSet(BaseModel):
@@ -211,6 +227,6 @@ class NodeSet(BaseModel):
211
227
  name: str = Field(title='Node Set Name',
212
228
  description='A name for the node set')
213
229
 
214
- nodes: dict[NodeIdentifier, NodeSpec] = Field(min_length=1,
215
- title='Nodes',
216
- description='A non-empty list of nodes')
230
+ nodes: list[NodeSpec] = Field(min_length=1,
231
+ title='Nodes',
232
+ description='A non-empty list of nodes')