anson.py3 0.1.0__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.
anson/__init__.py ADDED
File without changes
File without changes
File without changes
@@ -0,0 +1,10 @@
1
+
2
+ class AnsonException:
3
+ type = "io.odysz.ansons.x.AnsonException"
4
+ excode = 0
5
+ err = ""
6
+
7
+ def __init__(self, excode: int, template: str, *param: object):
8
+ super().__init__()
9
+ self.excode = excode
10
+ self.err = template if param is None else template.format(param)
@@ -0,0 +1,231 @@
1
+ import importlib
2
+ import sys
3
+ from dataclasses import dataclass, fields, MISSING, Field
4
+
5
+ import json
6
+ from numbers import Number
7
+ from typing import TypeVar, List, Dict, get_origin, get_args, ForwardRef, Type, Any, Union, Optional
8
+
9
+ from .common import Utils
10
+
11
+ TAnson = TypeVar('TAnson', bound='Anson')
12
+
13
+ java_src_path: str = ''
14
+
15
+ @dataclass
16
+ class Anson(dict):
17
+ enclosinguardtypes = set()
18
+
19
+ __type__: str
20
+ '''ansons.antson.Anson'''
21
+
22
+ def __init__(self):
23
+ super().__init__()
24
+ t = type(self)
25
+ self.__type__ = f'{t.__module__}.{t.__name__}'
26
+
27
+ def __setitem__(self, key, value):
28
+ self.__dict__[key] = value
29
+
30
+ def __getitem__(self, key):
31
+ return self.__dict__[key]
32
+
33
+ @dataclass()
34
+ class Trumpfield():
35
+ '''
36
+ {name, type, isAnson, antype, factory}
37
+ '''
38
+ name: str
39
+ fieldtype: type
40
+ origintype: type
41
+ isAnson: bool
42
+ elemtype: type
43
+ antype: str
44
+ factory: any
45
+
46
+ @staticmethod
47
+ def fields(instance) -> dict[str, Trumpfield]:
48
+ _FIELDS = '__dataclass_fields__' # see dataclasses.fields()
49
+ fds = getattr(type(instance), _FIELDS)
50
+
51
+ def get_mandatype(t: TypeVar):
52
+ if isinstance(t, type(Optional[Any])):
53
+ return get_args(t)[0]
54
+ elif sys.version_info > (3,10,0) and isinstance(t, type(Any | None)):
55
+ print(get_args(t))
56
+ for m in get_args(t):
57
+ if m is not type(None):
58
+ return m
59
+ return t
60
+
61
+ def figureNormalType(f: Field) -> tuple:
62
+ try: isAnson = issubclass(f.type, Anson) or isinstance(f.type, Anson)
63
+ except: isAnson = False
64
+ return f, get_origin(f.type), get_args(f.type), isAnson
65
+
66
+ def figure_list(f: Field, guardTypes: set[Anson]):
67
+ if not isinstance(f.type, list):
68
+ raise Exception("Not here")
69
+
70
+ ot = list
71
+
72
+ try:
73
+ et = f.type[0].__bound__ if len(f.type[0]) > 0 and isinstance(f.type[0], TypeVar) else get_args(f.type)
74
+ et = et.__evaluate(globals(), locals(), recursive_guard=guardTypes) if isinstance(et, ForwardRef) else et
75
+ et = (et)
76
+ except: et = ()
77
+ return f, ot, et, False
78
+
79
+ def figure_dict(f: Field, envType: set[Anson]) -> tuple:
80
+ pass
81
+
82
+ def toTrump(fn: Field):
83
+ """
84
+ Simply & brutally figuring types. We only care about Anson types, the exceptional.
85
+ :param fn:
86
+ :return: TrumpField
87
+ """
88
+ f = fds[fn]
89
+ f.type = get_mandatype(f.type)
90
+
91
+ if isinstance(f.type, List):
92
+ f, ot, et, isAnson = figure_list(f, Anson.enclosinguardtypes)
93
+ elif isinstance(f.type, Dict):
94
+ f, ot, et, isAnson = figure_dict(f, Anson.enclosinguardtypes)
95
+ else:
96
+ f, ot, et, isAnson = figureNormalType(f)
97
+
98
+ return Anson.Trumpfield(
99
+ f.name, f.type,
100
+ ot, isAnson,
101
+ None if et is None or len(et) == 0 else et[0],
102
+ 'str' if f.type == str else
103
+ 'lst' if ot == list else
104
+ 'dic' if ot == dict else
105
+ 'num' if ot is None and issubclass(f.type, Number) else
106
+ f.type if isAnson else
107
+ 'obj',
108
+ None if f.default_factory is MISSING else f.default_factory)
109
+
110
+ return {it: toTrump(it) for it in fds}
111
+
112
+ @staticmethod
113
+ def toList_(lst: list, elemtype: type, ind: int):
114
+ if elemtype is None or not issubclass(elemtype, Anson): return str(lst)
115
+ return '[\n' + ','.join([Anson.toBlock_(e, ind + 1) for e in lst]) + ']'
116
+
117
+ @staticmethod
118
+ def toDict_(dic: dict, elemtype: type, ind: int):
119
+ if elemtype is None or not issubclass(elemtype, Anson): return json.dumps(dic)
120
+ return '{\n' + ',\n'.join(' ' * (ind * 2 + 2) + Anson.toBlock_(dic[k], ind + 1) for k in dic) + ']'
121
+
122
+ def toBlock(self) -> str:
123
+ return self.toBlock_(0)
124
+
125
+ def toFile(self, path: str):
126
+ with open(path, 'w+') as jf:
127
+ jf.write(self.toBlock())
128
+
129
+ def toBlock_(self, ind: int) -> str:
130
+ myfds = self.fields(self)
131
+ s = ' ' * (ind * 2) + '{\n'
132
+ # incorrect if there is a ignored: lx = len(self.__dict__) - 1
133
+ has_prvious = False
134
+ for x, k in enumerate(self.__dict__):
135
+ if '__type__' == k:
136
+ if ind == 0:
137
+ tp = str(self['__type__']).removeprefix(java_src_path+'.')
138
+ if has_prvious: s += ',\n'
139
+ s += f' "type": "{tp}"'
140
+ has_prvious = True
141
+ else: continue # later can figure out type by field's type
142
+ else:
143
+ if k not in myfds:
144
+ Utils.warn("Field {0}.{1} is not defined in Anson, which is presenting in data object. Value ignored: {1}.",
145
+ str(self['__type__']), k, self[k])
146
+ continue
147
+ if has_prvious: s += ',\n'
148
+ s += f'{" " * (ind * 2 + 2)}"{k}": '
149
+ v = self[k]
150
+ s += 'null' if v is None or isinstance(v, Field) \
151
+ else f'"{v}"' if isinstance(v, str) \
152
+ else v.toBlock_(ind + 1) if myfds[k].isAnson \
153
+ else Anson.toList_(v, myfds[k].elemtype, ind + 1) if myfds[k].antype == 'lst' \
154
+ else Anson.toDict_(v, myfds[k].elemtype, ind + 1) if myfds[k].antype == 'obj' \
155
+ else str(v)
156
+
157
+ has_prvious = True
158
+ # s += ',\n' if x != lx else '\n'
159
+ return s + ('\n' if has_prvious else '') + ' ' * (ind * 2) + '}'
160
+
161
+ @staticmethod
162
+ def from_dict(v: dict, eletype: type) -> dict:
163
+ if eletype is None: return v
164
+
165
+ d = {}
166
+ for k in v:
167
+ d[k] = Anson.from_obj(v[k], eletype)
168
+ return d
169
+
170
+ @staticmethod
171
+ def from_list(v: list, eletype: type) -> list:
172
+ if eletype is None: return v
173
+ return [Anson.from_obj(x, eletype) for x in v]
174
+
175
+ @staticmethod
176
+ def from_obj(obj: dict, typename: Union[str, type]) -> TAnson:
177
+ def getClass(_typ_: str):
178
+ parts = _typ_.split('.')
179
+ # if len(java_src_path) > 0:
180
+ # parts.insert(0, java_src_path)
181
+ module = ".".join(parts[:-1])
182
+ m = __import__(module if module is not None else '__main__')
183
+ for comp in parts[1:]:
184
+ m = getattr(m, comp)
185
+ return m
186
+
187
+ anson = getClass(typename)() if isinstance(typename, str) else typename()
188
+
189
+ fds = Anson.fields(anson)
190
+ if '__type__' not in fds:
191
+ raise Exception(f'Class {type(anson)} has no field "__type__". Is it a subclass of Anson?')
192
+
193
+ for jsonk in obj:
194
+ k = '__type__' if jsonk == 'type' else jsonk
195
+ if k != '__type__' and k not in fds:
196
+ Utils.warn(f'Field ignored: {k}: {obj[k]}')
197
+ continue
198
+
199
+ # else [Anson.from_obj(x, 'str' if thefields[k].elemtype is None else thefields[k].elemtype) for x in obj[jsonk]] if thefields[k].antype == 'obj' else \
200
+ anson[k] = Anson.from_obj(obj[jsonk], fds[k].antype) if fds[k].isAnson \
201
+ else Anson.from_dict(obj[jsonk], fds[k].elemtype) if fds[k].antype == 'obj'\
202
+ else Anson.from_list(obj[jsonk], fds[k].elemtype) if fds[k].antype == 'lst' \
203
+ else obj[jsonk]
204
+
205
+ return anson
206
+
207
+ @staticmethod
208
+ def from_json(jsonstr: str) -> TAnson:
209
+ obj = json.loads(jsonstr)
210
+ v = Anson.from_envelope(obj)
211
+ print(v, type(v))
212
+ return v
213
+
214
+ @staticmethod
215
+ def from_file(fp: str) -> TAnson:
216
+ with open(fp, 'r') as file:
217
+ obj = json.load(file)
218
+ return Anson.from_envelope(obj)
219
+
220
+ @classmethod
221
+ def java_src(cls, src_root: str = ''):
222
+ """
223
+ :param src_root: e. g. 'src'
224
+ """
225
+ global java_src_path
226
+ java_src_path = src_root
227
+
228
+ @classmethod
229
+ def from_envelope(cls, obj: dict):
230
+ return Anson.from_obj(obj,
231
+ '.'.join([java_src_path, obj['type']]) if len(java_src_path) > 0 else obj['type'])
@@ -0,0 +1,83 @@
1
+ '''
2
+ Created on 25 Oct 2019
3
+
4
+ @author: odys-z@github.com
5
+ '''
6
+ import sys
7
+ from re import match
8
+ from typing import TextIO, Optional, TypeVar
9
+
10
+ T = TypeVar('T')
11
+
12
+
13
+ class LangExt:
14
+ '''
15
+ classdocs
16
+ '''
17
+
18
+ def __init__(self, params):
19
+ '''
20
+ Constructor
21
+ '''
22
+
23
+ @staticmethod
24
+ def isblank(s, regex=None):
25
+ if (s == None):
26
+ return True
27
+ if isinstance(s, str):
28
+ if regex == None or s == "":
29
+ return len(s) == 0
30
+ else:
31
+ return match(s, regex)
32
+ return False
33
+
34
+ @staticmethod
35
+ def ifnull(a: T, b: T) -> T:
36
+ return b if a is None else a
37
+
38
+ @classmethod
39
+ def len(cls, obj):
40
+ return 0 if obj is None else len(obj)
41
+
42
+ @staticmethod
43
+ def str(obj: dict | list):
44
+ def quot(v) -> str:
45
+ return f'"{v}"' if type(v) == str else f'"{v.toBlock()}"' if isinstance(v, Anson) else LangExt.str(v)
46
+ from src.anson.io.odysz.ansons import Anson
47
+ if type(obj) == dict:
48
+ s = '{'
49
+ for k, v in obj.items():
50
+ # s += f'{"" if len(s) == 1 else ",\n"}"{k}": "{LangExt.str(v)}"'
51
+ s += f'{"" if len(s) == 1 else ",\n"}"{k}": {quot(v)}'
52
+ s += '}'
53
+ return s
54
+ elif type(obj) == list:
55
+ s = '['
56
+ # s += ", ".join(f'"{x}"' if type(x) == str else LangExt.str(x) for x in obj)
57
+ s += ", ".join(quot(x) for x in obj)
58
+ return s + ']'
59
+ elif isinstance(obj, Anson):
60
+ return obj.toBlock()
61
+ else:
62
+ return str(obj)
63
+
64
+ def log(out: Optional[TextIO], templt: str, *args):
65
+ try:
66
+ print(templt if LangExt.isblank(args) else templt.format(*args), file=out)
67
+ except Exception as e:
68
+ print(templt, args, e)
69
+
70
+
71
+ class Utils:
72
+ def __init__(self, params):
73
+ '''
74
+ Constructor
75
+ '''
76
+
77
+ @staticmethod
78
+ def logi(templt, *args):
79
+ log(sys.stdout, templt, *args)
80
+
81
+ @staticmethod
82
+ def warn(templt, *args):
83
+ log(sys.stderr, templt, *args)
@@ -0,0 +1,181 @@
1
+ Metadata-Version: 2.4
2
+ Name: anson.py3
3
+ Version: 0.1.0
4
+ Summary: Anson for Python3
5
+ Project-URL: Homepage, https://github.com/odys-z/antson
6
+ Project-URL: Issues, https://github.com/odys-z/antson/issues
7
+ Author-email: Ody Z <odys.zhou@gmail.com>
8
+ License: Copyright (c) 2025 Ody Z. All rights reserved.
9
+
10
+ This work is licensed under the terms of the MIT license.
11
+ For a copy, see <https://opensource.org/licenses/MIT>.
12
+ License-File: LICENSE
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+
18
+ # Anson.py3
19
+
20
+ A testing package ...
21
+
22
+ ```code
23
+ from ansons.anson import Anson
24
+ ```
25
+
26
+ # Install from testpypi
27
+
28
+ ```
29
+ pip install --index-url https://test.pypi.org/simple --extra-index-url https://pypi.org/simple anson.py3
30
+ ```
31
+
32
+ # Guide
33
+
34
+ - Mapping Java vs Python package structure
35
+
36
+ Python packages tree is in format of *path/to/module/class*, while java has no node of *module*:
37
+
38
+ ```
39
+ ├── io
40
+ │ └── oz
41
+ │ ├── jserv
42
+ │ │ └── docs
43
+ │ │ └── syn
44
+ │ │ └── singleton.py "class AppSettings"
45
+ │ └── syn.py "class AnRegistry, SynodeConfig, SynOrg, YellowPages"
46
+ ```
47
+
48
+ Java packages tree:
49
+
50
+ ```
51
+ .
52
+ └── io
53
+ └── oz
54
+ └── jserv
55
+ └── docs
56
+ └── syn
57
+ ├── singleton
58
+ │ └── AppSettings.java
59
+ ```
60
+
61
+ ```
62
+ .
63
+ └── io
64
+ └── oz
65
+ └── syn
66
+ ├── AnRegistry.java
67
+ ├── SynodeConfig.java
68
+ ├── SynOrg.java
69
+ └── YellowPages.java
70
+ ```
71
+
72
+ Anson.py3 is using package name path with a top level path. Say, if the json envelope
73
+ define as
74
+
75
+ ```
76
+ { type: io.oz.syn.AnRegistry,
77
+ ...
78
+ }
79
+ ```
80
+
81
+ and the user's project tree is:
82
+
83
+ ```
84
+ src
85
+ ├── io
86
+ │ └── oz
87
+ │ ├── jserv
88
+ │ │ └── docs
89
+ │ │ └── syn
90
+ │ │ └── singleton.py "class AppSettings"
91
+ │ └── syn.py "class AnRegistry, SynodeConfig, SynOrg, YellowPages"
92
+ main.py
93
+ ```
94
+
95
+ In main.py, call
96
+
97
+ ```code
98
+ Anson.java_src('src')
99
+ ```
100
+
101
+ before
102
+
103
+ ```code
104
+ AnRegistry.from_file(path)
105
+ ```
106
+
107
+ # Issues
108
+
109
+ - Printing Anson subclasses with non-default field without value initialization will result in errors
110
+
111
+ If SynOrg.parent is defined as
112
+
113
+ ```
114
+ class SynOrg(Anson)
115
+ parent: str
116
+
117
+ def __init__(self):
118
+ super().__init__()
119
+
120
+ ```
121
+
122
+ ```
123
+ org = Anson.from_file(...)
124
+ print (org)
125
+
126
+ Error
127
+ Traceback (most recent call last):
128
+ File "/home/antson/py3/test/testYellowPages.py", line 18, in testAnregistry
129
+ print(diction)
130
+ File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper
131
+ result = user_function(self)
132
+ ^^^^^^^^^^^^^^^^^^^
133
+ File "<string>", line 3, in __repr__
134
+ File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper
135
+ result = user_function(self)
136
+ ^^^^^^^^^^^^^^^^^^^
137
+ File "<string>", line 3, in __repr__
138
+ File "/usr/lib/python3.12/dataclasses.py", line 262, in wrapper
139
+ result = user_function(self)
140
+ ^^^^^^^^^^^^^^^^^^^
141
+ File "<string>", line 3, in __repr__
142
+ AttributeError: 'SynOrg' object has no attribute 'parent'
143
+ ```
144
+
145
+ # References
146
+
147
+ - https://packaging.python.org/en/latest/tutorials/packaging-projects/
148
+
149
+ ```python3 -m twine upload --repository testpypi dist/*```
150
+
151
+ # Troubleshootings
152
+
153
+ .pypirc
154
+
155
+ ```
156
+ [testpypi]
157
+ repository: https://test.pypi.org/legacy/
158
+ username = __token__
159
+ password = pypi-zzz
160
+ ```
161
+
162
+ ```
163
+ python3 -m twine --version
164
+ twine version 6.1.0 (keyring: 25.6.0, packaging: 24.2, requests: 2.31.0, requests-toolbelt: 1.0.0,
165
+ urllib3: 2.0.7, id: 1.5.0)
166
+ python3 -m build
167
+ python3 -m twine upload --repository testpypi dist/*
168
+ ```
169
+
170
+ ```
171
+ ERROR InvalidDistribution: Invalid distribution metadata: unrecognized or malformed field
172
+ 'license-file'; unrecognized or malformed field 'license-expression'
173
+ ```
174
+
175
+ Install twine 6.1.0 and packaging 24.
176
+
177
+ ```
178
+ pip install packaging -U
179
+ ```
180
+
181
+ See [issue #1216](https://github.com/pypa/twine/issues/1216#issuecomment-2609745412).
@@ -0,0 +1,10 @@
1
+ anson/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ anson/io/odysz/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ anson/io/odysz/ansons.py,sha256=Na3MF9tnAIlssI3xMcrwcugEMUyzfApLHj1iHWk5ptY,8565
4
+ anson/io/odysz/common.py,sha256=wvuAAxciEsKGRwx2Y_2mAAJnA7PgvXtjnyuA-DhdXd4,2145
5
+ anson/io/odysz/anson/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
+ anson/io/odysz/anson/x/__init__.py,sha256=UHUOTmlyuMySt7cmaupaqy4fUDAJ1cH5oYhSElFcMaI,305
7
+ anson_py3-0.1.0.dist-info/METADATA,sha256=CvaI-KkpoIrU4uO3GS92ixExTUxVx_9QiuyVyEoTw6I,4134
8
+ anson_py3-0.1.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
9
+ anson_py3-0.1.0.dist-info/licenses/LICENSE,sha256=z_ecrO9KkQ-pyddGEAsjUEOS-igXLy4GnjQE2sEpwlo,1084
10
+ anson_py3-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.27.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2018 odys-z
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.