sera-2 1.6.0__py3-none-any.whl → 1.6.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.
- sera/make/__main__.py +2 -1
- sera/make/make_app.py +10 -2
- sera/make/make_typescript_model.py +87 -11
- sera/misc/__init__.py +4 -0
- sera/misc/_formatter.py +61 -0
- sera/models/__init__.py +3 -2
- sera/models/_enum.py +5 -1
- sera/models/_module.py +5 -14
- sera/typing.py +6 -0
- {sera_2-1.6.0.dist-info → sera_2-1.6.1.dist-info}/METADATA +2 -2
- {sera_2-1.6.0.dist-info → sera_2-1.6.1.dist-info}/RECORD +12 -11
- {sera_2-1.6.0.dist-info → sera_2-1.6.1.dist-info}/WHEEL +0 -0
sera/make/__main__.py
CHANGED
@@ -4,8 +4,9 @@ from pathlib import Path
|
|
4
4
|
from typing import Annotated
|
5
5
|
|
6
6
|
import typer
|
7
|
+
|
7
8
|
from sera.make.make_app import make_app
|
8
|
-
from sera.
|
9
|
+
from sera.typing import Language
|
9
10
|
|
10
11
|
app = typer.Typer(pretty_exceptions_short=True, pretty_exceptions_enable=False)
|
11
12
|
|
sera/make/make_app.py
CHANGED
@@ -7,6 +7,7 @@ from typing import Annotated
|
|
7
7
|
|
8
8
|
from codegen.models import DeferredVar, PredefinedFn, Program, expr, stmt
|
9
9
|
from loguru import logger
|
10
|
+
|
10
11
|
from sera.make.make_python_api import make_python_api
|
11
12
|
from sera.make.make_python_model import (
|
12
13
|
make_python_data_model,
|
@@ -14,8 +15,13 @@ from sera.make.make_python_model import (
|
|
14
15
|
make_python_relational_model,
|
15
16
|
)
|
16
17
|
from sera.make.make_python_services import make_python_service_structure
|
17
|
-
from sera.make.make_typescript_model import
|
18
|
-
|
18
|
+
from sera.make.make_typescript_model import (
|
19
|
+
make_typescript_data_model,
|
20
|
+
make_typescript_enum,
|
21
|
+
)
|
22
|
+
from sera.misc import Formatter
|
23
|
+
from sera.models import App, DataCollection, parse_schema
|
24
|
+
from sera.typing import Language
|
19
25
|
|
20
26
|
|
21
27
|
def make_config(app: App):
|
@@ -164,8 +170,10 @@ def make_app(
|
|
164
170
|
# generate services
|
165
171
|
make_python_service_structure(app, collections)
|
166
172
|
elif language == Language.Typescript:
|
173
|
+
make_typescript_enum(schema, app.models)
|
167
174
|
make_typescript_data_model(schema, app.models)
|
168
175
|
|
176
|
+
Formatter.get_instance().process()
|
169
177
|
return app
|
170
178
|
|
171
179
|
|
@@ -5,6 +5,7 @@ from typing import Any, Callable
|
|
5
5
|
from codegen.models import AST, PredefinedFn, Program, expr, stmt
|
6
6
|
from codegen.models.var import DeferredVar
|
7
7
|
from loguru import logger
|
8
|
+
|
8
9
|
from sera.misc import (
|
9
10
|
assert_isinstance,
|
10
11
|
assert_not_null,
|
@@ -15,6 +16,7 @@ from sera.misc import (
|
|
15
16
|
from sera.models import (
|
16
17
|
Class,
|
17
18
|
DataProperty,
|
19
|
+
Enum,
|
18
20
|
ObjectProperty,
|
19
21
|
Package,
|
20
22
|
Schema,
|
@@ -26,6 +28,15 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
26
28
|
"""Generate TypeScript data model from the schema. The data model aligns with the public data model in Python, not the database model."""
|
27
29
|
app = target_pkg.app
|
28
30
|
|
31
|
+
# mapping from type alias of idprop to its real type
|
32
|
+
idprop_aliases = {}
|
33
|
+
for cls in schema.classes.values():
|
34
|
+
idprop = cls.get_id_property()
|
35
|
+
if idprop is not None:
|
36
|
+
idprop_aliases[f"{cls.name}Id"] = (
|
37
|
+
idprop.get_data_model_datatype().get_typescript_type()
|
38
|
+
)
|
39
|
+
|
29
40
|
def clone_prop(prop: DataProperty | ObjectProperty, value: expr.Expr):
|
30
41
|
# detect all complex types is hard, we can assume that any update to this does not mutate
|
31
42
|
# the original object, then it's okay.
|
@@ -38,7 +49,6 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
38
49
|
return
|
39
50
|
|
40
51
|
idprop = cls.get_id_property()
|
41
|
-
|
42
52
|
program = Program()
|
43
53
|
|
44
54
|
prop_defs = []
|
@@ -161,7 +171,8 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
161
171
|
else None
|
162
172
|
),
|
163
173
|
stmt.LineBreak(),
|
164
|
-
lambda ast00: ast00.
|
174
|
+
lambda ast00: ast00.class_like(
|
175
|
+
"interface",
|
165
176
|
cls.name + "ConstructorArgs",
|
166
177
|
)(*prop_defs),
|
167
178
|
stmt.LineBreak(),
|
@@ -230,9 +241,9 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
230
241
|
update_field_funcs: list[Callable[[AST], Any]] = []
|
231
242
|
|
232
243
|
for prop in cls.properties.values():
|
233
|
-
if prop.data.is_private:
|
234
|
-
|
235
|
-
|
244
|
+
# if prop.data.is_private:
|
245
|
+
# # skip private fields as this is for APIs exchange
|
246
|
+
# continue
|
236
247
|
|
237
248
|
propname = to_camel_case(prop.name)
|
238
249
|
|
@@ -291,15 +302,23 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
291
302
|
):
|
292
303
|
create_propvalue = expr.ExprConstant(-1)
|
293
304
|
else:
|
294
|
-
|
305
|
+
if tstype.type in idprop_aliases:
|
306
|
+
create_propvalue = idprop_aliases[tstype.type].get_default()
|
307
|
+
else:
|
308
|
+
create_propvalue = tstype.get_default()
|
295
309
|
|
296
310
|
if prop.db is not None and prop.db.is_primary_key:
|
297
311
|
# for checking if the primary key is from the database or default (create_propvalue)
|
298
312
|
cls_pk = (expr.ExprIdent(propname), create_propvalue)
|
299
313
|
|
300
|
-
|
301
|
-
|
302
|
-
|
314
|
+
# if this field is private, we cannot get it from the normal record
|
315
|
+
# we have to create a default value for it.
|
316
|
+
if prop.data.is_private:
|
317
|
+
update_propvalue = create_propvalue
|
318
|
+
else:
|
319
|
+
update_propvalue = PredefinedFn.attr_getter(
|
320
|
+
expr.ExprIdent("record"), expr.ExprIdent(propname)
|
321
|
+
)
|
303
322
|
|
304
323
|
ser_args.append(
|
305
324
|
(
|
@@ -333,7 +352,22 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
333
352
|
)
|
334
353
|
if prop.cardinality.is_star_to_many():
|
335
354
|
tstype = tstype.as_list_type()
|
336
|
-
|
355
|
+
create_propvalue = expr.ExprConstant([])
|
356
|
+
else:
|
357
|
+
# if target class has an auto-increment primary key, we set a different default value
|
358
|
+
# to be -1 to avoid start from 0
|
359
|
+
target_idprop = prop.target.get_id_property()
|
360
|
+
if (
|
361
|
+
target_idprop is not None
|
362
|
+
and target_idprop.db is not None
|
363
|
+
and target_idprop.db.is_primary_key
|
364
|
+
and target_idprop.db.is_auto_increment
|
365
|
+
):
|
366
|
+
create_propvalue = expr.ExprConstant(-1)
|
367
|
+
else:
|
368
|
+
assert tstype.type in idprop_aliases
|
369
|
+
create_propvalue = idprop_aliases[tstype.type].get_default()
|
370
|
+
|
337
371
|
update_propvalue = PredefinedFn.attr_getter(
|
338
372
|
expr.ExprIdent("record"), expr.ExprIdent(propname)
|
339
373
|
)
|
@@ -474,7 +508,8 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
474
508
|
observable_args.sort(key=lambda x: {"observable": 0, "action": 1}[x[1].ident])
|
475
509
|
|
476
510
|
program.root(
|
477
|
-
lambda ast00: ast00.
|
511
|
+
lambda ast00: ast00.class_like(
|
512
|
+
"interface",
|
478
513
|
draft_clsname + "ConstructorArgs",
|
479
514
|
)(*prop_defs),
|
480
515
|
stmt.LineBreak(),
|
@@ -861,3 +896,44 @@ def make_typescript_data_model(schema: Schema, target_pkg: Package):
|
|
861
896
|
make_definition(cls, pkg)
|
862
897
|
|
863
898
|
make_index(pkg)
|
899
|
+
|
900
|
+
|
901
|
+
def make_typescript_enum(schema: Schema, target_pkg: Package):
|
902
|
+
"""Make typescript enum for the schema"""
|
903
|
+
enum_pkg = target_pkg.pkg("enums")
|
904
|
+
|
905
|
+
def make_enum(enum: Enum, pkg: Package):
|
906
|
+
program = Program()
|
907
|
+
program.root(
|
908
|
+
stmt.LineBreak(),
|
909
|
+
lambda ast: ast.class_like("enum", enum.name)(
|
910
|
+
*[
|
911
|
+
stmt.DefClassVarStatement(
|
912
|
+
name=value.name,
|
913
|
+
type=None,
|
914
|
+
value=expr.ExprConstant(value.value),
|
915
|
+
)
|
916
|
+
for value in enum.values.values()
|
917
|
+
]
|
918
|
+
),
|
919
|
+
stmt.LineBreak(),
|
920
|
+
stmt.TypescriptStatement("export " + enum.name + ";"),
|
921
|
+
)
|
922
|
+
pkg.module(enum.get_tsmodule_name()).write(program)
|
923
|
+
|
924
|
+
for enum in schema.enums.values():
|
925
|
+
make_enum(enum, enum_pkg)
|
926
|
+
|
927
|
+
program = Program()
|
928
|
+
for enum in schema.enums.values():
|
929
|
+
program.import_(f"@.models.enums.{enum.get_tsmodule_name()}.{enum.name}", True)
|
930
|
+
|
931
|
+
program.root(
|
932
|
+
stmt.LineBreak(),
|
933
|
+
stmt.TypescriptStatement(
|
934
|
+
"export { "
|
935
|
+
+ ", ".join([enum.name for enum in schema.enums.values()])
|
936
|
+
+ "};"
|
937
|
+
),
|
938
|
+
)
|
939
|
+
enum_pkg.module("index").write(program)
|
sera/misc/__init__.py
CHANGED
@@ -1,3 +1,4 @@
|
|
1
|
+
from sera.misc._formatter import File, Formatter
|
1
2
|
from sera.misc._utils import (
|
2
3
|
assert_isinstance,
|
3
4
|
assert_not_null,
|
@@ -14,4 +15,7 @@ __all__ = [
|
|
14
15
|
"assert_not_null",
|
15
16
|
"to_snake_case",
|
16
17
|
"to_camel_case",
|
18
|
+
"to_pascal_case",
|
19
|
+
"Formatter",
|
20
|
+
"File",
|
17
21
|
]
|
sera/misc/_formatter.py
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
from __future__ import annotations
|
2
|
+
|
3
|
+
import subprocess
|
4
|
+
from concurrent.futures import ThreadPoolExecutor
|
5
|
+
from dataclasses import dataclass
|
6
|
+
from pathlib import Path
|
7
|
+
|
8
|
+
from tqdm import tqdm
|
9
|
+
|
10
|
+
from sera.typing import Language
|
11
|
+
|
12
|
+
|
13
|
+
@dataclass
|
14
|
+
class File:
|
15
|
+
path: Path
|
16
|
+
language: Language
|
17
|
+
|
18
|
+
|
19
|
+
class Formatter:
|
20
|
+
instance = None
|
21
|
+
|
22
|
+
def __init__(self):
|
23
|
+
self.pending_files: list[File] = []
|
24
|
+
|
25
|
+
@staticmethod
|
26
|
+
def get_instance():
|
27
|
+
if Formatter.instance is None:
|
28
|
+
Formatter.instance = Formatter()
|
29
|
+
return Formatter.instance
|
30
|
+
|
31
|
+
def register(self, file: File):
|
32
|
+
self.pending_files.append(file)
|
33
|
+
|
34
|
+
def process(self):
|
35
|
+
"""Format pending files in parallel"""
|
36
|
+
|
37
|
+
def format_file(file: File):
|
38
|
+
if file.language == Language.Typescript:
|
39
|
+
try:
|
40
|
+
subprocess.check_output(
|
41
|
+
["npx", "prettier", "--write", str(file.path.absolute())],
|
42
|
+
cwd=file.path.parent,
|
43
|
+
)
|
44
|
+
except subprocess.CalledProcessError as e:
|
45
|
+
print(f"Error formatting {file.path}: {e}")
|
46
|
+
raise
|
47
|
+
else:
|
48
|
+
raise NotImplementedError(
|
49
|
+
f"Formatting not implemented for {file.language}"
|
50
|
+
)
|
51
|
+
|
52
|
+
with ThreadPoolExecutor() as executor:
|
53
|
+
list(
|
54
|
+
tqdm(
|
55
|
+
executor.map(format_file, self.pending_files),
|
56
|
+
total=len(self.pending_files),
|
57
|
+
desc="Formatting files",
|
58
|
+
)
|
59
|
+
)
|
60
|
+
|
61
|
+
self.pending_files.clear()
|
sera/models/__init__.py
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
from sera.models._class import Class
|
2
2
|
from sera.models._collection import DataCollection
|
3
3
|
from sera.models._datatype import DataType, PyTypeWithDep, TsTypeWithDep
|
4
|
-
from sera.models.
|
4
|
+
from sera.models._enum import Enum
|
5
|
+
from sera.models._module import App, Module, Package
|
5
6
|
from sera.models._multi_lingual_string import MultiLingualString
|
6
7
|
from sera.models._parse import parse_schema
|
7
8
|
from sera.models._property import Cardinality, DataProperty, ObjectProperty, Property
|
@@ -21,7 +22,7 @@ __all__ = [
|
|
21
22
|
"DataCollection",
|
22
23
|
"Module",
|
23
24
|
"App",
|
24
|
-
"Language",
|
25
25
|
"PyTypeWithDep",
|
26
26
|
"TsTypeWithDep",
|
27
|
+
"Enum",
|
27
28
|
]
|
sera/models/_enum.py
CHANGED
@@ -39,9 +39,13 @@ class Enum:
|
|
39
39
|
)
|
40
40
|
|
41
41
|
def get_pymodule_name(self) -> str:
|
42
|
-
"""Get the python module name of this
|
42
|
+
"""Get the python module name of this enum as if there is a python module created to store this enum only."""
|
43
43
|
return to_snake_case(self.name)
|
44
44
|
|
45
|
+
def get_tsmodule_name(self) -> str:
|
46
|
+
"""Get the typescript module name of this enum as if there is a typescript module created to store this enum only."""
|
47
|
+
return self.name[0].lower() + self.name[1:]
|
48
|
+
|
45
49
|
def is_str_enum(self) -> bool:
|
46
50
|
"""Check if this enum is a string enum."""
|
47
51
|
return all(isinstance(value.value, str) for value in self.values.values())
|
sera/models/_module.py
CHANGED
@@ -2,7 +2,6 @@ from __future__ import annotations
|
|
2
2
|
|
3
3
|
import subprocess
|
4
4
|
from dataclasses import dataclass
|
5
|
-
from enum import Enum
|
6
5
|
from pathlib import Path
|
7
6
|
from typing import Sequence
|
8
7
|
|
@@ -12,10 +11,8 @@ import isort
|
|
12
11
|
from codegen.models import Program
|
13
12
|
from loguru import logger
|
14
13
|
|
15
|
-
|
16
|
-
|
17
|
-
Python = "python"
|
18
|
-
Typescript = "typescript"
|
14
|
+
from sera.misc import File, Formatter
|
15
|
+
from sera.typing import Language
|
19
16
|
|
20
17
|
|
21
18
|
@dataclass
|
@@ -106,15 +103,9 @@ class Module:
|
|
106
103
|
outfile.write_text(copyright_statement + code)
|
107
104
|
|
108
105
|
if self.language == Language.Typescript:
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
["npx", "prettier", "--write", str(outfile.absolute())],
|
113
|
-
cwd=self.package.app.root.dir,
|
114
|
-
)
|
115
|
-
except Exception as e:
|
116
|
-
logger.error("Error formatting Typescript file: {}", e)
|
117
|
-
raise
|
106
|
+
Formatter.get_instance().register(
|
107
|
+
File(path=outfile, language=self.language)
|
108
|
+
)
|
118
109
|
|
119
110
|
def exists(self) -> bool:
|
120
111
|
"""Check if the module exists"""
|
sera/typing.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
from __future__ import annotations
|
2
2
|
|
3
|
+
from enum import Enum
|
3
4
|
from typing import Annotated, TypeVar
|
4
5
|
|
5
6
|
|
@@ -12,3 +13,8 @@ FieldName = Annotated[str, doc("field name of a class")]
|
|
12
13
|
ObjectPath = Annotated[
|
13
14
|
str, doc("path of an object (e.g., can be function, class, etc.)")
|
14
15
|
]
|
16
|
+
|
17
|
+
|
18
|
+
class Language(str, Enum):
|
19
|
+
Python = "python"
|
20
|
+
Typescript = "typescript"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.3
|
2
2
|
Name: sera-2
|
3
|
-
Version: 1.6.
|
3
|
+
Version: 1.6.1
|
4
4
|
Summary:
|
5
5
|
Author: Binh Vu
|
6
6
|
Author-email: bvu687@gmail.com
|
@@ -9,7 +9,7 @@ Classifier: Programming Language :: Python :: 3
|
|
9
9
|
Classifier: Programming Language :: Python :: 3.12
|
10
10
|
Classifier: Programming Language :: Python :: 3.13
|
11
11
|
Requires-Dist: black (>=25.1.0,<26.0.0)
|
12
|
-
Requires-Dist: codegen-2 (>=2.
|
12
|
+
Requires-Dist: codegen-2 (>=2.6.0,<3.0.0)
|
13
13
|
Requires-Dist: isort (>=6.0.1,<7.0.0)
|
14
14
|
Requires-Dist: litestar (>=2.15.1,<3.0.0)
|
15
15
|
Requires-Dist: loguru (>=0.7.0,<0.8.0)
|
@@ -5,27 +5,28 @@ sera/libs/api_helper.py,sha256=hUEy0INHM18lxTQ348tgbXNceOHcjiAnqmuL_8CRpLQ,2509
|
|
5
5
|
sera/libs/base_orm.py,sha256=Fte-NOq62-4ulpjKmdO4V5pQKy9JfaL3ryBqpRRKkNQ,2913
|
6
6
|
sera/libs/base_service.py,sha256=CNAdOPxgXfvdANj75qd4GW5EC0WYW-L5Utm0ozfBbio,4075
|
7
7
|
sera/make/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
-
sera/make/__main__.py,sha256=
|
9
|
-
sera/make/make_app.py,sha256=
|
8
|
+
sera/make/__main__.py,sha256=G5O7s6135-708honwqMFn2yPTs06WbGQTHpupID0eZ4,1417
|
9
|
+
sera/make/make_app.py,sha256=3Crq-Y-VzeHKKAaxfrEqguqTZEtdNPOuGojBQpC9QHw,5950
|
10
10
|
sera/make/make_python_api.py,sha256=RuJUm9z-4plBEtjobeOPr12o27OT-0tSeXI4ZlM3IY0,29433
|
11
11
|
sera/make/make_python_model.py,sha256=8U3fmIK_ahpxyMsCypQvISBVoncRUspYEppWX-_MLtU,35402
|
12
12
|
sera/make/make_python_services.py,sha256=RsinYZdfkrTlTn9CT50VgqGs9w6IZawsJx-KEmqfnEY,2062
|
13
|
-
sera/make/make_typescript_model.py,sha256=
|
14
|
-
sera/misc/__init__.py,sha256=
|
13
|
+
sera/make/make_typescript_model.py,sha256=dT1ZHpsDvegX07jxDRncd_iu4FAiaV_7xyioWBJdbes,36999
|
14
|
+
sera/misc/__init__.py,sha256=Dh4uDq0D4N53h3zhvmwfa5a0TPVRSUvLzb0hkFuPirk,411
|
15
|
+
sera/misc/_formatter.py,sha256=_m1lK2__wD0I0xnZ7JpjQeaxyf6ygYOZdrYzaYJd4s4,1625
|
15
16
|
sera/misc/_utils.py,sha256=V5g4oLGHOhUCR75Kkcn1w01pAvGvaepK-T8Z3pIgHjI,1450
|
16
|
-
sera/models/__init__.py,sha256=
|
17
|
+
sera/models/__init__.py,sha256=vJC5Kzo_N7wd16ocNPy1VvAZDGNiWeiAhWJ4ihATKvA,780
|
17
18
|
sera/models/_class.py,sha256=Wf0e8x6-szG9TzoFkAlqj7_dG0SCICMBw_333n3paxk,2514
|
18
19
|
sera/models/_collection.py,sha256=ZnQEriKC4X88Zz48Kn1AVZKH-1_l8OgWa-zf2kcQOOE,1414
|
19
20
|
sera/models/_constraints.py,sha256=lZmCh6Py0UVMdhTR7zUOPPzGqJGbmDCzf7xH7yITcbQ,1278
|
20
21
|
sera/models/_datatype.py,sha256=uMxK_8wBLowaIMIAYCb3V17YmkzJrKKc5orjImzqWbA,5818
|
21
22
|
sera/models/_default.py,sha256=ABggW6qdPR4ZDqIPJdJ0GCGQ-7kfsfZmQ_DchgZEa-I,137
|
22
|
-
sera/models/_enum.py,sha256=
|
23
|
-
sera/models/_module.py,sha256=
|
23
|
+
sera/models/_enum.py,sha256=sy0q7E646F-APsqrVQ52r1fAQ_DCAeaNq5YM5QN3zIk,2070
|
24
|
+
sera/models/_module.py,sha256=CAk8CCuo3hs_mBkmfXDV-BsMQhv4Wgtp0tcY78JLjUE,4922
|
24
25
|
sera/models/_multi_lingual_string.py,sha256=JETN6k00VH4wrA4w5vAHMEJV8fp3SY9bJebskFTjQLA,1186
|
25
26
|
sera/models/_parse.py,sha256=sJYfQtwek96ltpgxExG4xUbiLnU3qvNYhTP1CeyXGjs,9746
|
26
27
|
sera/models/_property.py,sha256=CmEmgOShtSyNFq05YW3tGupwCIVRzPMKudXWld8utPk,5530
|
27
28
|
sera/models/_schema.py,sha256=r-Gqg9Lb_wR3UrbNvfXXgt_qs5bts0t2Ve7aquuF_OI,1155
|
28
|
-
sera/typing.py,sha256=
|
29
|
-
sera_2-1.6.
|
30
|
-
sera_2-1.6.
|
31
|
-
sera_2-1.6.
|
29
|
+
sera/typing.py,sha256=Ak0Su0EhjFirWOOwbIW8u4YpEwP8apfpIH5Tv9foTRo,430
|
30
|
+
sera_2-1.6.1.dist-info/METADATA,sha256=GuYpBmAl_5gq9go1LZ7AcjVsXi3Nx5a21dDYIpEokzg,856
|
31
|
+
sera_2-1.6.1.dist-info/WHEEL,sha256=fGIA9gx4Qxk2KDKeNJCbOEwSrmLtjWCwzBz351GyrPQ,88
|
32
|
+
sera_2-1.6.1.dist-info/RECORD,,
|
File without changes
|