lockss-pybasic 0.2.0.dev6__py3-none-any.whl → 0.2.0.dev7__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.
- lockss/pybasic/__init__.py +1 -1
- lockss/pybasic/auidutil.py +37 -22
- lockss/pybasic/cliutil.py +13 -10
- {lockss_pybasic-0.2.0.dev6.dist-info → lockss_pybasic-0.2.0.dev7.dist-info}/METADATA +1 -1
- lockss_pybasic-0.2.0.dev7.dist-info/RECORD +10 -0
- lockss_pybasic-0.2.0.dev6.dist-info/RECORD +0 -10
- {lockss_pybasic-0.2.0.dev6.dist-info → lockss_pybasic-0.2.0.dev7.dist-info}/WHEEL +0 -0
- {lockss_pybasic-0.2.0.dev6.dist-info → lockss_pybasic-0.2.0.dev7.dist-info}/licenses/LICENSE +0 -0
lockss/pybasic/__init__.py
CHANGED
lockss/pybasic/auidutil.py
CHANGED
|
@@ -36,7 +36,7 @@ and related utility classes in the LOCKSS lockss-core library.
|
|
|
36
36
|
"""
|
|
37
37
|
|
|
38
38
|
import urllib.parse
|
|
39
|
-
from typing import Dict
|
|
39
|
+
from typing import Dict
|
|
40
40
|
|
|
41
41
|
|
|
42
42
|
class InvalidAuidError(ValueError):
|
|
@@ -60,7 +60,7 @@ class AuidGenerator:
|
|
|
60
60
|
>>> params = {"base_url": "http://example.com/", "year": "2023"}
|
|
61
61
|
>>> auid = AuidGenerator.generate_auid(plugin_id, params)
|
|
62
62
|
>>> print(auid)
|
|
63
|
-
org|lockss|plugin|simulated|SimulatedPlugin&base_url~http%3A%2F%2Fexample
|
|
63
|
+
org|lockss|plugin|simulated|SimulatedPlugin&base_url~http%3A%2F%2Fexample%2Ecom%2F&year~2023
|
|
64
64
|
"""
|
|
65
65
|
|
|
66
66
|
@staticmethod
|
|
@@ -103,6 +103,18 @@ class AuidGenerator:
|
|
|
103
103
|
raise ValueError("plugin_key cannot be empty")
|
|
104
104
|
return plugin_key.replace("|", ".")
|
|
105
105
|
|
|
106
|
+
# Characters that don't need encoding - matches Java PropKeyEncoder exactly
|
|
107
|
+
# See lockss-core PropKeyEncoder.java lines 46-62
|
|
108
|
+
_DONT_NEED_ENCODING = set(
|
|
109
|
+
'abcdefghijklmnopqrstuvwxyz'
|
|
110
|
+
'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
|
|
111
|
+
'0123456789'
|
|
112
|
+
' ' # Space is converted to '+' in encode()
|
|
113
|
+
'-'
|
|
114
|
+
'_'
|
|
115
|
+
'*'
|
|
116
|
+
)
|
|
117
|
+
|
|
106
118
|
@staticmethod
|
|
107
119
|
def encode_component(s: str) -> str:
|
|
108
120
|
"""
|
|
@@ -111,9 +123,13 @@ class AuidGenerator:
|
|
|
111
123
|
Port of PropKeyEncoder.encode() from lockss-core.
|
|
112
124
|
|
|
113
125
|
This method encodes strings using URL encoding with the following rules:
|
|
114
|
-
- Alphanumeric characters
|
|
126
|
+
- Alphanumeric characters (a-z, A-Z, 0-9) are not encoded
|
|
127
|
+
- Hyphens (-), underscores (_), and asterisks (*) are not encoded
|
|
115
128
|
- Spaces are encoded as '+'
|
|
116
|
-
- All other characters are percent-encoded with uppercase hex digits
|
|
129
|
+
- All other characters (including periods) are percent-encoded with uppercase hex digits
|
|
130
|
+
|
|
131
|
+
Note: This differs from standard URL encoding (RFC 3986) which treats
|
|
132
|
+
periods (.) as unreserved. Java's PropKeyEncoder encodes periods.
|
|
117
133
|
|
|
118
134
|
Args:
|
|
119
135
|
s: String to encode
|
|
@@ -123,28 +139,25 @@ class AuidGenerator:
|
|
|
123
139
|
|
|
124
140
|
Example:
|
|
125
141
|
>>> AuidGenerator.encode_component("http://example.com/")
|
|
126
|
-
'http%3A%2F%2Fexample
|
|
142
|
+
'http%3A%2F%2Fexample%2Ecom%2F'
|
|
127
143
|
"""
|
|
128
144
|
if not s:
|
|
129
145
|
return ""
|
|
130
146
|
|
|
131
|
-
# Use quote_plus which converts spaces to '+'
|
|
132
|
-
# safe='-_.~' preserves unreserved characters per RFC 3986
|
|
133
|
-
# We use safe='' to match Java's PropKeyEncoder behavior more closely
|
|
134
|
-
encoded = urllib.parse.quote_plus(s, safe='')
|
|
135
|
-
|
|
136
|
-
# Convert to uppercase hex (urllib uses lowercase by default)
|
|
137
147
|
result = []
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
if
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
148
|
+
# Encode string to UTF-8 bytes, matching Java's OutputStreamWriter behavior
|
|
149
|
+
for char in s:
|
|
150
|
+
if char in AuidGenerator._DONT_NEED_ENCODING:
|
|
151
|
+
if char == ' ':
|
|
152
|
+
result.append('+')
|
|
153
|
+
else:
|
|
154
|
+
result.append(char)
|
|
145
155
|
else:
|
|
146
|
-
|
|
147
|
-
|
|
156
|
+
# Encode character to UTF-8 bytes and percent-encode each byte
|
|
157
|
+
char_bytes = char.encode('utf-8')
|
|
158
|
+
for byte in char_bytes:
|
|
159
|
+
result.append('%')
|
|
160
|
+
result.append(format(byte, '02X'))
|
|
148
161
|
|
|
149
162
|
return ''.join(result)
|
|
150
163
|
|
|
@@ -165,7 +178,7 @@ class AuidGenerator:
|
|
|
165
178
|
"""
|
|
166
179
|
if not s:
|
|
167
180
|
return ""
|
|
168
|
-
return urllib.parse.unquote_plus(s)
|
|
181
|
+
return urllib.parse.unquote_plus(s, errors="strict")
|
|
169
182
|
|
|
170
183
|
@staticmethod
|
|
171
184
|
def props_to_canonical_encoded_string(props: Dict[str, str]) -> str:
|
|
@@ -231,8 +244,10 @@ class AuidGenerator:
|
|
|
231
244
|
|
|
232
245
|
for pair in pairs:
|
|
233
246
|
if "~" not in pair:
|
|
234
|
-
|
|
247
|
+
raise ValueError("Missing tilde in key-value pair")
|
|
235
248
|
key_encoded, val_encoded = pair.split("~", 1)
|
|
249
|
+
if "~" in val_encoded:
|
|
250
|
+
raise ValueError("Additional tilde in key-value pair")
|
|
236
251
|
key = AuidGenerator.decode_component(key_encoded)
|
|
237
252
|
val = AuidGenerator.decode_component(val_encoded)
|
|
238
253
|
props[key] = val
|
lockss/pybasic/cliutil.py
CHANGED
|
@@ -129,22 +129,25 @@ class BaseCli(Generic[BaseModelT]):
|
|
|
129
129
|
|
|
130
130
|
def dispatch(self) -> None:
|
|
131
131
|
"""
|
|
132
|
-
Dispatches from the first field ``x_y_z`` in ``self.
|
|
132
|
+
Dispatches from the first field ``x_y_z`` in ``self._args`` that is a
|
|
133
133
|
command (i.e. whose value derives from ``BaseModel``) to a method
|
|
134
134
|
called ``_x_y_z``.
|
|
135
135
|
"""
|
|
136
|
-
|
|
136
|
+
self._dispatch_recursive(self._args, [])
|
|
137
|
+
|
|
138
|
+
def _dispatch_recursive(self, base_model: BaseModel, subcommands: list[str]) -> None:
|
|
139
|
+
field_names = base_model.__class__.__fields__.keys()
|
|
137
140
|
for field_name in field_names:
|
|
138
|
-
field_value = getattr(
|
|
141
|
+
field_value = getattr(base_model, field_name)
|
|
139
142
|
if issubclass(type(field_value), BaseModel):
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
143
|
+
self._dispatch_recursive(field_value, [*subcommands, field_name])
|
|
144
|
+
return
|
|
145
|
+
func_name = ''.join(f'_{sub}' for sub in subcommands)
|
|
146
|
+
func = getattr(self, func_name)
|
|
147
|
+
if callable(func):
|
|
148
|
+
func(base_model) # FIXME?
|
|
146
149
|
else:
|
|
147
|
-
self._parser.
|
|
150
|
+
self._parser.exit(1, f'internal error: no {func_name} callable for the {" ".join(sub for sub in subcommands)} command')
|
|
148
151
|
|
|
149
152
|
def _initialize_rich_argparse(self) -> None:
|
|
150
153
|
"""
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
lockss/pybasic/__init__.py,sha256=zBCFbOy0mzMxX4ofmKT5pq7i2s5bTNX5ECUCi02W2M4,1678
|
|
2
|
+
lockss/pybasic/auidutil.py,sha256=o5IsRLEYROXRVS6oTO1VFtdzw7SImYSR5VcqAMHY4To,13921
|
|
3
|
+
lockss/pybasic/cliutil.py,sha256=BFOijSrTByMHno3bbDmtICLQfREJpD7lVGUAuzvrHBY,10697
|
|
4
|
+
lockss/pybasic/errorutil.py,sha256=XI84PScZ851_-gfoazivJ8ceieMYWaxQr7qih5ltga0,1951
|
|
5
|
+
lockss/pybasic/fileutil.py,sha256=BpdoPWL70xYTuhyQRBEurScRVnPQg0mX-XW8yyKPGjw,2958
|
|
6
|
+
lockss/pybasic/outpututil.py,sha256=8naQEZ1rM6vOFNL-9mWoK4dMBWokHmzQ0FkHaz8dyuM,2345
|
|
7
|
+
lockss_pybasic-0.2.0.dev7.dist-info/METADATA,sha256=ReyRdrU3vbGjOcdjkz4r2nqNCHXHWLK0tsN7g_2b2_s,4269
|
|
8
|
+
lockss_pybasic-0.2.0.dev7.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
9
|
+
lockss_pybasic-0.2.0.dev7.dist-info/licenses/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
|
|
10
|
+
lockss_pybasic-0.2.0.dev7.dist-info/RECORD,,
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
lockss/pybasic/__init__.py,sha256=36qFj2gOmi2s3h_V6wwYbwaqcj9bSd41ZEes0hGtRFE,1678
|
|
2
|
-
lockss/pybasic/auidutil.py,sha256=Udqompf_F4li3pfzQPgzjfp_A2s_litsZ7uucBQDHEg,13283
|
|
3
|
-
lockss/pybasic/cliutil.py,sha256=tW6ojnvu2B_GNTmWS6ElV2xHJ3E2YGRDnQ4zQ9dU6uY,10533
|
|
4
|
-
lockss/pybasic/errorutil.py,sha256=XI84PScZ851_-gfoazivJ8ceieMYWaxQr7qih5ltga0,1951
|
|
5
|
-
lockss/pybasic/fileutil.py,sha256=BpdoPWL70xYTuhyQRBEurScRVnPQg0mX-XW8yyKPGjw,2958
|
|
6
|
-
lockss/pybasic/outpututil.py,sha256=8naQEZ1rM6vOFNL-9mWoK4dMBWokHmzQ0FkHaz8dyuM,2345
|
|
7
|
-
lockss_pybasic-0.2.0.dev6.dist-info/METADATA,sha256=1R9WSiC304TcXF7EtMxlVqMLFQAU_bvZ9SH1NjMhSmg,4269
|
|
8
|
-
lockss_pybasic-0.2.0.dev6.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
9
|
-
lockss_pybasic-0.2.0.dev6.dist-info/licenses/LICENSE,sha256=O9ONND4uDxY_jucI4jZDf2liAk05ScEJaYu-Al7EOdQ,1506
|
|
10
|
-
lockss_pybasic-0.2.0.dev6.dist-info/RECORD,,
|
|
File without changes
|
{lockss_pybasic-0.2.0.dev6.dist-info → lockss_pybasic-0.2.0.dev7.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|