jsonbp 1.2.2__tar.gz → 1.3.0__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.
- {jsonbp-1.2.2/jsonbp.egg-info → jsonbp-1.3.0}/PKG-INFO +3 -2
- {jsonbp-1.2.2 → jsonbp-1.3.0}/README.md +1 -1
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/blueprint.py +128 -8
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/error.py +7 -4
- jsonbp-1.3.0/jsonbp/localization/de_DE.ini +20 -0
- jsonbp-1.3.0/jsonbp/localization/fr_FR.ini +20 -0
- jsonbp-1.3.0/jsonbp/localization/ru_RU.ini +20 -0
- jsonbp-1.3.0/jsonbp/localization/zh_CN.ini +20 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/parser.py +200 -14
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/types.py +1 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0/jsonbp.egg-info}/PKG-INFO +3 -2
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp.egg-info/SOURCES.txt +13 -9
- {jsonbp-1.2.2 → jsonbp-1.3.0}/pyproject.toml +8 -2
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_deserialization.py +1 -1
- {jsonbp-1.2.2 → jsonbp-1.3.0}/LICENSE.txt +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/MANIFEST.in +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/__init__.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/array.py +0 -0
- {jsonbp-1.2.2/jsonbp/localization → jsonbp-1.3.0/jsonbp/basetypes}/__init__.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/bool.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/date.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/decimal.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/float.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/instant.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/integer.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/limits.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/basetypes}/string.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/declaration.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/exception.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/field.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/loader.py +0 -0
- {jsonbp-1.2.2/jsonbp/types → jsonbp-1.3.0/jsonbp/localization}/__init__.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/localization/en_US.ini +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/localization/pt_BR.ini +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/ply/__init__.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/ply/lex.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp/ply/yacc.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp.egg-info/dependency_links.txt +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/jsonbp.egg-info/top_level.txt +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/setup.cfg +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_caching.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_choose_root.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_error_fallbacks.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_loading.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_parsing.py +0 -0
- {jsonbp-1.2.2 → jsonbp-1.3.0}/tests/test_serialization.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonbp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Schema based JSON Parser/Serializer
|
|
5
5
|
Author-email: Gustavo Venturini <gustavo.c.venturini@gmail.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -11,6 +11,7 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
15
|
Requires-Python: >=3.11
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
License-File: LICENSE.txt
|
|
@@ -27,7 +28,7 @@ to and from Python based on schemas. While [json-schema][json_schema] and its
|
|
|
27
28
|
implementations offer a more mature and widely used technique, a different
|
|
28
29
|
approach was desired, which led to the development of this library.
|
|
29
30
|
|
|
30
|
-
jsonbp's design
|
|
31
|
+
jsonbp's main design goals were:
|
|
31
32
|
- schema reuse through import / type system
|
|
32
33
|
- custom user definable primitive types
|
|
33
34
|
- built in numeric fixed precision type which deserializes into Python's Decimal
|
|
@@ -9,7 +9,7 @@ to and from Python based on schemas. While [json-schema][json_schema] and its
|
|
|
9
9
|
implementations offer a more mature and widely used technique, a different
|
|
10
10
|
approach was desired, which led to the development of this library.
|
|
11
11
|
|
|
12
|
-
jsonbp's design
|
|
12
|
+
jsonbp's main design goals were:
|
|
13
13
|
- schema reuse through import / type system
|
|
14
14
|
- custom user definable primitive types
|
|
15
15
|
- built in numeric fixed precision type which deserializes into Python's Decimal
|
|
@@ -28,7 +28,7 @@ unquote = lambda x : unquoted_str(x)
|
|
|
28
28
|
|
|
29
29
|
class JsonBlueprint:
|
|
30
30
|
"""
|
|
31
|
-
Class to serialize Python objects to JSON strings and vice
|
|
31
|
+
Class to serialize Python objects to JSON strings and vice versa.
|
|
32
32
|
"""
|
|
33
33
|
|
|
34
34
|
def __init__(self, primitive_types):
|
|
@@ -38,6 +38,8 @@ class JsonBlueprint:
|
|
|
38
38
|
self.derived_types = dict()
|
|
39
39
|
self.enums = dict()
|
|
40
40
|
self.objects = dict()
|
|
41
|
+
self.templates = dict()
|
|
42
|
+
self.unions = dict()
|
|
41
43
|
self.root = None
|
|
42
44
|
|
|
43
45
|
def __str__(self): # pragma: no cover
|
|
@@ -90,6 +92,35 @@ class JsonBlueprint:
|
|
|
90
92
|
return True, value
|
|
91
93
|
|
|
92
94
|
|
|
95
|
+
def _validate_union(self, field_name, unionDef, contents):
|
|
96
|
+
if not isinstance(contents, collections.abc.Mapping):
|
|
97
|
+
return False, create_field_error(field_name,
|
|
98
|
+
ErrorType.INVALID_OBJECT)
|
|
99
|
+
|
|
100
|
+
discriminator = unionDef['discriminator']
|
|
101
|
+
if discriminator not in contents:
|
|
102
|
+
return False, create_field_error(field_name,
|
|
103
|
+
ErrorType.MISSING_FIELD, field=discriminator)
|
|
104
|
+
|
|
105
|
+
tag_value = contents[discriminator]
|
|
106
|
+
if isinstance(tag_value, unquoted_str):
|
|
107
|
+
return False, create_field_error(field_name,
|
|
108
|
+
ErrorType.INVALID_ENUM)
|
|
109
|
+
|
|
110
|
+
branch_name = unionDef['branches'].get(tag_value)
|
|
111
|
+
if branch_name is None:
|
|
112
|
+
return False, create_field_error(field_name,
|
|
113
|
+
ErrorType.UNKNOWN_LITERAL)
|
|
114
|
+
|
|
115
|
+
branch_fields = self._find_object_decl(branch_name)
|
|
116
|
+
success, outcome = self._validate_object(field_name, branch_fields, contents)
|
|
117
|
+
if not success:
|
|
118
|
+
return False, outcome
|
|
119
|
+
|
|
120
|
+
outcome[discriminator] = tag_value
|
|
121
|
+
return True, outcome
|
|
122
|
+
|
|
123
|
+
|
|
93
124
|
def _validate_array(self, field_name, jArray, contents):
|
|
94
125
|
if not isinstance(contents, collections.abc.Sequence):
|
|
95
126
|
return False, create_field_error(field_name,
|
|
@@ -111,6 +142,10 @@ class JsonBlueprint:
|
|
|
111
142
|
element_type = self._find_object_decl(array_type)
|
|
112
143
|
deserializer = self._validate_object
|
|
113
144
|
|
|
145
|
+
if array_kind == FieldType.UNION:
|
|
146
|
+
element_type = self._find_union_decl(array_type)
|
|
147
|
+
deserializer = self._validate_union
|
|
148
|
+
|
|
114
149
|
result = list()
|
|
115
150
|
for idx, value in enumerate(contents):
|
|
116
151
|
if value is None:
|
|
@@ -213,6 +248,18 @@ class JsonBlueprint:
|
|
|
213
248
|
result[field_name] = processed
|
|
214
249
|
continue
|
|
215
250
|
|
|
251
|
+
if field_kind == FieldType.UNION:
|
|
252
|
+
unionDef = self._find_union_decl(field_type)
|
|
253
|
+
success, outcome = self._validate_union(
|
|
254
|
+
field_name, unionDef,
|
|
255
|
+
retrieved)
|
|
256
|
+
|
|
257
|
+
if not success:
|
|
258
|
+
return False, outcome
|
|
259
|
+
|
|
260
|
+
result[field_name] = outcome
|
|
261
|
+
continue
|
|
262
|
+
|
|
216
263
|
success, outcome = self._validate_field(
|
|
217
264
|
field_name, field_type,
|
|
218
265
|
retrieved)
|
|
@@ -264,12 +311,16 @@ class JsonBlueprint:
|
|
|
264
311
|
return self._validate_field(None, root_type,
|
|
265
312
|
root_contents)
|
|
266
313
|
|
|
314
|
+
if root_kind == FieldType.UNION:
|
|
315
|
+
unionDef = self._find_union_decl(root_type)
|
|
316
|
+
return self._validate_union(None, unionDef, root_contents)
|
|
317
|
+
|
|
267
318
|
|
|
268
319
|
def deserialize(self, contents):
|
|
269
320
|
"""Attempts to deserialize a JSON string into a Python object.
|
|
270
321
|
|
|
271
322
|
The returned tuple's first element indicates whether the deserialization
|
|
272
|
-
was successful. When deserialization succeeds the
|
|
323
|
+
was successful. When deserialization succeeds the resulting Python data
|
|
273
324
|
will be the second element. Conversely, when a problem is found the first
|
|
274
325
|
element will be False and the second element will be an instance of
|
|
275
326
|
:class:`DeserializationError`.
|
|
@@ -318,6 +369,8 @@ class JsonBlueprint:
|
|
|
318
369
|
collected.extend(source.derived_types.keys())
|
|
319
370
|
collected.extend(source.enums.keys())
|
|
320
371
|
collected.extend(source.objects.keys())
|
|
372
|
+
collected.extend(source.templates.keys())
|
|
373
|
+
collected.extend(source.unions.keys())
|
|
321
374
|
|
|
322
375
|
return collected
|
|
323
376
|
|
|
@@ -369,6 +422,38 @@ class JsonBlueprint:
|
|
|
369
422
|
|
|
370
423
|
return None
|
|
371
424
|
|
|
425
|
+
|
|
426
|
+
def _find_union_decl(self, union_name, checked=None):
|
|
427
|
+
if union_name in self.unions:
|
|
428
|
+
return self.unions[union_name]
|
|
429
|
+
|
|
430
|
+
checked = checked or set()
|
|
431
|
+
checked.add(self)
|
|
432
|
+
|
|
433
|
+
for blueprint in self.includes:
|
|
434
|
+
if blueprint not in checked:
|
|
435
|
+
found = blueprint._find_union_decl(union_name, checked)
|
|
436
|
+
if found is not None:
|
|
437
|
+
return found
|
|
438
|
+
|
|
439
|
+
return None
|
|
440
|
+
|
|
441
|
+
|
|
442
|
+
def _find_template_decl(self, template_name, checked=None):
|
|
443
|
+
if template_name in self.templates:
|
|
444
|
+
return self.templates[template_name]
|
|
445
|
+
|
|
446
|
+
checked = checked or set()
|
|
447
|
+
checked.add(self)
|
|
448
|
+
|
|
449
|
+
for blueprint in self.includes:
|
|
450
|
+
if blueprint not in checked:
|
|
451
|
+
found = blueprint._find_template_decl(template_name, checked)
|
|
452
|
+
if found is not None:
|
|
453
|
+
return found
|
|
454
|
+
|
|
455
|
+
return None
|
|
456
|
+
|
|
372
457
|
#----------------------------------------------------------------------------
|
|
373
458
|
|
|
374
459
|
def choose_root(self, root_type, as_array=False,
|
|
@@ -377,15 +462,15 @@ class JsonBlueprint:
|
|
|
377
462
|
"""Selects or changes the root element of a blueprint.
|
|
378
463
|
|
|
379
464
|
This function allows you to create a derivative JsonBlueprint
|
|
380
|
-
instance,
|
|
465
|
+
instance, overriding the root type (or selecting one, if none
|
|
381
466
|
is defined). The original source JsonBlueprint instance remains
|
|
382
467
|
unchanged.
|
|
383
468
|
|
|
384
469
|
Args:
|
|
385
470
|
root_type (str): name of the type to be used as root.
|
|
386
|
-
as_array (bool): whether the
|
|
387
|
-
min_array_length (int): if
|
|
388
|
-
max_array_length (int): if
|
|
471
|
+
as_array (bool): whether the root should be an array of the selected type.
|
|
472
|
+
min_array_length (int): if `as_array` is True, the minimum number of elements the root array must contain.
|
|
473
|
+
max_array_length (int): if `as_array` is True, the maximum number of elements the root array can contain.
|
|
389
474
|
|
|
390
475
|
Returns:
|
|
391
476
|
A new blueprint, with the root selected or replaced.
|
|
@@ -399,7 +484,8 @@ class JsonBlueprint:
|
|
|
399
484
|
lookups = [
|
|
400
485
|
(self._find_object_decl, FieldType.OBJECT),
|
|
401
486
|
(self._find_element_decl, FieldType.SIMPLE),
|
|
402
|
-
(self._find_enum_decl, FieldType.ENUM)
|
|
487
|
+
(self._find_enum_decl, FieldType.ENUM),
|
|
488
|
+
(self._find_union_decl, FieldType.UNION),
|
|
403
489
|
]
|
|
404
490
|
|
|
405
491
|
found_root = None
|
|
@@ -428,11 +514,44 @@ class JsonBlueprint:
|
|
|
428
514
|
result.derived_types = self.derived_types
|
|
429
515
|
result.enums = self.enums
|
|
430
516
|
result.objects = self.objects
|
|
517
|
+
result.unions = self.unions
|
|
431
518
|
|
|
432
519
|
return result
|
|
433
520
|
|
|
434
521
|
#----------------------------------------------------------------------------
|
|
435
522
|
|
|
523
|
+
def _serialize_union(self, union_type, field_name, content):
|
|
524
|
+
unionDef = self._find_union_decl(union_type)
|
|
525
|
+
discriminator = unionDef['discriminator']
|
|
526
|
+
|
|
527
|
+
if not isinstance(content, collections.abc.Mapping):
|
|
528
|
+
raise SerializationException(f"{field_name} needs a dict to serialize")
|
|
529
|
+
|
|
530
|
+
if discriminator not in content:
|
|
531
|
+
raise SerializationException(
|
|
532
|
+
f"{field_name}: missing discriminator field '{discriminator}'")
|
|
533
|
+
|
|
534
|
+
tag_value = content[discriminator]
|
|
535
|
+
branch_name = unionDef['branches'].get(tag_value)
|
|
536
|
+
if branch_name is None:
|
|
537
|
+
raise SerializationException(
|
|
538
|
+
f"{field_name}: unknown tag value '{tag_value}'")
|
|
539
|
+
|
|
540
|
+
serialized = [f'"{discriminator}":"{tag_value}"']
|
|
541
|
+
branch_fields = self._find_object_decl(branch_name)
|
|
542
|
+
|
|
543
|
+
for fname, fdata in branch_fields.items():
|
|
544
|
+
if fname not in content:
|
|
545
|
+
if fdata.optional:
|
|
546
|
+
continue
|
|
547
|
+
raise SerializationException(f"{field_name}: missing field '{fname}'")
|
|
548
|
+
|
|
549
|
+
processed = self._serialize_element(fdata, fname, content[fname])
|
|
550
|
+
serialized.append(f'"{fname}":{processed}')
|
|
551
|
+
|
|
552
|
+
return "{" + ",".join(serialized) + "}"
|
|
553
|
+
|
|
554
|
+
|
|
436
555
|
def _serialize_element(self, element, elementName, content):
|
|
437
556
|
contentKind = element.fieldKind
|
|
438
557
|
contentType = element.fieldType
|
|
@@ -440,7 +559,8 @@ class JsonBlueprint:
|
|
|
440
559
|
method = {
|
|
441
560
|
FieldType.OBJECT: JsonBlueprint._serialize_object,
|
|
442
561
|
FieldType.ENUM: JsonBlueprint._serialize_enum,
|
|
443
|
-
FieldType.SIMPLE: JsonBlueprint._serialize_field
|
|
562
|
+
FieldType.SIMPLE: JsonBlueprint._serialize_field,
|
|
563
|
+
FieldType.UNION: JsonBlueprint._serialize_union,
|
|
444
564
|
} [contentKind]
|
|
445
565
|
|
|
446
566
|
if is_array(element):
|
|
@@ -30,8 +30,8 @@ def use_default_language(language):
|
|
|
30
30
|
|
|
31
31
|
This language will be applied when no languages are
|
|
32
32
|
specified in the :func:`DeserializationError.localize` method. When
|
|
33
|
-
jsonbp starts, this language is set to American English (en-US). If
|
|
34
|
-
|
|
33
|
+
jsonbp starts, this language is set to American English (en-US). If
|
|
34
|
+
the language code passed is not available, it will also fall back to
|
|
35
35
|
American English.
|
|
36
36
|
|
|
37
37
|
Args:
|
|
@@ -57,7 +57,7 @@ def load_translation(filename, language):
|
|
|
57
57
|
|
|
58
58
|
Args:
|
|
59
59
|
filename (str): path to the ini file.
|
|
60
|
-
language (str): language code that
|
|
60
|
+
language (str): language code that the file corresponds to.
|
|
61
61
|
|
|
62
62
|
Raises:
|
|
63
63
|
IOError: When `filename` cannot be opened for reading.
|
|
@@ -139,7 +139,7 @@ class DeserializationError:
|
|
|
139
139
|
:func:`use_default_language`.
|
|
140
140
|
|
|
141
141
|
Returns:
|
|
142
|
-
the localized
|
|
142
|
+
the localized error message describing the issue.
|
|
143
143
|
|
|
144
144
|
"""
|
|
145
145
|
|
|
@@ -149,6 +149,9 @@ class DeserializationError:
|
|
|
149
149
|
|
|
150
150
|
for candidate in localization_priority:
|
|
151
151
|
translation = translations.get(candidate)
|
|
152
|
+
if translation is None:
|
|
153
|
+
prefix = candidate + '_'
|
|
154
|
+
translation = next((translations[k] for k in translations if k.startswith(prefix)), None)
|
|
152
155
|
if translation is not None:
|
|
153
156
|
break
|
|
154
157
|
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
[Messages]
|
|
3
|
+
JSON_PARSING=Ungültiges JSON, Fehler in Zeile {line}, Spalte {column}: {message}
|
|
4
|
+
VALUE_PARSING=Wert konnte nicht als '{type}' dekodiert werden
|
|
5
|
+
NULL_VALUE=Nullwert
|
|
6
|
+
OUTSIDE_RANGE=Wert {value} liegt außerhalb des erwarteten Bereichs
|
|
7
|
+
INVALID_LENGTH=Länge {length} liegt außerhalb des erwarteten Bereichs
|
|
8
|
+
UNKNOWN_LITERAL=Unbekannter Wert
|
|
9
|
+
INVALID_ENUM=Wert muss eine Zeichenkette sein
|
|
10
|
+
INVALID_FORMAT=Inhalt entspricht nicht dem erwarteten Format
|
|
11
|
+
MISSING_FIELD=Fehlendes Feld '{field}'
|
|
12
|
+
INVALID_ARRAY=Muss eine Liste sein
|
|
13
|
+
INVALID_OBJECT=Muss ein Objekt sein
|
|
14
|
+
|
|
15
|
+
[Prefixes]
|
|
16
|
+
FIELD=Feld '{assignee}'
|
|
17
|
+
OBJECT=Objekt '{assignee}'
|
|
18
|
+
ARRAY=In Liste '{assignee}' bei Index {index}
|
|
19
|
+
ROOT=Auf Wurzelebene
|
|
20
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
[Messages]
|
|
3
|
+
JSON_PARSING=JSON invalide, erreur à la ligne {line}, colonne {column}: {message}
|
|
4
|
+
VALUE_PARSING=Impossible de décoder la valeur en '{type}'
|
|
5
|
+
NULL_VALUE=Valeur nulle
|
|
6
|
+
OUTSIDE_RANGE=La valeur {value} est en dehors de la plage attendue
|
|
7
|
+
INVALID_LENGTH=La longueur {length} est en dehors de la plage attendue
|
|
8
|
+
UNKNOWN_LITERAL=Valeur inconnue
|
|
9
|
+
INVALID_ENUM=La valeur doit être une chaîne de caractères
|
|
10
|
+
INVALID_FORMAT=Le contenu n'est pas dans le format attendu
|
|
11
|
+
MISSING_FIELD=Champ manquant '{field}'
|
|
12
|
+
INVALID_ARRAY=Doit être une liste
|
|
13
|
+
INVALID_OBJECT=Doit être un objet
|
|
14
|
+
|
|
15
|
+
[Prefixes]
|
|
16
|
+
FIELD=Champ '{assignee}'
|
|
17
|
+
OBJECT=Objet '{assignee}'
|
|
18
|
+
ARRAY=Dans la liste '{assignee}' à l'index {index}
|
|
19
|
+
ROOT=Au niveau racine
|
|
20
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
[Messages]
|
|
3
|
+
JSON_PARSING=Некорректный JSON, ошибка в строке {line}, столбец {column}: {message}
|
|
4
|
+
VALUE_PARSING=Не удалось декодировать значение как '{type}'
|
|
5
|
+
NULL_VALUE=Нулевое значение
|
|
6
|
+
OUTSIDE_RANGE=Значение {value} выходит за пределы ожидаемого диапазона
|
|
7
|
+
INVALID_LENGTH=Длина {length} выходит за пределы ожидаемого диапазона
|
|
8
|
+
UNKNOWN_LITERAL=Неизвестное значение
|
|
9
|
+
INVALID_ENUM=Значение должно быть строкой
|
|
10
|
+
INVALID_FORMAT=Содержимое не соответствует ожидаемому формату
|
|
11
|
+
MISSING_FIELD=Отсутствует поле '{field}'
|
|
12
|
+
INVALID_ARRAY=Должно быть списком
|
|
13
|
+
INVALID_OBJECT=Должно быть объектом
|
|
14
|
+
|
|
15
|
+
[Prefixes]
|
|
16
|
+
FIELD=Поле '{assignee}'
|
|
17
|
+
OBJECT=Объект '{assignee}'
|
|
18
|
+
ARRAY=В списке '{assignee}' по индексу {index}
|
|
19
|
+
ROOT=На корневом уровне
|
|
20
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
|
|
2
|
+
[Messages]
|
|
3
|
+
JSON_PARSING=无效的JSON,第{line}行第{column}列发生错误:{message}
|
|
4
|
+
VALUE_PARSING=无法将值解码为"{type}"
|
|
5
|
+
NULL_VALUE=空值
|
|
6
|
+
OUTSIDE_RANGE=值{value}超出预期范围
|
|
7
|
+
INVALID_LENGTH=长度{length}超出预期范围
|
|
8
|
+
UNKNOWN_LITERAL=未知值
|
|
9
|
+
INVALID_ENUM=值必须是字符串
|
|
10
|
+
INVALID_FORMAT=内容格式不符合预期
|
|
11
|
+
MISSING_FIELD=缺少字段"{field}"
|
|
12
|
+
INVALID_ARRAY=必须是列表
|
|
13
|
+
INVALID_OBJECT=必须是对象
|
|
14
|
+
|
|
15
|
+
[Prefixes]
|
|
16
|
+
FIELD=字段"{assignee}"
|
|
17
|
+
OBJECT=对象"{assignee}"
|
|
18
|
+
ARRAY=列表"{assignee}"的索引{index}处
|
|
19
|
+
ROOT=根级别
|
|
20
|
+
|
|
@@ -9,8 +9,8 @@ from .error import print_warning, print_error
|
|
|
9
9
|
from .loader import load_types
|
|
10
10
|
from .blueprint import JsonBlueprint
|
|
11
11
|
from .declaration import create_declaration
|
|
12
|
-
from .field import create_field
|
|
13
|
-
from .array import make_array
|
|
12
|
+
from .field import create_field, JsonField
|
|
13
|
+
from .array import make_array, is_array
|
|
14
14
|
|
|
15
15
|
reserved = (
|
|
16
16
|
'root',
|
|
@@ -20,7 +20,10 @@ reserved = (
|
|
|
20
20
|
'optional',
|
|
21
21
|
'nullable',
|
|
22
22
|
'extends',
|
|
23
|
-
'include'
|
|
23
|
+
'include',
|
|
24
|
+
'wraps',
|
|
25
|
+
'union',
|
|
26
|
+
'on'
|
|
24
27
|
)
|
|
25
28
|
|
|
26
29
|
literals = (
|
|
@@ -32,7 +35,9 @@ literals = (
|
|
|
32
35
|
']',
|
|
33
36
|
',',
|
|
34
37
|
'=',
|
|
35
|
-
':'
|
|
38
|
+
':',
|
|
39
|
+
'<',
|
|
40
|
+
'>'
|
|
36
41
|
)
|
|
37
42
|
|
|
38
43
|
# List of tokens
|
|
@@ -44,12 +49,17 @@ types = (
|
|
|
44
49
|
'FLOAT_CONST',
|
|
45
50
|
'INTEGER_AMOUNT',
|
|
46
51
|
'IDENTIFIER',
|
|
52
|
+
'ARROW',
|
|
47
53
|
)
|
|
48
54
|
|
|
49
55
|
tokens = list(types) + [token.upper() for token in reserved]
|
|
50
56
|
|
|
51
57
|
#------------------------------------------------------------
|
|
52
58
|
|
|
59
|
+
def t_ARROW(t):
|
|
60
|
+
r'=>'
|
|
61
|
+
return t
|
|
62
|
+
|
|
53
63
|
def t_STRING(t):
|
|
54
64
|
r'"[^"]*"'
|
|
55
65
|
t.value = t.value[1:-1]
|
|
@@ -117,7 +127,8 @@ def typeExists(typeName, excluded=None):
|
|
|
117
127
|
lookups = (
|
|
118
128
|
currentBlueprint._find_object_decl,
|
|
119
129
|
currentBlueprint._find_element_decl,
|
|
120
|
-
currentBlueprint._find_enum_decl
|
|
130
|
+
currentBlueprint._find_enum_decl,
|
|
131
|
+
currentBlueprint._find_union_decl,
|
|
121
132
|
)
|
|
122
133
|
|
|
123
134
|
for method in lookups:
|
|
@@ -134,6 +145,8 @@ def getNextAdhoc():
|
|
|
134
145
|
adhoc_counter += 1
|
|
135
146
|
return adhoc_counter
|
|
136
147
|
|
|
148
|
+
currentTypeParam = None
|
|
149
|
+
|
|
137
150
|
#---------------- general structure -----------------------------
|
|
138
151
|
|
|
139
152
|
def p_schema(p):
|
|
@@ -149,6 +162,7 @@ def p_construction(p):
|
|
|
149
162
|
| object
|
|
150
163
|
| root
|
|
151
164
|
| include
|
|
165
|
+
| union
|
|
152
166
|
'''
|
|
153
167
|
|
|
154
168
|
#---------------- constructs -----------------------------
|
|
@@ -191,12 +205,39 @@ def p_root(p):
|
|
|
191
205
|
currentBlueprint.root = p[2]
|
|
192
206
|
|
|
193
207
|
|
|
208
|
+
def p_generic_header(p):
|
|
209
|
+
'''
|
|
210
|
+
generic_header : OBJECT IDENTIFIER WRAPS IDENTIFIER
|
|
211
|
+
'''
|
|
212
|
+
|
|
213
|
+
global currentTypeParam
|
|
214
|
+
currentTypeParam = p[4]
|
|
215
|
+
p[0] = p[2]
|
|
216
|
+
|
|
217
|
+
|
|
194
218
|
def p_object(p):
|
|
195
219
|
'''
|
|
196
|
-
object :
|
|
220
|
+
object : generic_header object_declaration
|
|
221
|
+
| OBJECT IDENTIFIER EXTENDS IDENTIFIER object_declaration
|
|
197
222
|
| OBJECT IDENTIFIER object_declaration
|
|
198
223
|
'''
|
|
199
224
|
|
|
225
|
+
global currentTypeParam
|
|
226
|
+
|
|
227
|
+
if len(p) == 3:
|
|
228
|
+
templateName = p[1]
|
|
229
|
+
paramName = currentTypeParam
|
|
230
|
+
currentTypeParam = None
|
|
231
|
+
|
|
232
|
+
if typeExists(templateName) or currentBlueprint._find_template_decl(templateName) is not None:
|
|
233
|
+
raise SchemaViolation(f"Duplicated type '{templateName}'")
|
|
234
|
+
|
|
235
|
+
currentBlueprint.templates[templateName] = {
|
|
236
|
+
'param': paramName,
|
|
237
|
+
'fields': p[2]
|
|
238
|
+
}
|
|
239
|
+
return
|
|
240
|
+
|
|
200
241
|
objectName = p[2]
|
|
201
242
|
if typeExists(objectName):
|
|
202
243
|
msg = f"Duplicated type '{objectName}'"
|
|
@@ -229,13 +270,30 @@ def p_object(p):
|
|
|
229
270
|
def p_object_specs(p):
|
|
230
271
|
'''
|
|
231
272
|
object_declaration : '{' attributes '}'
|
|
273
|
+
| EXTENDS IDENTIFIER '{' attributes '}'
|
|
232
274
|
'''
|
|
233
275
|
|
|
234
|
-
|
|
276
|
+
if len(p) == 4:
|
|
277
|
+
decls = p[2]
|
|
278
|
+
baseFields = {}
|
|
279
|
+
|
|
280
|
+
else:
|
|
281
|
+
baseObject = p[2]
|
|
282
|
+
baseFields = currentBlueprint._find_object_decl(baseObject)
|
|
283
|
+
if baseFields is None:
|
|
284
|
+
raise SchemaViolation(f"Object '{baseObject}' is not defined")
|
|
285
|
+
decls = p[4]
|
|
286
|
+
|
|
235
287
|
newObject = dict()
|
|
236
288
|
for decl in decls:
|
|
237
|
-
|
|
289
|
+
fieldName = decl[0]
|
|
290
|
+
if fieldName in baseFields:
|
|
291
|
+
raise SchemaViolation(
|
|
292
|
+
f"Field '{fieldName}' already defined in '{baseObject}'"
|
|
293
|
+
)
|
|
294
|
+
newObject[fieldName] = decl[1]
|
|
238
295
|
|
|
296
|
+
newObject.update(baseFields)
|
|
239
297
|
p[0] = newObject
|
|
240
298
|
|
|
241
299
|
|
|
@@ -296,10 +354,12 @@ def p_type(p):
|
|
|
296
354
|
def p_attributes(p):
|
|
297
355
|
'''
|
|
298
356
|
attributes : attributes ',' attribute
|
|
357
|
+
| attributes ','
|
|
299
358
|
| attribute
|
|
300
359
|
'''
|
|
301
360
|
|
|
302
361
|
if len(p) == 4: p[1].append(p[3])
|
|
362
|
+
if len(p) == 3: p[0] = p[1]; return
|
|
303
363
|
if len(p) == 2: p[1] = [p[1]]
|
|
304
364
|
p[0] = p[1]
|
|
305
365
|
|
|
@@ -358,11 +418,66 @@ def p_single_declaration(p):
|
|
|
358
418
|
p[0] = p[1]
|
|
359
419
|
|
|
360
420
|
|
|
421
|
+
def instantiateTemplate(templateName, argType):
|
|
422
|
+
template = currentBlueprint._find_template_decl(templateName)
|
|
423
|
+
if template is None:
|
|
424
|
+
raise SchemaViolation(f"Template '{templateName}' is not defined")
|
|
425
|
+
|
|
426
|
+
if not typeExists(argType):
|
|
427
|
+
raise SchemaViolation(f"Type not declared: '{argType}'")
|
|
428
|
+
|
|
429
|
+
concreteName = f'{templateName}__{argType}'
|
|
430
|
+
if currentBlueprint._find_object_decl(concreteName) is not None:
|
|
431
|
+
return concreteName
|
|
432
|
+
|
|
433
|
+
if currentBlueprint._find_object_decl(argType) is not None:
|
|
434
|
+
argKind = FieldType.OBJECT
|
|
435
|
+
elif currentBlueprint._find_enum_decl(argType) is not None:
|
|
436
|
+
argKind = FieldType.ENUM
|
|
437
|
+
else:
|
|
438
|
+
argKind = FieldType.SIMPLE
|
|
439
|
+
|
|
440
|
+
paramName = template['param']
|
|
441
|
+
concreteFields = dict()
|
|
442
|
+
|
|
443
|
+
for fieldName, field in template['fields'].items():
|
|
444
|
+
if field.fieldType != paramName:
|
|
445
|
+
concreteFields[fieldName] = field
|
|
446
|
+
continue
|
|
447
|
+
|
|
448
|
+
if is_array(field):
|
|
449
|
+
newBase = create_field(argKind, argType)
|
|
450
|
+
newBase.nullable = field.nullable
|
|
451
|
+
newField = make_array(newBase)
|
|
452
|
+
newField.minLength = field.minLength
|
|
453
|
+
newField.maxLength = field.maxLength
|
|
454
|
+
newField.nullableArray = field.nullableArray
|
|
455
|
+
else:
|
|
456
|
+
newField = create_field(argKind, argType)
|
|
457
|
+
newField.nullable = field.nullable
|
|
458
|
+
newField.optional = field.optional
|
|
459
|
+
|
|
460
|
+
concreteFields[fieldName] = newField
|
|
461
|
+
|
|
462
|
+
currentBlueprint.objects[concreteName] = concreteFields
|
|
463
|
+
return concreteName
|
|
464
|
+
|
|
465
|
+
|
|
466
|
+
def p_template_declaration(p):
|
|
467
|
+
'''
|
|
468
|
+
template_declaration : IDENTIFIER '<' IDENTIFIER '>'
|
|
469
|
+
'''
|
|
470
|
+
|
|
471
|
+
concreteName = instantiateTemplate(p[1], p[3])
|
|
472
|
+
p[0] = create_field(FieldType.OBJECT, concreteName)
|
|
473
|
+
|
|
474
|
+
|
|
361
475
|
def p_atomic_declaration(p):
|
|
362
476
|
'''
|
|
363
477
|
atomic_declaration : object_declaration
|
|
364
478
|
| enum_declaration
|
|
365
479
|
| element_declaration
|
|
480
|
+
| template_declaration
|
|
366
481
|
'''
|
|
367
482
|
|
|
368
483
|
declaration = p[1]
|
|
@@ -378,6 +493,10 @@ def p_atomic_declaration(p):
|
|
|
378
493
|
kind = FieldType.ENUM
|
|
379
494
|
fieldId = adhoc_enum
|
|
380
495
|
|
|
496
|
+
elif isinstance(declaration, JsonField):
|
|
497
|
+
p[0] = declaration
|
|
498
|
+
return
|
|
499
|
+
|
|
381
500
|
else:
|
|
382
501
|
declType = declaration.typeName
|
|
383
502
|
if currentBlueprint._find_element_decl(declType) is not None:
|
|
@@ -395,6 +514,10 @@ def p_atomic_declaration(p):
|
|
|
395
514
|
kind = FieldType.ENUM
|
|
396
515
|
fieldId = declType
|
|
397
516
|
|
|
517
|
+
elif currentBlueprint._find_union_decl(declType):
|
|
518
|
+
kind = FieldType.UNION
|
|
519
|
+
fieldId = declType
|
|
520
|
+
|
|
398
521
|
else:
|
|
399
522
|
kind = FieldType.OBJECT
|
|
400
523
|
fieldId = declType
|
|
@@ -427,7 +550,7 @@ def p_element_declaration(p):
|
|
|
427
550
|
specs = p[3]
|
|
428
551
|
|
|
429
552
|
else:
|
|
430
|
-
if not typeExists(typeName):
|
|
553
|
+
if typeName != currentTypeParam and not typeExists(typeName):
|
|
431
554
|
msg = f"Type not declared: '{typeName}'"
|
|
432
555
|
raise SchemaViolation(msg)
|
|
433
556
|
|
|
@@ -466,6 +589,65 @@ def p_specified_value(p):
|
|
|
466
589
|
|
|
467
590
|
p[0] = p[1]
|
|
468
591
|
|
|
592
|
+
#---------------- unions ----------------------------
|
|
593
|
+
|
|
594
|
+
def p_union(p):
|
|
595
|
+
'''
|
|
596
|
+
union : UNION IDENTIFIER ON STRING '{' union_branches '}'
|
|
597
|
+
'''
|
|
598
|
+
|
|
599
|
+
union_name = p[2]
|
|
600
|
+
if typeExists(union_name) or currentBlueprint._find_template_decl(union_name) is not None:
|
|
601
|
+
raise SchemaViolation(f"Duplicated type '{union_name}'")
|
|
602
|
+
|
|
603
|
+
currentBlueprint.unions[union_name] = {
|
|
604
|
+
'discriminator': p[4],
|
|
605
|
+
'branches': p[6]
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def p_union_branches(p):
|
|
610
|
+
'''
|
|
611
|
+
union_branches : union_branches ',' union_branch
|
|
612
|
+
| union_branches ','
|
|
613
|
+
| union_branch
|
|
614
|
+
'''
|
|
615
|
+
|
|
616
|
+
if len(p) == 4:
|
|
617
|
+
p[1].update(p[3])
|
|
618
|
+
p[0] = p[1]
|
|
619
|
+
elif len(p) == 3:
|
|
620
|
+
p[0] = p[1]
|
|
621
|
+
else:
|
|
622
|
+
p[0] = p[1]
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def p_union_branch(p):
|
|
626
|
+
'''
|
|
627
|
+
union_branch : STRING ARROW branch_declaration
|
|
628
|
+
'''
|
|
629
|
+
|
|
630
|
+
p[0] = {p[1]: p[3]}
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def p_branch_declaration(p):
|
|
634
|
+
'''
|
|
635
|
+
branch_declaration : object_declaration
|
|
636
|
+
| IDENTIFIER
|
|
637
|
+
'''
|
|
638
|
+
|
|
639
|
+
if isinstance(p[1], dict):
|
|
640
|
+
adhoc_name = '_union_branch_' + str(getNextAdhoc()) + '_'
|
|
641
|
+
currentBlueprint.objects[adhoc_name] = p[1]
|
|
642
|
+
p[0] = adhoc_name
|
|
643
|
+
|
|
644
|
+
else:
|
|
645
|
+
type_name = p[1]
|
|
646
|
+
if currentBlueprint._find_object_decl(type_name) is None:
|
|
647
|
+
raise SchemaViolation(f"Object '{type_name}' is not defined")
|
|
648
|
+
p[0] = type_name
|
|
649
|
+
|
|
650
|
+
|
|
469
651
|
#---------------- enums -----------------------------
|
|
470
652
|
|
|
471
653
|
def p_enum(p):
|
|
@@ -492,6 +674,7 @@ def p_enum_declaration(p):
|
|
|
492
674
|
def p_constants(p):
|
|
493
675
|
'''
|
|
494
676
|
constants : constants ',' IDENTIFIER
|
|
677
|
+
| constants ','
|
|
495
678
|
| IDENTIFIER
|
|
496
679
|
'''
|
|
497
680
|
|
|
@@ -499,6 +682,9 @@ def p_constants(p):
|
|
|
499
682
|
p[1].append(p[3])
|
|
500
683
|
p[0] = p[1]
|
|
501
684
|
|
|
685
|
+
elif len(p) == 3:
|
|
686
|
+
p[0] = p[1]
|
|
687
|
+
|
|
502
688
|
else:
|
|
503
689
|
p[0] = [p[1]]
|
|
504
690
|
|
|
@@ -536,7 +722,7 @@ def _load(contents, contentPath, contentName, typeDirs):
|
|
|
536
722
|
parser = plyYacc.yacc()
|
|
537
723
|
|
|
538
724
|
ownPath = os.path.dirname(os.path.realpath(__file__))
|
|
539
|
-
primitivesPath = os.path.join(ownPath, "
|
|
725
|
+
primitivesPath = os.path.join(ownPath, "basetypes")
|
|
540
726
|
loaded, notLoaded = load_types(primitivesPath)
|
|
541
727
|
|
|
542
728
|
primitive_types = dict()
|
|
@@ -589,9 +775,9 @@ def load_file(filepath, **kwargs):
|
|
|
589
775
|
|
|
590
776
|
'''Loads a :class:`JsonBlueprint` from a file.
|
|
591
777
|
|
|
592
|
-
On success, the
|
|
593
|
-
absolute path of the argument file, and subsequent calls
|
|
594
|
-
function
|
|
778
|
+
On success, the returned instance is associated with the
|
|
779
|
+
absolute path of the argument file, and subsequent calls to this
|
|
780
|
+
function with the same file path will return the same cached instance,
|
|
595
781
|
unless :func:`invalidate_cache` is invoked.
|
|
596
782
|
|
|
597
783
|
Args:
|
|
@@ -651,7 +837,7 @@ def invalidate_cache():
|
|
|
651
837
|
'''Clears associations between files and existing JsonBlueprint instances.
|
|
652
838
|
|
|
653
839
|
This forces new :func:`load_file` invocations to effectively parse the files instead
|
|
654
|
-
of returning a cached result (
|
|
840
|
+
of returning a cached result (useful when the schema file has changed and needs
|
|
655
841
|
to be refreshed).
|
|
656
842
|
|
|
657
843
|
'''
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: jsonbp
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: Schema based JSON Parser/Serializer
|
|
5
5
|
Author-email: Gustavo Venturini <gustavo.c.venturini@gmail.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -11,6 +11,7 @@ Classifier: Development Status :: 5 - Production/Stable
|
|
|
11
11
|
Classifier: Intended Audience :: Developers
|
|
12
12
|
Classifier: Operating System :: OS Independent
|
|
13
13
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
14
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
14
15
|
Requires-Python: >=3.11
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
License-File: LICENSE.txt
|
|
@@ -27,7 +28,7 @@ to and from Python based on schemas. While [json-schema][json_schema] and its
|
|
|
27
28
|
implementations offer a more mature and widely used technique, a different
|
|
28
29
|
approach was desired, which led to the development of this library.
|
|
29
30
|
|
|
30
|
-
jsonbp's design
|
|
31
|
+
jsonbp's main design goals were:
|
|
31
32
|
- schema reuse through import / type system
|
|
32
33
|
- custom user definable primitive types
|
|
33
34
|
- built in numeric fixed precision type which deserializes into Python's Decimal
|
|
@@ -16,21 +16,25 @@ jsonbp.egg-info/PKG-INFO
|
|
|
16
16
|
jsonbp.egg-info/SOURCES.txt
|
|
17
17
|
jsonbp.egg-info/dependency_links.txt
|
|
18
18
|
jsonbp.egg-info/top_level.txt
|
|
19
|
+
jsonbp/basetypes/__init__.py
|
|
20
|
+
jsonbp/basetypes/bool.py
|
|
21
|
+
jsonbp/basetypes/date.py
|
|
22
|
+
jsonbp/basetypes/decimal.py
|
|
23
|
+
jsonbp/basetypes/float.py
|
|
24
|
+
jsonbp/basetypes/instant.py
|
|
25
|
+
jsonbp/basetypes/integer.py
|
|
26
|
+
jsonbp/basetypes/limits.py
|
|
27
|
+
jsonbp/basetypes/string.py
|
|
19
28
|
jsonbp/localization/__init__.py
|
|
29
|
+
jsonbp/localization/de_DE.ini
|
|
20
30
|
jsonbp/localization/en_US.ini
|
|
31
|
+
jsonbp/localization/fr_FR.ini
|
|
21
32
|
jsonbp/localization/pt_BR.ini
|
|
33
|
+
jsonbp/localization/ru_RU.ini
|
|
34
|
+
jsonbp/localization/zh_CN.ini
|
|
22
35
|
jsonbp/ply/__init__.py
|
|
23
36
|
jsonbp/ply/lex.py
|
|
24
37
|
jsonbp/ply/yacc.py
|
|
25
|
-
jsonbp/types/__init__.py
|
|
26
|
-
jsonbp/types/bool.py
|
|
27
|
-
jsonbp/types/date.py
|
|
28
|
-
jsonbp/types/decimal.py
|
|
29
|
-
jsonbp/types/float.py
|
|
30
|
-
jsonbp/types/instant.py
|
|
31
|
-
jsonbp/types/integer.py
|
|
32
|
-
jsonbp/types/limits.py
|
|
33
|
-
jsonbp/types/string.py
|
|
34
38
|
tests/test_caching.py
|
|
35
39
|
tests/test_choose_root.py
|
|
36
40
|
tests/test_deserialization.py
|
|
@@ -4,11 +4,16 @@ requires = ["setuptools"]
|
|
|
4
4
|
build-backend = "setuptools.build_meta"
|
|
5
5
|
|
|
6
6
|
[tool.setuptools]
|
|
7
|
-
packages = [
|
|
7
|
+
packages = [
|
|
8
|
+
"jsonbp",
|
|
9
|
+
"jsonbp.basetypes",
|
|
10
|
+
"jsonbp.localization",
|
|
11
|
+
"jsonbp.ply"
|
|
12
|
+
]
|
|
8
13
|
|
|
9
14
|
[project]
|
|
10
15
|
name = "jsonbp"
|
|
11
|
-
version = "1.
|
|
16
|
+
version = "1.3.0"
|
|
12
17
|
description = "Schema based JSON Parser/Serializer"
|
|
13
18
|
authors = [
|
|
14
19
|
{ name = "Gustavo Venturini", email = "gustavo.c.venturini@gmail.com" }
|
|
@@ -22,6 +27,7 @@ classifiers = [
|
|
|
22
27
|
"Intended Audience :: Developers",
|
|
23
28
|
"Operating System :: OS Independent",
|
|
24
29
|
"Programming Language :: Python :: 3 :: Only",
|
|
30
|
+
"Topic :: Software Development :: Libraries :: Python Modules"
|
|
25
31
|
]
|
|
26
32
|
|
|
27
33
|
[project.urls]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|