boabem 0.1.0__tar.gz → 0.2.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.
- {boabem-0.1.0 → boabem-0.2.0}/Cargo.lock +10 -9
- {boabem-0.1.0 → boabem-0.2.0}/Cargo.toml +1 -1
- {boabem-0.1.0 → boabem-0.2.0}/PKG-INFO +23 -1
- {boabem-0.1.0 → boabem-0.2.0}/README.md +20 -0
- {boabem-0.1.0 → boabem-0.2.0}/pyproject.toml +1 -1
- boabem-0.2.0/python/boabem/__init__.py +3 -0
- {boabem-0.1.0 → boabem-0.2.0}/python/boabem/boabem.pyi +1 -1
- {boabem-0.1.0 → boabem-0.2.0}/src/hebi.rs +48 -13
- {boabem-0.1.0 → boabem-0.2.0}/src/lib.rs +3 -1
- boabem-0.2.0/tests/__init__.py +0 -0
- boabem-0.2.0/tests/conversion_test.py +155 -0
- {boabem-0.1.0 → boabem-0.2.0}/tests/main_test.py +2 -14
- boabem-0.1.0/python/boabem/__init__.py +0 -3
- {boabem-0.1.0 → boabem-0.2.0}/.github/dependabot.yml +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/.github/workflows/release.yml +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/.github/workflows/test.yml +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/.gitignore +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/.pre-commit-config.yaml +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/LICENSE-APACHE +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/LICENSE-MIT +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/noxfile.py +0 -0
- /boabem-0.1.0/tests/__init__.py → /boabem-0.2.0/python/boabem/py.typed +0 -0
- {boabem-0.1.0 → boabem-0.2.0}/tests/intl_test.py +0 -0
|
@@ -31,9 +31,9 @@ checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8"
|
|
|
31
31
|
|
|
32
32
|
[[package]]
|
|
33
33
|
name = "bitflags"
|
|
34
|
-
version = "2.9.
|
|
34
|
+
version = "2.9.3"
|
|
35
35
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
36
|
-
checksum = "
|
|
36
|
+
checksum = "34efbcccd345379ca2868b2b2c9d3782e9cc58ba87bc7d79d5b53d9c9ae6f25d"
|
|
37
37
|
|
|
38
38
|
[[package]]
|
|
39
39
|
name = "boa_ast"
|
|
@@ -244,7 +244,7 @@ dependencies = [
|
|
|
244
244
|
|
|
245
245
|
[[package]]
|
|
246
246
|
name = "boabem"
|
|
247
|
-
version = "0.
|
|
247
|
+
version = "0.2.0"
|
|
248
248
|
dependencies = [
|
|
249
249
|
"boa_engine",
|
|
250
250
|
"boa_runtime",
|
|
@@ -976,9 +976,9 @@ checksum = "964de6e86d545b246d84badc0fef527924ace5134f30641c203ef52ba83f58d5"
|
|
|
976
976
|
|
|
977
977
|
[[package]]
|
|
978
978
|
name = "indexmap"
|
|
979
|
-
version = "2.
|
|
979
|
+
version = "2.11.0"
|
|
980
980
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
981
|
-
checksum = "
|
|
981
|
+
checksum = "f2481980430f9f78649238835720ddccc57e52df14ffce1c6f37391d61b563e9"
|
|
982
982
|
dependencies = [
|
|
983
983
|
"equivalent",
|
|
984
984
|
"hashbrown 0.15.5",
|
|
@@ -1729,13 +1729,14 @@ checksum = "7264e107f553ccae879d21fbea1d6724ac785e8c3bfc762137959b5802826ef3"
|
|
|
1729
1729
|
|
|
1730
1730
|
[[package]]
|
|
1731
1731
|
name = "url"
|
|
1732
|
-
version = "2.5.
|
|
1732
|
+
version = "2.5.7"
|
|
1733
1733
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1734
|
-
checksum = "
|
|
1734
|
+
checksum = "08bc136a29a3d1758e07a9cca267be308aeebf5cfd5a10f3f67ab2097683ef5b"
|
|
1735
1735
|
dependencies = [
|
|
1736
1736
|
"form_urlencoded",
|
|
1737
1737
|
"idna",
|
|
1738
1738
|
"percent-encoding",
|
|
1739
|
+
"serde",
|
|
1739
1740
|
]
|
|
1740
1741
|
|
|
1741
1742
|
[[package]]
|
|
@@ -1949,9 +1950,9 @@ checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
|
|
|
1949
1950
|
|
|
1950
1951
|
[[package]]
|
|
1951
1952
|
name = "winnow"
|
|
1952
|
-
version = "0.7.
|
|
1953
|
+
version = "0.7.13"
|
|
1953
1954
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
1954
|
-
checksum = "
|
|
1955
|
+
checksum = "21a0236b59786fed61e2a80582dd500fe61f18b5dca67a4a067d0bc9039339cf"
|
|
1955
1956
|
dependencies = [
|
|
1956
1957
|
"memchr",
|
|
1957
1958
|
]
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boabem
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0
|
|
4
4
|
Classifier: Programming Language :: Rust
|
|
5
5
|
Classifier: Programming Language :: JavaScript
|
|
6
6
|
Requires-Dist: hypothesis[pytest] ; extra == 'test'
|
|
7
7
|
Requires-Dist: pytest-pretty ; extra == 'test'
|
|
8
|
+
Requires-Dist: json-five ; extra == 'test'
|
|
9
|
+
Requires-Dist: deepdiff ; extra == 'test'
|
|
8
10
|
Provides-Extra: test
|
|
9
11
|
License-File: LICENSE-APACHE
|
|
10
12
|
License-File: LICENSE-MIT
|
|
@@ -60,6 +62,8 @@ result = ctx.eval_from_filepath(Path("script.js"))
|
|
|
60
62
|
- eval(source: str) -> Any
|
|
61
63
|
- eval_from_bytes(source: str) -> Any (same behavior as eval)
|
|
62
64
|
- eval_from_filepath(path: str | os.PathLike[str]) -> Any
|
|
65
|
+
- boabem.PanicException
|
|
66
|
+
- Exception class exposed for Rust panics (e.g., attempting to use a Context across threads).
|
|
63
67
|
- boabem.Undefined
|
|
64
68
|
- Sentinel type representing JavaScript `undefined`.
|
|
65
69
|
- String representation: "Undefined".
|
|
@@ -82,6 +86,22 @@ Notes:
|
|
|
82
86
|
- Some JS values (e.g., Symbol) cannot be converted and will raise an error.
|
|
83
87
|
- Each `undefined` you get back is a distinct Python object, but compares equal to another `Undefined`.
|
|
84
88
|
|
|
89
|
+
### Object/Array conversion details
|
|
90
|
+
|
|
91
|
+
When converting composite values (JavaScript Objects and Arrays) to Python `dict`/`list`, elements are converted recursively with a few caveats:
|
|
92
|
+
|
|
93
|
+
- BigInt inside Objects/Arrays is converted to Python `int`.
|
|
94
|
+
- Examples: `({ a: 1n, 1: 2n, 2n: 3n }) -> {"a": 1, "1": 2, "2": 3}` and `[1, 2, 3n] -> [1, 2, 3]`.
|
|
95
|
+
- `NaN` and `±Infinity` inside Objects/Arrays are preserved as Python floats (`float('nan')` / `float('inf')`).
|
|
96
|
+
- Examples: `({ a: NaN, b: Infinity }) -> {"a": nan, "b": inf}` and `[1, 2, NaN, Infinity] -> [1, 2, nan, inf]`.
|
|
97
|
+
- `undefined` inside Objects/Arrays is converted to `boabem.Undefined`.
|
|
98
|
+
|
|
99
|
+
Additional notes:
|
|
100
|
+
|
|
101
|
+
- JavaScript object property keys are coerced to strings during conversion; for example, a `2n` property name becomes the Python key `"2"`.
|
|
102
|
+
|
|
103
|
+
Note: Top-level primitives are still mapped as documented above (e.g., `10n` -> `int`, `NaN`/`Infinity` -> `float('nan')`/`float('inf')`). The special rules here apply only to values nested within Objects/Arrays.
|
|
104
|
+
|
|
85
105
|
## Threading and processes
|
|
86
106
|
|
|
87
107
|
Context is not thread-sendable or picklable:
|
|
@@ -90,6 +110,8 @@ Context is not thread-sendable or picklable:
|
|
|
90
110
|
- Do not send a Context to another process (cannot pickle).
|
|
91
111
|
- Create and use a Context only in the thread where it was created.
|
|
92
112
|
|
|
113
|
+
If you try to use a Context across threads, you'll get a Rust panic surfaced as `pyo3_runtime.PanicException` (exposed as `boabem.PanicException`).
|
|
114
|
+
|
|
93
115
|
## Errors
|
|
94
116
|
|
|
95
117
|
- JavaScript exceptions (e.g., `throw new Error('boom')`) raise RuntimeError with the JS message.
|
|
@@ -42,6 +42,8 @@ result = ctx.eval_from_filepath(Path("script.js"))
|
|
|
42
42
|
- eval(source: str) -> Any
|
|
43
43
|
- eval_from_bytes(source: str) -> Any (same behavior as eval)
|
|
44
44
|
- eval_from_filepath(path: str | os.PathLike[str]) -> Any
|
|
45
|
+
- boabem.PanicException
|
|
46
|
+
- Exception class exposed for Rust panics (e.g., attempting to use a Context across threads).
|
|
45
47
|
- boabem.Undefined
|
|
46
48
|
- Sentinel type representing JavaScript `undefined`.
|
|
47
49
|
- String representation: "Undefined".
|
|
@@ -64,6 +66,22 @@ Notes:
|
|
|
64
66
|
- Some JS values (e.g., Symbol) cannot be converted and will raise an error.
|
|
65
67
|
- Each `undefined` you get back is a distinct Python object, but compares equal to another `Undefined`.
|
|
66
68
|
|
|
69
|
+
### Object/Array conversion details
|
|
70
|
+
|
|
71
|
+
When converting composite values (JavaScript Objects and Arrays) to Python `dict`/`list`, elements are converted recursively with a few caveats:
|
|
72
|
+
|
|
73
|
+
- BigInt inside Objects/Arrays is converted to Python `int`.
|
|
74
|
+
- Examples: `({ a: 1n, 1: 2n, 2n: 3n }) -> {"a": 1, "1": 2, "2": 3}` and `[1, 2, 3n] -> [1, 2, 3]`.
|
|
75
|
+
- `NaN` and `±Infinity` inside Objects/Arrays are preserved as Python floats (`float('nan')` / `float('inf')`).
|
|
76
|
+
- Examples: `({ a: NaN, b: Infinity }) -> {"a": nan, "b": inf}` and `[1, 2, NaN, Infinity] -> [1, 2, nan, inf]`.
|
|
77
|
+
- `undefined` inside Objects/Arrays is converted to `boabem.Undefined`.
|
|
78
|
+
|
|
79
|
+
Additional notes:
|
|
80
|
+
|
|
81
|
+
- JavaScript object property keys are coerced to strings during conversion; for example, a `2n` property name becomes the Python key `"2"`.
|
|
82
|
+
|
|
83
|
+
Note: Top-level primitives are still mapped as documented above (e.g., `10n` -> `int`, `NaN`/`Infinity` -> `float('nan')`/`float('inf')`). The special rules here apply only to values nested within Objects/Arrays.
|
|
84
|
+
|
|
67
85
|
## Threading and processes
|
|
68
86
|
|
|
69
87
|
Context is not thread-sendable or picklable:
|
|
@@ -72,6 +90,8 @@ Context is not thread-sendable or picklable:
|
|
|
72
90
|
- Do not send a Context to another process (cannot pickle).
|
|
73
91
|
- Create and use a Context only in the thread where it was created.
|
|
74
92
|
|
|
93
|
+
If you try to use a Context across threads, you'll get a Rust panic surfaced as `pyo3_runtime.PanicException` (exposed as `boabem.PanicException`).
|
|
94
|
+
|
|
75
95
|
## Errors
|
|
76
96
|
|
|
77
97
|
- JavaScript exceptions (e.g., `throw new Error('boom')`) raise RuntimeError with the JS message.
|
|
@@ -13,7 +13,7 @@ classifiers = [
|
|
|
13
13
|
dynamic = ["version"]
|
|
14
14
|
|
|
15
15
|
[project.optional-dependencies]
|
|
16
|
-
test = ["hypothesis[pytest]", "pytest-pretty"]
|
|
16
|
+
test = ["hypothesis[pytest]", "pytest-pretty", "json-five", "deepdiff"]
|
|
17
17
|
|
|
18
18
|
[project.urls]
|
|
19
19
|
source = "https://github.com/Bing-su/boabem"
|
|
@@ -4,9 +4,9 @@ from typing import Any
|
|
|
4
4
|
__version__: str
|
|
5
5
|
|
|
6
6
|
class Context:
|
|
7
|
-
def __init__(self): ...
|
|
8
7
|
def eval(self, source: str) -> Any: ...
|
|
9
8
|
def eval_from_bytes(self, source: str) -> Any: ...
|
|
10
9
|
def eval_from_filepath(self, source: str | os.PathLike[str]) -> Any: ...
|
|
11
10
|
|
|
12
11
|
class Undefined: ...
|
|
12
|
+
class PanicException(BaseException): ...
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
use boa_engine::value::TryFromJs;
|
|
1
2
|
use boa_engine::{Context, JsValue, Source};
|
|
2
3
|
use eyre::{Result, eyre};
|
|
4
|
+
use pyo3::IntoPyObjectExt;
|
|
3
5
|
use pyo3::prelude::*;
|
|
6
|
+
use pyo3::types::{PyDict, PyList};
|
|
4
7
|
use pythonize::pythonize;
|
|
8
|
+
use std::collections::HashMap;
|
|
5
9
|
use std::path::PathBuf;
|
|
6
10
|
|
|
7
11
|
#[pyclass(name = "Undefined", module = "boabem.boabem", str, eq, frozen)]
|
|
@@ -11,8 +15,8 @@ pub struct PyUndefined {}
|
|
|
11
15
|
#[pymethods]
|
|
12
16
|
impl PyUndefined {
|
|
13
17
|
#[new]
|
|
14
|
-
fn
|
|
15
|
-
|
|
18
|
+
fn py_new() -> Self {
|
|
19
|
+
Self::new()
|
|
16
20
|
}
|
|
17
21
|
|
|
18
22
|
fn __repr__(&self) -> &str {
|
|
@@ -20,6 +24,12 @@ impl PyUndefined {
|
|
|
20
24
|
}
|
|
21
25
|
}
|
|
22
26
|
|
|
27
|
+
impl PyUndefined {
|
|
28
|
+
fn new() -> Self {
|
|
29
|
+
Self {}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
23
33
|
impl std::fmt::Display for PyUndefined {
|
|
24
34
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
25
35
|
write!(f, "undefined")
|
|
@@ -65,7 +75,7 @@ impl PyContext {
|
|
|
65
75
|
}
|
|
66
76
|
}
|
|
67
77
|
|
|
68
|
-
fn
|
|
78
|
+
fn to_pybigint(value: &str) -> Result<PyObject> {
|
|
69
79
|
Python::with_gil(|py| {
|
|
70
80
|
let builtins = PyModule::import(py, "builtins")?;
|
|
71
81
|
let int_class = builtins.getattr("int")?;
|
|
@@ -74,24 +84,21 @@ fn pybigint(value: &str) -> Result<PyObject> {
|
|
|
74
84
|
})
|
|
75
85
|
}
|
|
76
86
|
|
|
77
|
-
fn
|
|
78
|
-
|
|
79
|
-
let pyfloat = value.into_pyobject(py)?;
|
|
80
|
-
Ok(pyfloat.into())
|
|
81
|
-
})
|
|
87
|
+
fn to_pyobject<'a, T: IntoPyObjectExt<'a>>(py: Python<'a>, value: T) -> Result<PyObject> {
|
|
88
|
+
Ok(value.into_py_any(py)?)
|
|
82
89
|
}
|
|
83
90
|
|
|
84
91
|
impl PyContext {
|
|
85
92
|
fn jsvalue_to_pyobject(&mut self, value: JsValue) -> Result<PyObject> {
|
|
86
93
|
match value {
|
|
87
|
-
JsValue::Undefined =>
|
|
88
|
-
Python::with_gil(|py| Ok(Py::new(py, PyUndefined::new())?.into_any()))
|
|
89
|
-
}
|
|
94
|
+
JsValue::Undefined => Python::with_gil(|py| to_pyobject(py, PyUndefined::new())),
|
|
90
95
|
JsValue::BigInt(js_bigint) => {
|
|
91
96
|
let bigint_str = js_bigint.to_string_radix(10);
|
|
92
|
-
|
|
97
|
+
to_pybigint(&bigint_str)
|
|
93
98
|
}
|
|
94
|
-
JsValue::Rational(
|
|
99
|
+
JsValue::Rational(v) => Python::with_gil(|py| to_pyobject(py, v)),
|
|
100
|
+
JsValue::Object(obj) if obj.is_array() => self.jsobj_to_py_list(&JsValue::Object(obj)),
|
|
101
|
+
JsValue::Object(obj) => self.jsobj_to_py_dict(&JsValue::Object(obj)),
|
|
95
102
|
other => {
|
|
96
103
|
let json = other
|
|
97
104
|
.to_json(&mut self.context)
|
|
@@ -104,4 +111,32 @@ impl PyContext {
|
|
|
104
111
|
}
|
|
105
112
|
}
|
|
106
113
|
}
|
|
114
|
+
|
|
115
|
+
fn jsobj_to_py_list(&mut self, obj: &JsValue) -> Result<PyObject> {
|
|
116
|
+
let arr: Vec<JsValue> =
|
|
117
|
+
Vec::try_from_js(obj, &mut self.context).map_err(|e| eyre!(e.to_string()))?;
|
|
118
|
+
|
|
119
|
+
Python::with_gil(|py| {
|
|
120
|
+
let py_list = PyList::empty(py);
|
|
121
|
+
for item in arr {
|
|
122
|
+
let py_item = self.jsvalue_to_pyobject(item)?;
|
|
123
|
+
py_list.append(py_item)?;
|
|
124
|
+
}
|
|
125
|
+
Ok(py_list.into())
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn jsobj_to_py_dict(&mut self, obj: &JsValue) -> Result<PyObject> {
|
|
130
|
+
let map: HashMap<String, JsValue> =
|
|
131
|
+
HashMap::try_from_js(obj, &mut self.context).map_err(|e| eyre!(e.to_string()))?;
|
|
132
|
+
|
|
133
|
+
Python::with_gil(|py| {
|
|
134
|
+
let py_dict = PyDict::new(py);
|
|
135
|
+
for (key, value) in map {
|
|
136
|
+
let py_value = self.jsvalue_to_pyobject(value)?;
|
|
137
|
+
py_dict.set_item(key, py_value)?;
|
|
138
|
+
}
|
|
139
|
+
Ok(py_dict.into())
|
|
140
|
+
})
|
|
141
|
+
}
|
|
107
142
|
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
use pyo3::panic::PanicException;
|
|
1
2
|
use pyo3::prelude::*;
|
|
2
3
|
mod hebi;
|
|
3
4
|
|
|
4
5
|
#[pymodule]
|
|
5
|
-
fn boabem(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
6
|
+
fn boabem(py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|
6
7
|
m.add("__version__", env!("CARGO_PKG_VERSION"))?;
|
|
7
8
|
m.add_class::<hebi::PyUndefined>()?;
|
|
8
9
|
m.add_class::<hebi::PyContext>()?;
|
|
10
|
+
m.add("PanicException", py.get_type::<PanicException>())?;
|
|
9
11
|
Ok(())
|
|
10
12
|
}
|
|
File without changes
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from math import isnan
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import json5
|
|
6
|
+
import pytest
|
|
7
|
+
from deepdiff import DeepDiff
|
|
8
|
+
from hypothesis import given
|
|
9
|
+
from hypothesis import strategies as st
|
|
10
|
+
|
|
11
|
+
from boabem import Context, Undefined
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def json(*, finite_only: bool = True):
|
|
15
|
+
"""Helper function to describe JSON objects, with optional inf and nan."""
|
|
16
|
+
numbers = st.floats(allow_infinity=not finite_only, allow_nan=not finite_only)
|
|
17
|
+
return st.recursive(
|
|
18
|
+
st.none() | st.booleans() | st.integers() | numbers | st.text(),
|
|
19
|
+
extend=lambda xs: st.lists(xs) | st.dictionaries(st.text(), xs),
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def json_array(*, finite_only: bool = True):
|
|
24
|
+
return st.lists(json(finite_only=finite_only))
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def json_object(*, finite_only: bool = True):
|
|
28
|
+
return st.dictionaries(st.text(), json(finite_only=finite_only))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@given(value=st.integers(min_value=-(10**9), max_value=10**9))
|
|
32
|
+
def test_int(value: int):
|
|
33
|
+
ctx = Context()
|
|
34
|
+
code = str(value)
|
|
35
|
+
assert ctx.eval(code) == value
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@given(value=st.floats(allow_infinity=True, allow_nan=True))
|
|
39
|
+
def test_float(value: float):
|
|
40
|
+
ctx = Context()
|
|
41
|
+
code = json5.dumps(value).replace("nan", "NaN")
|
|
42
|
+
result = ctx.eval(code)
|
|
43
|
+
assert value == result or (isnan(value) and isnan(result))
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@given(value=st.integers(min_value=-(10**100), max_value=10**100))
|
|
47
|
+
def test_bigint(value: int):
|
|
48
|
+
ctx = Context()
|
|
49
|
+
code = f"{value}n"
|
|
50
|
+
assert ctx.eval(code) == value
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@given(value=json_array(finite_only=False))
|
|
54
|
+
def test_json_array(value: list[Any]):
|
|
55
|
+
ctx = Context()
|
|
56
|
+
code = json5.dumps(value).replace("nan", "NaN")
|
|
57
|
+
result = ctx.eval(f"let arr = {code};\narr")
|
|
58
|
+
assert isinstance(result, list)
|
|
59
|
+
|
|
60
|
+
diff = DeepDiff(
|
|
61
|
+
value, result, ignore_nan_inequality=True, ignore_numeric_type_changes=True
|
|
62
|
+
)
|
|
63
|
+
assert len(diff.affected_paths) == 0
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@given(value=json_object(finite_only=False))
|
|
67
|
+
def test_json_object(value: dict[str, Any]):
|
|
68
|
+
ctx = Context()
|
|
69
|
+
code = json5.dumps(value).replace("nan", "NaN")
|
|
70
|
+
result = ctx.eval(f"let obj = {code};\nobj")
|
|
71
|
+
assert isinstance(result, dict)
|
|
72
|
+
|
|
73
|
+
diff = DeepDiff(
|
|
74
|
+
value, result, ignore_nan_inequality=True, ignore_numeric_type_changes=True
|
|
75
|
+
)
|
|
76
|
+
assert len(diff.affected_paths) == 0
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@given(value=json(finite_only=False))
|
|
80
|
+
def test_any_json(value: Any):
|
|
81
|
+
ctx = Context()
|
|
82
|
+
code = json5.dumps(value).replace("nan", "NaN")
|
|
83
|
+
result = ctx.eval(f"let obj = {code};\nobj")
|
|
84
|
+
diff = DeepDiff(
|
|
85
|
+
value, result, ignore_nan_inequality=True, ignore_numeric_type_changes=True
|
|
86
|
+
)
|
|
87
|
+
assert len(diff.affected_paths) == 0
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def test_deep_object():
|
|
91
|
+
ctx = Context()
|
|
92
|
+
code = """
|
|
93
|
+
let obj = {a:{b:{c:{d:{e:{f:{g:{h:{i:{j:1}}}}}}}}}};
|
|
94
|
+
obj
|
|
95
|
+
"""
|
|
96
|
+
result = ctx.eval(code)
|
|
97
|
+
diff = DeepDiff(result, json5.loads("{a:{b:{c:{d:{e:{f:{g:{h:{i:{j:1}}}}}}}}}}"))
|
|
98
|
+
assert len(diff.affected_paths) == 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
@pytest.mark.skipif(
|
|
102
|
+
sys.version_info < (3, 11),
|
|
103
|
+
reason="https://docs.python.org/3.13/library/stdtypes.html#integer-string-conversion-length-limitation",
|
|
104
|
+
)
|
|
105
|
+
def test_integer_string_conversion_length_limitation():
|
|
106
|
+
ctx = Context()
|
|
107
|
+
code = "10n ** 4300n"
|
|
108
|
+
with pytest.raises(ValueError, match="Exceeds the limit"):
|
|
109
|
+
ctx.eval(code)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def test_bigint_in_object():
|
|
113
|
+
ctx = Context()
|
|
114
|
+
assert ctx.eval("({ a: 1n, 1: 2n, 2n: 3n })") == {"a": 1, "1": 2, "2": 3}
|
|
115
|
+
assert ctx.eval("[1, 2, 3n]") == [1, 2, 3]
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def test_nan_inf_in_object():
|
|
119
|
+
ctx = Context()
|
|
120
|
+
result = ctx.eval(
|
|
121
|
+
"({ a: NaN, b: Infinity, NaN: NaN, Infinity: Infinity, null: null })"
|
|
122
|
+
)
|
|
123
|
+
assert isinstance(result, dict)
|
|
124
|
+
assert result == pytest.approx(
|
|
125
|
+
{
|
|
126
|
+
"a": float("nan"),
|
|
127
|
+
"b": float("inf"),
|
|
128
|
+
"NaN": float("nan"),
|
|
129
|
+
"Infinity": float("inf"),
|
|
130
|
+
"null": None,
|
|
131
|
+
},
|
|
132
|
+
nan_ok=True,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
result = ctx.eval("[1, 2, NaN, Infinity]")
|
|
136
|
+
assert isinstance(result, list)
|
|
137
|
+
assert result == pytest.approx([1, 2, float("nan"), float("inf")], nan_ok=True)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def test_undefined_in_object():
|
|
141
|
+
ctx = Context()
|
|
142
|
+
assert ctx.eval("({ a: undefined, undefined: undefined })") == {
|
|
143
|
+
"a": Undefined(),
|
|
144
|
+
"undefined": Undefined(),
|
|
145
|
+
}
|
|
146
|
+
assert ctx.eval("[1, 2, undefined]") == [1, 2, Undefined()]
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_function():
|
|
150
|
+
ctx = Context()
|
|
151
|
+
code = """
|
|
152
|
+
let test_add = (a, b) => a + b;
|
|
153
|
+
test_add
|
|
154
|
+
"""
|
|
155
|
+
assert ctx.eval(code) == {"length": 2, "name": "test_add"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import json
|
|
2
|
-
import sys
|
|
3
2
|
from concurrent.futures import ProcessPoolExecutor, ThreadPoolExecutor
|
|
4
3
|
from math import isinf, isnan
|
|
5
4
|
from pathlib import Path
|
|
@@ -7,7 +6,7 @@ from typing import Any
|
|
|
7
6
|
|
|
8
7
|
import pytest
|
|
9
8
|
|
|
10
|
-
from boabem import Context, Undefined
|
|
9
|
+
from boabem import Context, PanicException, Undefined
|
|
11
10
|
|
|
12
11
|
|
|
13
12
|
def test_banana():
|
|
@@ -393,7 +392,7 @@ def test_thread_pool():
|
|
|
393
392
|
future = executor.submit(ctx.eval, "1 + 1")
|
|
394
393
|
|
|
395
394
|
# pyo3_runtime.PanicException
|
|
396
|
-
with pytest.raises(
|
|
395
|
+
with pytest.raises(PanicException, match="unsendable"):
|
|
397
396
|
future.result()
|
|
398
397
|
|
|
399
398
|
|
|
@@ -404,14 +403,3 @@ def test_process_pool():
|
|
|
404
403
|
|
|
405
404
|
with pytest.raises(TypeError, match="cannot pickle"):
|
|
406
405
|
future.result()
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
@pytest.mark.skipif(
|
|
410
|
-
sys.version_info < (3, 11),
|
|
411
|
-
reason="https://docs.python.org/3.13/library/stdtypes.html#integer-string-conversion-length-limitation",
|
|
412
|
-
)
|
|
413
|
-
def test_integer_string_conversion_length_limitation():
|
|
414
|
-
ctx = Context()
|
|
415
|
-
code = "10n ** 4300n"
|
|
416
|
-
with pytest.raises(ValueError, match="Exceeds the limit"):
|
|
417
|
-
ctx.eval(code)
|
|
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
|