tallipoika 2024.1.3__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Stefan Hagen
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.
@@ -0,0 +1,2 @@
1
+ include LICENSE
2
+ include README.md
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.1
2
+ Name: tallipoika
3
+ Version: 2024.1.3
4
+ Summary: Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation.
5
+ Author-email: Stefan Hagen <stefan@hagen.link>
6
+ Maintainer-email: Stefan Hagen <stefan@hagen.link>
7
+ Project-URL: Homepage, https://git.sr.ht/~sthagen/tallipoika
8
+ Project-URL: Bug-Tracker, https://todo.sr.ht/~sthagen/tallipoika
9
+ Project-URL: Documentation, https://codes.dilettant.life/docs/tallipoika
10
+ Project-URL: Source-Code, https://git.sr.ht/~sthagen/tallipoika
11
+ Project-URL: Test-Coverage, https://codes.dilettant.life/coverage/tallipoika
12
+ Keywords: developer-tools,devops
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: typer
24
+ Provides-Extra: dev
25
+ Requires-Dist: black; extra == "dev"
26
+ Requires-Dist: coverage; extra == "dev"
27
+ Requires-Dist: hypothesis; extra == "dev"
28
+ Requires-Dist: mypy; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: pytest-cov; extra == "dev"
31
+ Requires-Dist: pytest-flake8; extra == "dev"
32
+ Requires-Dist: ruff; extra == "dev"
33
+
34
+ # Tallipoika
35
+
36
+ Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation.
37
+
38
+ [![License](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/license-spdx-mit.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/LICENSE)
39
+ [![Country of Origin](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/country-of-origin-name-switzerland-neutral.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/COUNTRY-OF-ORIGIN)
40
+ [![Export Classification Control Number (ECCN)](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/export-control-classification-number_eccn-ear99-neutral.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/EXPORT-CONTROL-CLASSIFICATION-NUMBER)
41
+ [![Configuration](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/configuration-sbom.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/docs/third-party/README.md)
42
+
43
+ [![Version](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/latest-release.svg)](https://pypi.python.org/pypi/tallipoika/)
44
+ [![Downloads](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/downloads-per-month.svg)](https://pepy.tech/project/tallipoika)
45
+ [![Python](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/python-versions.svg)](https://pypi.python.org/pypi/tallipoika/)
46
+ [![Maintenance Status](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/commits-per-year.svg)](https://git.sr.ht/~sthagen/tallipoika/log)
47
+
48
+ ## Documentation
49
+
50
+ User and developer [documentation of tallipoika](https://codes.dilettant.life/docs/tallipoika).
51
+
52
+ ## Bug Tracker
53
+
54
+ Any feature requests or bug reports shall go to the [todos of tallipoika](https://todo.sr.ht/~sthagen/tallipoika).
55
+
56
+ ## Primary Source repository
57
+
58
+ The main source of `tallipoika` is on a mountain in central Switzerland.
59
+ We use distributed version control (git).
60
+ There is no central hub.
61
+ Every clone can become a new source for the benefit of all.
62
+ The preferred public clone of `tallipoika` is:
63
+
64
+ * [at sourcehut](https://git.sr.ht/~sthagen/tallipoika) - a collection of tools useful for software development.
65
+
66
+ ## Contributions
67
+
68
+ Please do not submit "pull requests" (I found no way to disable that "feature" on GitHub).
69
+ If you like to share small changes under the repositories license please kindly do so by sending a patchset.
70
+ You can either send such a patchset per email using [git send-email](https://git-send-email.io) or
71
+ if you are a sourcehut user by selecting "Prepare a patchset" on the summary page of your fork at [sourcehut](https://git.sr.ht/).
72
+
73
+ ## Support
74
+
75
+ Please kindly submit issues at https://todo.sr.ht/~sthagen/tallipoika or write plain text email to ~sthagen/tallipoika@lists.sr.ht to submit patches and request support. Thanks.
76
+
77
+ ## Thanks also to
78
+
79
+ This implementation is based on the [original implementation of the JSON Canonicalization Scheme (JCS)](https://github.com/cyberphone/json-canonicalization) gently provided under the APACHE-2.0 license by Anders Rundgren.
80
+ The initial implementation of `tallipoika` is kept similar to show the identical functioning of both implementation.
81
+ Also, the input and output JSON tests are provided within this repository as a service for validators.
82
+
83
+ # Status
84
+
85
+ Experimental.
86
+
87
+ **Note**: The default branch is `default`.
@@ -0,0 +1,54 @@
1
+ # Tallipoika
2
+
3
+ Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation.
4
+
5
+ [![License](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/license-spdx-mit.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/LICENSE)
6
+ [![Country of Origin](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/country-of-origin-name-switzerland-neutral.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/COUNTRY-OF-ORIGIN)
7
+ [![Export Classification Control Number (ECCN)](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/export-control-classification-number_eccn-ear99-neutral.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/EXPORT-CONTROL-CLASSIFICATION-NUMBER)
8
+ [![Configuration](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/configuration-sbom.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/docs/third-party/README.md)
9
+
10
+ [![Version](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/latest-release.svg)](https://pypi.python.org/pypi/tallipoika/)
11
+ [![Downloads](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/downloads-per-month.svg)](https://pepy.tech/project/tallipoika)
12
+ [![Python](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/python-versions.svg)](https://pypi.python.org/pypi/tallipoika/)
13
+ [![Maintenance Status](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/commits-per-year.svg)](https://git.sr.ht/~sthagen/tallipoika/log)
14
+
15
+ ## Documentation
16
+
17
+ User and developer [documentation of tallipoika](https://codes.dilettant.life/docs/tallipoika).
18
+
19
+ ## Bug Tracker
20
+
21
+ Any feature requests or bug reports shall go to the [todos of tallipoika](https://todo.sr.ht/~sthagen/tallipoika).
22
+
23
+ ## Primary Source repository
24
+
25
+ The main source of `tallipoika` is on a mountain in central Switzerland.
26
+ We use distributed version control (git).
27
+ There is no central hub.
28
+ Every clone can become a new source for the benefit of all.
29
+ The preferred public clone of `tallipoika` is:
30
+
31
+ * [at sourcehut](https://git.sr.ht/~sthagen/tallipoika) - a collection of tools useful for software development.
32
+
33
+ ## Contributions
34
+
35
+ Please do not submit "pull requests" (I found no way to disable that "feature" on GitHub).
36
+ If you like to share small changes under the repositories license please kindly do so by sending a patchset.
37
+ You can either send such a patchset per email using [git send-email](https://git-send-email.io) or
38
+ if you are a sourcehut user by selecting "Prepare a patchset" on the summary page of your fork at [sourcehut](https://git.sr.ht/).
39
+
40
+ ## Support
41
+
42
+ Please kindly submit issues at https://todo.sr.ht/~sthagen/tallipoika or write plain text email to ~sthagen/tallipoika@lists.sr.ht to submit patches and request support. Thanks.
43
+
44
+ ## Thanks also to
45
+
46
+ This implementation is based on the [original implementation of the JSON Canonicalization Scheme (JCS)](https://github.com/cyberphone/json-canonicalization) gently provided under the APACHE-2.0 license by Anders Rundgren.
47
+ The initial implementation of `tallipoika` is kept similar to show the identical functioning of both implementation.
48
+ Also, the input and output JSON tests are provided within this repository as a service for validators.
49
+
50
+ # Status
51
+
52
+ Experimental.
53
+
54
+ **Note**: The default branch is `default`.
@@ -0,0 +1,116 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "tallipoika"
7
+ version = "2024.1.3"
8
+ description = "Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation."
9
+ readme = "README.md"
10
+ authors = [{ name = "Stefan Hagen", email = "stefan@hagen.link" }]
11
+ maintainers = [{ name = "Stefan Hagen", email = "stefan@hagen.link" }]
12
+ classifiers = [
13
+ "Development Status :: 3 - Alpha",
14
+ "Intended Audience :: Developers",
15
+ "License :: OSI Approved :: MIT License",
16
+ "Programming Language :: Python :: 3.10",
17
+ "Programming Language :: Python :: 3.11",
18
+ "Programming Language :: Python :: 3.12",
19
+ "Programming Language :: Python :: 3.9",
20
+ ]
21
+ keywords = ["developer-tools", "devops"]
22
+ dependencies = [
23
+ "typer",
24
+ ]
25
+ requires-python = ">=3.9"
26
+
27
+ [project.optional-dependencies]
28
+ dev = ["black", "coverage", "hypothesis", "mypy", "pytest", "pytest-cov", "pytest-flake8", "ruff"]
29
+
30
+ [project.urls]
31
+ Homepage = "https://git.sr.ht/~sthagen/tallipoika"
32
+ Bug-Tracker = "https://todo.sr.ht/~sthagen/tallipoika"
33
+ Documentation = "https://codes.dilettant.life/docs/tallipoika"
34
+ Source-Code = "https://git.sr.ht/~sthagen/tallipoika"
35
+ Test-Coverage = "https://codes.dilettant.life/coverage/tallipoika"
36
+
37
+ [project.scripts]
38
+ tallipoika = "tallipoika.cli:app"
39
+
40
+ [tool.setuptools.packages.find]
41
+ include = ["tallipoika"]
42
+ exclude = ["test*"]
43
+
44
+ [tool.black]
45
+ line-length = 120
46
+ skip-string-normalization = true
47
+ target-version = ["py39", "py310", "py311", "py312"]
48
+
49
+ [tool.coverage.run]
50
+ branch = true
51
+
52
+ [tool.coverage.report]
53
+ precision = 2
54
+ exclude_lines = [
55
+ "pragma: no cover",
56
+ "raise NotImplementedError",
57
+ "if TYPE_CHECKING:",
58
+ "@overload",
59
+ ]
60
+ omit = [
61
+ "*/__main__.py",
62
+ ]
63
+
64
+ [tool.ruff]
65
+ line-length = 120
66
+ select = ["E", "F", "Q"]
67
+ ignore = []
68
+ exclude = [
69
+ ".bzr",
70
+ ".direnv",
71
+ ".eggs",
72
+ ".git",
73
+ ".hg",
74
+ ".mypy_cache",
75
+ ".nox",
76
+ ".pants.d",
77
+ ".ruff_cache",
78
+ ".svn",
79
+ ".tox",
80
+ ".venv",
81
+ "__pypackages__",
82
+ "_build",
83
+ "buck-out",
84
+ "build",
85
+ "dist",
86
+ "node_modules",
87
+ "venv",
88
+ ]
89
+ dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
90
+ target-version = "py310"
91
+ unfixable = ["F401"]
92
+
93
+ [tool.ruff.flake8-quotes]
94
+ docstring-quotes = "double"
95
+ inline-quotes = "single"
96
+
97
+ [tool.ruff.per-file-ignores]
98
+ "__init__.py" = ["E402"]
99
+
100
+ [tool.ruff.flake8-import-conventions.aliases]
101
+ altair = "alt"
102
+ "matplotlib.pyplot" = "plt"
103
+ numpy = "np"
104
+ pandas = "pd"
105
+ seaborn = "sns"
106
+
107
+ [tool.ruff.mccabe]
108
+ max-complexity = 42 # default is 10
109
+
110
+ [tool.mypy]
111
+ strict = true
112
+ implicit_reexport = true
113
+
114
+ [tool.pytest]
115
+ testpaths = "test"
116
+ filterwarnings = "error"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,78 @@
1
+ """Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation."""
2
+ import datetime as dti
3
+ import logging
4
+ import os
5
+ import pathlib
6
+ from typing import List, no_type_check
7
+
8
+ # [[[fill git_describe()]]]
9
+ __version__ = '2024.1.3+parent.2e105da5'
10
+ # [[[end]]] (checksum: 74ad8c59fd85ec37e5170a11f7e6a7a4)
11
+ __version_info__ = tuple(
12
+ e if '-' not in e else e.split('-')[0] for part in __version__.split('+') for e in part.split('.') if e != 'parent'
13
+ )
14
+
15
+ APP_ALIAS = str(pathlib.Path(__file__).parent.name)
16
+ APP_ENV = APP_ALIAS.upper()
17
+ APP_NAME = locals()['__doc__']
18
+ DEBUG = bool(os.getenv(f'{APP_ENV}_DEBUG', ''))
19
+ VERBOSE = bool(os.getenv(f'{APP_ENV}_VERBOSE', ''))
20
+ QUIET = False
21
+ STRICT = bool(os.getenv(f'{APP_ENV}_STRICT', ''))
22
+ ENCODING = 'utf-8'
23
+ ENCODING_ERRORS_POLICY = 'ignore'
24
+ DEFAULT_CONFIG_NAME = f'.{APP_ALIAS}.json'
25
+ DEFAULT_LF_ONLY = 'YES'
26
+ DEFAULT_STRUCTURE_NAME = 'structure.yml'
27
+ DEFAULT_STRUCTURES_NAME = 'structures.yml'
28
+ log = logging.getLogger() # Module level logger is sufficient
29
+ LOG_FOLDER = pathlib.Path('logs')
30
+ LOG_FILE = f'{APP_ALIAS}.log'
31
+ LOG_PATH = pathlib.Path(LOG_FOLDER, LOG_FILE) if LOG_FOLDER.is_dir() else pathlib.Path(LOG_FILE)
32
+ LOG_LEVEL = logging.INFO
33
+ VERSION = __version__
34
+ VERSION_DOTTED_TRIPLE = '.'.join(__version_info__[:3])
35
+ TS_FORMAT_LOG = '%Y-%m-%dT%H:%M:%S'
36
+ TS_FORMAT_PAYLOADS = '%Y-%m-%d %H:%M:%S.%f UTC'
37
+ TS_FORMAT_GENERATOR = '%Y-%m-%d %H:%M:%S.%f +00:00'
38
+
39
+ __all__: List[str] = [
40
+ 'APP_ALIAS',
41
+ 'APP_ENV',
42
+ 'APP_NAME',
43
+ 'DEBUG',
44
+ 'DEFAULT_CONFIG_NAME',
45
+ 'DEFAULT_STRUCTURE_NAME',
46
+ 'DEFAULT_STRUCTURES_NAME',
47
+ 'ENCODING',
48
+ 'TS_FORMAT_GENERATOR',
49
+ 'VERSION',
50
+ 'VERSION_DOTTED_TRIPLE',
51
+ 'log',
52
+ ]
53
+
54
+
55
+ @no_type_check
56
+ def formatTime_RFC3339(self, record, datefmt=None): # noqa
57
+ """HACK A DID ACK we could inject .astimezone() to localize ..."""
58
+ return dti.datetime.fromtimestamp(record.created, dti.timezone.utc).isoformat() # pragma: no cover
59
+
60
+
61
+ @no_type_check
62
+ def init_logger(name=None, level=None):
63
+ """Initialize module level logger"""
64
+ global log # pylint: disable=global-statement
65
+
66
+ log_format = {
67
+ 'format': '%(asctime)s %(levelname)s [%(name)s]: %(message)s',
68
+ 'datefmt': TS_FORMAT_LOG,
69
+ # 'filename': LOG_PATH,
70
+ 'level': LOG_LEVEL if level is None else level,
71
+ }
72
+ logging.Formatter.formatTime = formatTime_RFC3339
73
+ logging.basicConfig(**log_format)
74
+ log = logging.getLogger(APP_ENV if name is None else name)
75
+ log.propagate = True
76
+
77
+
78
+ init_logger(name=APP_ENV, level=logging.DEBUG if DEBUG else None)
@@ -0,0 +1,473 @@
1
+ """JSON Canonicalization Scheme (JCS) serializer."""
2
+ import re
3
+ from typing import no_type_check
4
+
5
+ import tallipoika.py2es6 as py2es6
6
+
7
+ try:
8
+ from _json import encode_basestring_ascii as c_encode_basestring_ascii
9
+ except ImportError:
10
+ c_encode_basestring_ascii = None # type: ignore
11
+ try:
12
+ from _json import encode_basestring as c_encode_basestring # type: ignore
13
+ except ImportError:
14
+ c_encode_basestring = None
15
+ try:
16
+ from _json import make_encoder as c_make_encoder
17
+ except ImportError:
18
+ c_make_encoder = None # type: ignore
19
+
20
+ COLON = ':'
21
+ COMMA = ','
22
+ SPACE = ' '
23
+ OPEN_SB = '['
24
+ CLOSE_SB = ']'
25
+ EMPTY_ARRAY_REP = f'{OPEN_SB}{CLOSE_SB}'
26
+ OPEN_CB = '{'
27
+ CLOSE_CB = '}'
28
+ EMPTY_OBJECT_REP = f'{OPEN_CB}{CLOSE_CB}'
29
+ NL = '\n'
30
+
31
+ JSON_FALSE_REP = 'false'
32
+ JSON_NULL_REP = 'null'
33
+ JSON_TRUE_REP = 'true'
34
+
35
+ ESCAPE = re.compile(r'[\x00-\x1f\\"\b\f\n\r\t]')
36
+ ESCAPE_ASCII = re.compile(r'([\\"]|[^\ -~])')
37
+ HAS_UTF8 = re.compile(b'[\x80-\xff]')
38
+ ESCAPE_DCT = {
39
+ '\\': '\\\\',
40
+ '"': '\\"',
41
+ '\b': '\\b',
42
+ '\f': '\\f',
43
+ '\n': '\\n',
44
+ '\r': '\\r',
45
+ '\t': '\\t',
46
+ }
47
+ for i in range(0x20):
48
+ ESCAPE_DCT.setdefault(chr(i), '\\u{0:04x}'.format(i))
49
+
50
+ INFINITY = float('inf')
51
+
52
+
53
+ @no_type_check
54
+ def py_encode_basestring(s):
55
+ """Return a JSON representation of a Python string."""
56
+
57
+ def replace(match):
58
+ return ESCAPE_DCT[match.group(0)]
59
+
60
+ return f'"{ESCAPE.sub(replace, s)}"'
61
+
62
+
63
+ encode_basestring = c_encode_basestring or py_encode_basestring
64
+
65
+
66
+ @no_type_check
67
+ def py_encode_basestring_ascii(s):
68
+ """Return an ASCII-only JSON representation of a Python string."""
69
+
70
+ def replace(match):
71
+ s = match.group(0)
72
+ try:
73
+ return ESCAPE_DCT[s]
74
+ except KeyError:
75
+ n = ord(s)
76
+ if n < 0x10000:
77
+ return '\\u{0:04x}'.format(n)
78
+ else:
79
+ # surrogate pair
80
+ n -= 0x10000
81
+ s1 = 0xD800 | ((n >> 10) & 0x3FF)
82
+ s2 = 0xDC00 | (n & 0x3FF)
83
+ return '\\u{0:04x}\\u{1:04x}'.format(s1, s2)
84
+
85
+ return f'"{ESCAPE_ASCII.sub(replace, s)}"'
86
+
87
+
88
+ encode_basestring_ascii = c_encode_basestring_ascii or py_encode_basestring_ascii # type: ignore
89
+
90
+
91
+ @no_type_check
92
+ class JSONEncoder(object):
93
+ """JSON encoder for typical Python data structures.
94
+
95
+ Encodes the following nine Python types as the following 7 JSON types:
96
+
97
+ - Python `dict` -> JSON object
98
+ - Python `list` and `tuple` -> JSON array
99
+ - Python `str` -> JSON string
100
+ - Python `float` and `int` -> JSON number
101
+ - Python `True` ->JSON true
102
+ - Python `False` -> JSON false
103
+ - Python `None` -> JSON null
104
+
105
+ To extend recognition to other objects, subclass and implement a `.default()` method that returns a serializable
106
+ object for `o` if possible, otherwise it should call the superclass implementation (to raise `TypeError`).
107
+ """
108
+
109
+ item_separator = f'{COMMA}{SPACE}'
110
+ key_separator = f'{COLON}{SPACE}'
111
+
112
+ @no_type_check
113
+ def __init__(
114
+ self,
115
+ *,
116
+ skipkeys=False,
117
+ ensure_ascii=False,
118
+ check_circular=True,
119
+ allow_nan=True,
120
+ sort_keys=True,
121
+ indent=None,
122
+ separators=(COMMA, COLON),
123
+ default=None,
124
+ ):
125
+ """Constructor for JSONEncoder, with sensible defaults.
126
+
127
+ If skipkeys is false, then it is a TypeError to attempt encoding of keys that are not str, int, float or None.
128
+ If skipkeys is True, such items are simply skipped.
129
+
130
+ If ensure_ascii is true, the output is guaranteed to be str objects with all incoming non-ASCII characters
131
+ escaped. If ensure_ascii is false, the output can contain non-ASCII characters.
132
+
133
+ If check_circular is true, then lists, dicts, and custom encoded objects will be checked for circular references
134
+ during encoding to prevent an infinite recursion (which would cause an OverflowError). Otherwise, no such check
135
+ takes place.
136
+
137
+ If allow_nan is true, then NaN, Infinity, and -Infinity will be encoded as such. This behavior is not JSON
138
+ specification compliant, but is consistent with most JavaScript based encoders and decoders. Otherwise, it will
139
+ be a ValueError to encode such floats.
140
+
141
+ If sort_keys is true, then the output of dictionaries will be sorted by key; this is useful for regression tests
142
+ to ensure that JSON serializations can be compared on a day-to-day basis.
143
+
144
+ If indent is a non-negative integer, then JSON array elements and object members will be pretty-printed with
145
+ that indent level. An indent level of 0 will only insert newlines. None is the most compact representation.
146
+
147
+ If specified, separators should be an (item_separator, key_separator) tuple. The default is
148
+ (', ', ': ') if *indent* is ``None`` and(',', ': ') otherwise. To get the most compact JSON representation,
149
+ you should specify (',', ':') to eliminate whitespace.
150
+
151
+ If specified, default is a function that gets called for objects that can't otherwise be serialized. It should
152
+ return a JSON encodable version of the object or raise a ``TypeError``.
153
+
154
+ """
155
+
156
+ self.skipkeys = skipkeys
157
+ self.ensure_ascii = ensure_ascii
158
+ self.check_circular = check_circular
159
+ self.allow_nan = allow_nan
160
+ self.sort_keys = sort_keys
161
+ self.indent = indent
162
+ if separators is not None:
163
+ self.item_separator, self.key_separator = separators
164
+ elif indent is not None:
165
+ self.item_separator = COMMA
166
+ if default is not None:
167
+ self.default = default
168
+
169
+ @no_type_check
170
+ def default(self, o):
171
+ """Implement this method in a subclass such that it returns a serializable object for o.
172
+
173
+ For example, to support arbitrary iterators, you could implement default like this::
174
+
175
+ def default(self, o):
176
+ try:
177
+ iterable = iter(o)
178
+ except TypeError:
179
+ pass
180
+ else:
181
+ return list(iterable)
182
+ return JSONEncoder.default(self, o) # Let the base class default method raise the TypeError
183
+
184
+ """
185
+ raise TypeError(f"Object of type '{o.__class__.__name__}' is not JSON serializable")
186
+
187
+ @no_type_check
188
+ def encode(self, o):
189
+ """Return a JSON string representation of a Python data structure.
190
+
191
+ >>> from json.encoder import JSONEncoder
192
+ >>> JSONEncoder().encode({"foo": ["bar", "baz"]})
193
+ '{"foo": ["bar", "baz"]}'
194
+
195
+ """
196
+ # This is for extremely simple cases and benchmarks.
197
+ if isinstance(o, str):
198
+ if self.ensure_ascii:
199
+ return encode_basestring_ascii(o)
200
+ else:
201
+ return encode_basestring(o)
202
+ # This doesn't pass the iterator directly to ''.join() because the exceptions aren't as detailed.
203
+ # The list call should be roughly equivalent to the PySequence_Fast that ''.join() would do.
204
+ chunks = self.iterencode(o, _one_shot=False)
205
+ if not isinstance(chunks, (list, tuple)):
206
+ chunks = list(chunks)
207
+ return ''.join(chunks)
208
+
209
+ @no_type_check
210
+ def iterencode(self, o, _one_shot=False):
211
+ """Encode the given object and yield each string representation as available.
212
+
213
+ For example::
214
+
215
+ for chunk in JSONEncoder().iterencode(bigobject):
216
+ mysocket.write(chunk)
217
+
218
+ """
219
+ if self.check_circular:
220
+ markers = {}
221
+ else:
222
+ markers = None
223
+ if self.ensure_ascii:
224
+ _encoder = encode_basestring_ascii
225
+ else:
226
+ _encoder = encode_basestring
227
+
228
+ def floatstr(o, allow_nan=self.allow_nan, _repr=float.__repr__, _inf=INFINITY, _neginf=-INFINITY):
229
+ """Check for specials.
230
+
231
+ Note that this type of test is processor and platform-specific, so tests shall not rely on the internals.
232
+ """
233
+
234
+ if o != o:
235
+ text = 'NaN'
236
+ elif o == _inf:
237
+ text = 'Infinity'
238
+ elif o == _neginf:
239
+ text = '-Infinity'
240
+ else:
241
+ return _repr(o)
242
+
243
+ if not allow_nan:
244
+ raise ValueError(f'out of range float value {repr(o)} is not JSON compliant')
245
+
246
+ return text
247
+
248
+ if _one_shot and c_make_encoder is not None and self.indent is None:
249
+ _iterencode = c_make_encoder(
250
+ markers,
251
+ self.default,
252
+ _encoder,
253
+ self.indent,
254
+ self.key_separator,
255
+ self.item_separator,
256
+ self.sort_keys,
257
+ self.skipkeys,
258
+ self.allow_nan,
259
+ )
260
+ else:
261
+ _iterencode = _make_iterencode(
262
+ markers,
263
+ self.default,
264
+ _encoder,
265
+ self.indent,
266
+ floatstr,
267
+ self.key_separator,
268
+ self.item_separator,
269
+ self.sort_keys,
270
+ self.skipkeys,
271
+ _one_shot,
272
+ )
273
+ return _iterencode(o, 0)
274
+
275
+
276
+ @no_type_check
277
+ def _make_iterencode(
278
+ markers,
279
+ _default,
280
+ _encoder,
281
+ _indent,
282
+ _floatstr,
283
+ _key_separator,
284
+ _item_separator,
285
+ _sort_keys,
286
+ _skipkeys,
287
+ _one_shot,
288
+ # HACK: hand-optimized bytecode; turn globals into locals
289
+ ValueError=ValueError,
290
+ dict=dict,
291
+ float=float,
292
+ id=id,
293
+ int=int,
294
+ isinstance=isinstance,
295
+ list=list,
296
+ str=str,
297
+ tuple=tuple,
298
+ _intstr=int.__str__,
299
+ ):
300
+ if _indent is not None and not isinstance(_indent, str):
301
+ _indent = SPACE * _indent
302
+
303
+ @no_type_check
304
+ def _iterencode_list(lst, _current_indent_level):
305
+ if not lst:
306
+ yield EMPTY_ARRAY_REP
307
+ return
308
+ if markers is not None:
309
+ markerid = id(lst)
310
+ if markerid in markers:
311
+ raise ValueError('circular reference detected for sequence')
312
+ markers[markerid] = lst
313
+ buf = OPEN_SB
314
+ if _indent is not None:
315
+ _current_indent_level += 1
316
+ newline_indent = NL + _indent * _current_indent_level
317
+ separator = _item_separator + newline_indent
318
+ buf += newline_indent
319
+ else:
320
+ newline_indent = None
321
+ separator = _item_separator
322
+ first = True
323
+ for value in lst:
324
+ if first:
325
+ first = False
326
+ else:
327
+ buf = separator
328
+ if isinstance(value, str):
329
+ yield buf + _encoder(value)
330
+ elif value is None:
331
+ yield buf + JSON_NULL_REP
332
+ elif value is True:
333
+ yield buf + JSON_TRUE_REP
334
+ elif value is False:
335
+ yield buf + JSON_FALSE_REP
336
+ elif isinstance(value, (float, int)):
337
+ # Subclasses of float and int may override __str__, but should still serialize as numbers in JSON.
338
+ # One example within the standard library is IntEnum.
339
+ yield buf + py2es6.serialize(value)
340
+ else:
341
+ yield buf
342
+ if isinstance(value, (list, tuple)):
343
+ chunks = _iterencode_list(value, _current_indent_level)
344
+ elif isinstance(value, dict):
345
+ chunks = _iterencode_dict(value, _current_indent_level)
346
+ else:
347
+ chunks = _iterencode(value, _current_indent_level)
348
+ yield from chunks
349
+ if newline_indent is not None:
350
+ _current_indent_level -= 1
351
+ yield NL + _indent * _current_indent_level
352
+ yield CLOSE_SB
353
+ if markers is not None:
354
+ del markers[markerid]
355
+
356
+ @no_type_check
357
+ def _iterencode_dict(dct, _current_indent_level):
358
+ if not dct:
359
+ yield EMPTY_OBJECT_REP
360
+ return
361
+ if markers is not None:
362
+ markerid = id(dct)
363
+ if markerid in markers:
364
+ raise ValueError('circular reference detected for dict')
365
+ markers[markerid] = dct
366
+ yield OPEN_CB
367
+ if _indent is not None:
368
+ _current_indent_level += 1
369
+ newline_indent = '\n' + _indent * _current_indent_level
370
+ item_separator = _item_separator + newline_indent
371
+ yield newline_indent
372
+ else:
373
+ newline_indent = None
374
+ item_separator = _item_separator
375
+ first = True
376
+ if _sort_keys:
377
+ items = sorted(dct.items(), key=lambda kv: kv[0].encode('utf-16_be'))
378
+ else:
379
+ items = dct.items()
380
+ for key, value in items:
381
+ if isinstance(key, str):
382
+ pass
383
+ # JavaScript is weakly typed for these, so it makes sense to
384
+ # also allow them. Many encoders seem to do something like this.
385
+ elif isinstance(key, (float, int)):
386
+ # see comment for float/int in _make_iterencode
387
+ key = py2es6.serialize(key)
388
+ elif key is True:
389
+ key = 'true'
390
+ elif key is False:
391
+ key = 'false'
392
+ elif key is None:
393
+ key = 'null'
394
+ elif _skipkeys:
395
+ continue
396
+ else:
397
+ raise TypeError(f'key {repr(key)} is not a string')
398
+ if first:
399
+ first = False
400
+ else:
401
+ yield item_separator
402
+ yield _encoder(key)
403
+ yield _key_separator
404
+ if isinstance(value, str):
405
+ yield _encoder(value)
406
+ elif value is None:
407
+ yield JSON_NULL_REP
408
+ elif value is True:
409
+ yield JSON_TRUE_REP
410
+ elif value is False:
411
+ yield JSON_FALSE_REP
412
+ elif isinstance(value, (float, int)):
413
+ # see comment for int/float in _make_iterencode
414
+ yield py2es6.serialize(value)
415
+ else:
416
+ if isinstance(value, (list, tuple)):
417
+ chunks = _iterencode_list(value, _current_indent_level)
418
+ elif isinstance(value, dict):
419
+ chunks = _iterencode_dict(value, _current_indent_level)
420
+ else:
421
+ chunks = _iterencode(value, _current_indent_level)
422
+ yield from chunks
423
+ if newline_indent is not None:
424
+ _current_indent_level -= 1
425
+ yield NL + _indent * _current_indent_level
426
+ yield CLOSE_CB
427
+ if markers is not None:
428
+ del markers[markerid]
429
+
430
+ @no_type_check
431
+ def _iterencode(o, _current_indent_level):
432
+ if isinstance(o, str):
433
+ yield _encoder(o)
434
+ elif o is None:
435
+ yield JSON_NULL_REP
436
+ elif o is True:
437
+ yield JSON_TRUE_REP
438
+ elif o is False:
439
+ yield JSON_FALSE_REP
440
+ elif isinstance(o, (float, int)):
441
+ # see comment for float/int in _make_iterencode
442
+ yield py2es6.serialize(o)
443
+ elif isinstance(o, (list, tuple)):
444
+ yield from _iterencode_list(o, _current_indent_level)
445
+ elif isinstance(o, dict):
446
+ yield from _iterencode_dict(o, _current_indent_level)
447
+ else:
448
+ if markers is not None:
449
+ markerid = id(o)
450
+ if markerid in markers:
451
+ raise ValueError('circular reference detected')
452
+ markers[markerid] = o
453
+ o = _default(o)
454
+ yield from _iterencode(o, _current_indent_level)
455
+ if markers is not None:
456
+ del markers[markerid]
457
+
458
+ return _iterencode
459
+
460
+
461
+ @no_type_check
462
+ def ensure_encoding(text: str, utf8: bool) -> str:
463
+ return text.encode() if utf8 else text
464
+
465
+
466
+ @no_type_check
467
+ def canonicalize(obj, utf8=True):
468
+ return ensure_encoding(JSONEncoder(sort_keys=True).encode(obj), utf8=utf8)
469
+
470
+
471
+ @no_type_check
472
+ def serialize(obj, utf8=True):
473
+ return ensure_encoding(JSONEncoder(sort_keys=False).encode(obj), utf8=utf8)
@@ -0,0 +1,82 @@
1
+ """Serialize a Python numerical value as an ECMAScript v6 or later string."""
2
+ import math
3
+ from typing import Union, no_type_check
4
+
5
+ DASH = '-'
6
+ DECIMAL_POINT = '.'
7
+ DIGIT_ZERO = '0'
8
+ MIN_INTEGER_DIGITS = 0
9
+ MAX_INTEGER_DIGITS = 21
10
+ REPR_SWITCH_ABOVE = -7
11
+
12
+
13
+ @no_type_check
14
+ def serialize(number: Union[float, int]) -> str:
15
+ """Serialize a Python builtin number as an ECMAScript v6 or later string.
16
+
17
+ Implementation Note(s):
18
+
19
+ - (builtin) numbers are for now int and float but may be extended to other types
20
+ that can be mapped to JSON numbers
21
+ - separate into the components:
22
+ + sign
23
+ + integral part
24
+ + decimal point
25
+ + fractional part
26
+ + exponent part
27
+ - return the concatenation of the components (some components may be empty)
28
+
29
+ """
30
+ if not math.isfinite(number):
31
+ raise ValueError(f'invalid number ({number})')
32
+
33
+ as_float = float(number)
34
+
35
+ if as_float == 0:
36
+ return DIGIT_ZERO
37
+
38
+ as_text = str(as_float)
39
+
40
+ sign = DASH if as_text[0] == DASH else ''
41
+ magnitude = as_text[1:] if sign else as_text
42
+
43
+ mantissa = magnitude
44
+ exponent = ''
45
+ exponent_number = 0
46
+ if (fndx := magnitude.find('e')) > 0:
47
+ mantissa, exponent = magnitude[0:fndx], magnitude[fndx:]
48
+ if exponent[2:3] == '0': # Suppress leading zero on exponents
49
+ exponent = exponent[:2] + exponent[3:]
50
+ exponent_number = int(exponent[1:])
51
+
52
+ integral = mantissa
53
+ decimal_point = ''
54
+ fractional = ''
55
+ if (fndx := mantissa.find(DECIMAL_POINT)) > 0:
56
+ decimal_point = DECIMAL_POINT
57
+ integral, fractional = mantissa[:fndx], mantissa[fndx + 1 :]
58
+
59
+ if fractional == DIGIT_ZERO: # x.0 -> x
60
+ decimal_point = ''
61
+ fractional = ''
62
+
63
+ if MIN_INTEGER_DIGITS < exponent_number < MAX_INTEGER_DIGITS:
64
+ integral += fractional
65
+ fractional = ''
66
+ decimal_point = ''
67
+ exponent = ''
68
+ n = exponent_number - len(integral)
69
+ while n >= 0:
70
+ n -= 1
71
+ integral += DIGIT_ZERO
72
+ elif REPR_SWITCH_ABOVE < exponent_number < 0:
73
+ fractional = integral + fractional
74
+ integral = DIGIT_ZERO
75
+ decimal_point = DECIMAL_POINT
76
+ exponent = ''
77
+ n = exponent_number
78
+ while n < -1:
79
+ n += 1
80
+ fractional = DIGIT_ZERO + fractional
81
+
82
+ return f'{sign}{integral}{decimal_point}{fractional}{exponent}'
@@ -0,0 +1,87 @@
1
+ Metadata-Version: 2.1
2
+ Name: tallipoika
3
+ Version: 2024.1.3
4
+ Summary: Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation.
5
+ Author-email: Stefan Hagen <stefan@hagen.link>
6
+ Maintainer-email: Stefan Hagen <stefan@hagen.link>
7
+ Project-URL: Homepage, https://git.sr.ht/~sthagen/tallipoika
8
+ Project-URL: Bug-Tracker, https://todo.sr.ht/~sthagen/tallipoika
9
+ Project-URL: Documentation, https://codes.dilettant.life/docs/tallipoika
10
+ Project-URL: Source-Code, https://git.sr.ht/~sthagen/tallipoika
11
+ Project-URL: Test-Coverage, https://codes.dilettant.life/coverage/tallipoika
12
+ Keywords: developer-tools,devops
13
+ Classifier: Development Status :: 3 - Alpha
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Requires-Python: >=3.9
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: typer
24
+ Provides-Extra: dev
25
+ Requires-Dist: black; extra == "dev"
26
+ Requires-Dist: coverage; extra == "dev"
27
+ Requires-Dist: hypothesis; extra == "dev"
28
+ Requires-Dist: mypy; extra == "dev"
29
+ Requires-Dist: pytest; extra == "dev"
30
+ Requires-Dist: pytest-cov; extra == "dev"
31
+ Requires-Dist: pytest-flake8; extra == "dev"
32
+ Requires-Dist: ruff; extra == "dev"
33
+
34
+ # Tallipoika
35
+
36
+ Stableson (Finnish: tallipoika) - a JSON Canonicalization Scheme (JCS) implementation.
37
+
38
+ [![License](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/license-spdx-mit.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/LICENSE)
39
+ [![Country of Origin](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/country-of-origin-name-switzerland-neutral.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/COUNTRY-OF-ORIGIN)
40
+ [![Export Classification Control Number (ECCN)](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/export-control-classification-number_eccn-ear99-neutral.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/EXPORT-CONTROL-CLASSIFICATION-NUMBER)
41
+ [![Configuration](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/configuration-sbom.svg)](https://git.sr.ht/~sthagen/tallipoika/tree/default/item/docs/third-party/README.md)
42
+
43
+ [![Version](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/latest-release.svg)](https://pypi.python.org/pypi/tallipoika/)
44
+ [![Downloads](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/downloads-per-month.svg)](https://pepy.tech/project/tallipoika)
45
+ [![Python](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/python-versions.svg)](https://pypi.python.org/pypi/tallipoika/)
46
+ [![Maintenance Status](https://git.sr.ht/~sthagen/tallipoika/blob/default/docs/badges/commits-per-year.svg)](https://git.sr.ht/~sthagen/tallipoika/log)
47
+
48
+ ## Documentation
49
+
50
+ User and developer [documentation of tallipoika](https://codes.dilettant.life/docs/tallipoika).
51
+
52
+ ## Bug Tracker
53
+
54
+ Any feature requests or bug reports shall go to the [todos of tallipoika](https://todo.sr.ht/~sthagen/tallipoika).
55
+
56
+ ## Primary Source repository
57
+
58
+ The main source of `tallipoika` is on a mountain in central Switzerland.
59
+ We use distributed version control (git).
60
+ There is no central hub.
61
+ Every clone can become a new source for the benefit of all.
62
+ The preferred public clone of `tallipoika` is:
63
+
64
+ * [at sourcehut](https://git.sr.ht/~sthagen/tallipoika) - a collection of tools useful for software development.
65
+
66
+ ## Contributions
67
+
68
+ Please do not submit "pull requests" (I found no way to disable that "feature" on GitHub).
69
+ If you like to share small changes under the repositories license please kindly do so by sending a patchset.
70
+ You can either send such a patchset per email using [git send-email](https://git-send-email.io) or
71
+ if you are a sourcehut user by selecting "Prepare a patchset" on the summary page of your fork at [sourcehut](https://git.sr.ht/).
72
+
73
+ ## Support
74
+
75
+ Please kindly submit issues at https://todo.sr.ht/~sthagen/tallipoika or write plain text email to ~sthagen/tallipoika@lists.sr.ht to submit patches and request support. Thanks.
76
+
77
+ ## Thanks also to
78
+
79
+ This implementation is based on the [original implementation of the JSON Canonicalization Scheme (JCS)](https://github.com/cyberphone/json-canonicalization) gently provided under the APACHE-2.0 license by Anders Rundgren.
80
+ The initial implementation of `tallipoika` is kept similar to show the identical functioning of both implementation.
81
+ Also, the input and output JSON tests are provided within this repository as a service for validators.
82
+
83
+ # Status
84
+
85
+ Experimental.
86
+
87
+ **Note**: The default branch is `default`.
@@ -0,0 +1,15 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ tallipoika/__init__.py
6
+ tallipoika/api.py
7
+ tallipoika/py2es6.py
8
+ tallipoika.egg-info/PKG-INFO
9
+ tallipoika.egg-info/SOURCES.txt
10
+ tallipoika.egg-info/dependency_links.txt
11
+ tallipoika.egg-info/entry_points.txt
12
+ tallipoika.egg-info/requires.txt
13
+ tallipoika.egg-info/top_level.txt
14
+ test/test_api.py
15
+ test/test_py2es6.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ tallipoika = tallipoika.cli:app
@@ -0,0 +1,11 @@
1
+ typer
2
+
3
+ [dev]
4
+ black
5
+ coverage
6
+ hypothesis
7
+ mypy
8
+ pytest
9
+ pytest-cov
10
+ pytest-flake8
11
+ ruff
@@ -0,0 +1 @@
1
+ tallipoika
@@ -0,0 +1,26 @@
1
+ import json
2
+ import pathlib
3
+
4
+ from tallipoika.api import canonicalize
5
+
6
+ ENCODING = 'utf-8'
7
+ NL = '\n'
8
+ test_data_root: pathlib.Path = pathlib.Path('test') / 'fixtures'
9
+ input_data_root: pathlib.Path = test_data_root / 'reference_upstream_input'
10
+ output_data_root: pathlib.Path = test_data_root / 'reference_upstream_output'
11
+
12
+
13
+ def test_upstream():
14
+ for case in sorted(input_data_root.glob('*.json')):
15
+ file_name = case.name
16
+ with open(output_data_root / file_name, 'r', encoding=ENCODING) as sink:
17
+ expected = sink.read().encode()
18
+
19
+ with open(input_data_root / file_name, 'r', encoding=ENCODING) as source:
20
+ loaded = source.read()
21
+
22
+ actual = canonicalize(json.loads(loaded))
23
+ recycled = canonicalize(json.loads(actual.decode(ENCODING, 'strict')))
24
+
25
+ assert actual == expected
26
+ assert actual == recycled
@@ -0,0 +1,48 @@
1
+ import binascii
2
+ import pathlib
3
+ import struct
4
+
5
+ import tallipoika.py2es6 as py2es6
6
+
7
+ COMMA = ','
8
+ INVALID_NUMBER = 'null'
9
+ UPSTREAM_REF_TEST_DATA = pathlib.Path('test') / 'fixtures' / 'es6testfile100m.txt'
10
+
11
+
12
+ def verify(ieee_hex, expected) -> None:
13
+ """Test program using a 100 million value file formatted as follows:
14
+ value in ieee hex representation (1-16 digits) + ',' + correct ES6 format + '\n'
15
+ """
16
+ while len(ieee_hex) < 16:
17
+ ieee_hex = '0' + ieee_hex
18
+ value = struct.unpack('>d', binascii.a2b_hex(ieee_hex))[0]
19
+ try:
20
+ serialized = py2es6.serialize(value)
21
+ except ValueError:
22
+ if expected == INVALID_NUMBER:
23
+ return
24
+ if serialized == expected and value == float(serialized) and repr(value) == str(value):
25
+ return
26
+ raise ValueError('IEEE: ' + ieee_hex + '\nPython: ' + serialized + '\nExpected: ' + expected)
27
+
28
+
29
+ def test_corners():
30
+ assert verify('4340000000000001', '9007199254740994') is None
31
+ assert verify('4340000000000002', '9007199254740996') is None
32
+ assert verify('444b1ae4d6e2ef50', '1e+21') is None
33
+ assert verify('3eb0c6f7a0b5ed8d', '0.000001') is None
34
+ assert verify('3eb0c6f7a0b5ed8c', '9.999999999999997e-7') is None
35
+ assert verify('8000000000000000', '0') is None
36
+ assert verify('7fffffffffffffff', INVALID_NUMBER) is None
37
+ assert verify('7ff0000000000000', INVALID_NUMBER) is None
38
+ assert verify('fff0000000000000', INVALID_NUMBER) is None
39
+
40
+
41
+ def test_reference():
42
+ with open(UPSTREAM_REF_TEST_DATA, 'r') as line_source:
43
+ for count, line in enumerate(line_source):
44
+ if line:
45
+ i = line.find(COMMA)
46
+ if i <= 0 or i >= len(line) - 1:
47
+ raise ValueError(f'bad line ({line}) at {count}')
48
+ assert verify(line[:i], line[i + 1 : len(line) - 1]) is None