-
Notifications
You must be signed in to change notification settings - Fork 5
feature/SOF-7961: add scoped material bridge helpers #355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
786bb15
76ec178
64b0ccd
cf3972c
720d23f
721ae87
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| """Ready-to-execute namespace preambles for interactive Python environments.""" |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| """Imports exposed by the Materials Designer Python REPL.""" | ||
|
|
||
| from mat3ra.made.material import Material | ||
| from mat3ra.made.tools.build.defective_structures.zero_dimensional.point_defect.point_defect_type_enum import ( | ||
| PointDefectTypeEnum, | ||
| ) | ||
| from mat3ra.made.tools.build.pristine_structures.zero_dimensional.nanoparticle.enums import NanoparticleShapesEnum | ||
| from mat3ra.made.tools.build_components.entities.reusable.zero_dimensional.coordinates_shape_enum import ( | ||
| CoordinatesShapeEnum, | ||
| ) | ||
| from mat3ra.made.tools.helpers import * # noqa: F403 | ||
| from mat3ra.made.tools.helpers import __all__ as _HELPER_NAMES | ||
|
|
||
| __all__ = [ | ||
| "CoordinatesShapeEnum", | ||
| "Material", | ||
| "NanoparticleShapesEnum", | ||
| "PointDefectTypeEnum", | ||
| *_HELPER_NAMES, | ||
| ] | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3,11 +3,26 @@ | |
| import os | ||
| from typing import Any, Dict, Optional, Union | ||
|
|
||
| from IPython.display import Javascript, display | ||
|
|
||
| from ..core.io import set_data_python | ||
| from ..primitive.logger import log | ||
|
|
||
| try: | ||
| from IPython.display import Javascript, display | ||
| except ImportError: | ||
| Javascript = None | ||
| display = None | ||
|
|
||
| try: | ||
| from js import JSON, sendDataToHost # type: ignore | ||
| except ImportError: | ||
| JSON = None | ||
| sendDataToHost = None | ||
|
|
||
| try: | ||
| from pyodide.http import pyfetch # type: ignore | ||
| except ImportError: | ||
| pyfetch = None | ||
|
|
||
|
|
||
| async def read_from_url_pyodide(url: str, as_bytes: bool = False) -> Union[str, bytes]: | ||
| """ | ||
|
|
@@ -20,16 +35,29 @@ async def read_from_url_pyodide(url: str, as_bytes: bool = False) -> Union[str, | |
| Returns: | ||
| str or bytes: The content. | ||
| """ | ||
| # `http` is a Pyodide module that will be installed in the Pyodide environment by default. | ||
| from pyodide.http import pyfetch # type: ignore | ||
| if pyfetch is None: | ||
| raise RuntimeError("pyfetch is available only in Pyodide") | ||
|
|
||
| # Per https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch | ||
| response = await pyfetch(url) | ||
| if as_bytes: | ||
| return await response.bytes() | ||
| return await response.string() | ||
|
|
||
|
|
||
| def send_data_pyodide(payload: Dict[str, Any]): | ||
| """Send a bridge payload to the host application.""" | ||
| serialized_data = json.dumps(payload) | ||
|
|
||
| if JSON is not None and sendDataToHost is not None: | ||
| sendDataToHost(JSON.parse(serialized_data)) | ||
|
Comment on lines
+49
to
+52
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
serialized="$(python - <<'PY'
import json
print(json.dumps({"value": float("nan")}))
PY
)"
printf 'Serialized payload: %s\n' "$serialized"
if ! command -v node >/dev/null 2>&1; then
echo "Node.js is required for this verification." >&2
exit 2
fi
if node -e 'JSON.parse(process.argv[1])' "$serialized"; then
echo "Unexpectedly accepted non-standard JSON." >&2
exit 1
else
echo "Confirmed: JavaScript JSON.parse rejects the payload."
fiRepository: mat3ra/api-examples Length of output: 795 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf 'Target file exists:\n'
ls -l src/py/mat3ra/notebooks_utils/pyodide/io.py
printf '\nRelevant source:\n'
sed -n '1,120p' src/py/mat3ra/notebooks_utils/pyodide/io.py
printf '\nSearch for sendDataToHost and JSON usage:\n'
rg -n "sendDataToHost|JSON\.parse|json\.dumps|allow_nan|nan|NaN|Infinity|Infinity|-Infinity" src/py/mat3ra/notebooks_utils/pyodide -SRepository: mat3ra/api-examples Length of output: 4693 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf 'Find sendDataToHost definitions/usages in repo:\n'
rg -n "sendDataToHost|window\.sendDataToHost|JSON\.parse|json\.dumps\(.*,\s*allow_nan\s*=\s*False|allow_nan" . -S
printf '\nProbe Python/JS behavior for edge non-finite payloads if Node is available:\n'
python3 - <<'PY'
import json
for value, label in [
(float("nan"), "nan"),
(float("inf"), "inf"),
(float("-inf"), "-inf"),
(complex(1, 2), "complex"),
]:
try:
s = json.dumps({"value": value})
except Exception as exc:
print(label, "python_raises", type(exc).__name__, str(exc))
continue
print(label, "python", s)
PY
node - <<'JS'
for (const s of ['{"value": NaN}', '{"value": Infinity}', '{"value": -Infinity}', '{"value": 1+2j}']) {
try {
JSON.parse(s);
console.log("node accepted", s);
} catch (e) {
console.log("node rejected", s, e.name + ": " + e.message);
}
}
JSRepository: mat3ra/api-examples Length of output: 1794 🏁 Script executed: #!/bin/bash
set -euo pipefail
printf 'Find sendDataToHost definitions/usages in repo:\n'
rg -n "sendDataToHost|window\.sendDataToHost|JSON\.parse|json\.dumps\(.*,\s*allow_nan\s*=\s*False|allow_nan" . -S
printf '\nProbe Python/JS behavior for edge non-finite payloads if Node is available:\n'
python3 - <<'PY'
import json
for value, label in [
(float("nan"), "nan"),
(float("inf"), "inf"),
(float("-inf"), "-inf"),
(complex(1, 2), "complex"),
]:
try:
s = json.dumps({"value": value})
except Exception as exc:
print(label, "python_raises", type(exc).__name__, str(exc))
continue
print(label, "python", s)
PY
node - <<'JS'
for (const s of ['{"value": NaN}', '{"value": Infinity}', '{"value": -Infinity}', '{"value": 1+2j}']) {
try {
JSON.parse(s);
console.log("node accepted", s);
} catch (e) {
console.log("node rejected", s, e.name + ": " + e.message);
}
}
JSRepository: mat3ra/api-examples Length of output: 1794 Reject non-finite JSON before both transports.
🤖 Prompt for AI Agents |
||
| return | ||
|
|
||
| if Javascript is None or display is None: | ||
| raise RuntimeError("IPython is required to send data from JupyterLite") | ||
|
|
||
| display(Javascript(f"window.sendDataToHost({serialized_data});")) | ||
|
|
||
|
|
||
| def set_data_pyodide(key: str, value: Any): | ||
| """ | ||
| Take a Python object, serialize it to JSON, and send it to the host environment | ||
|
|
@@ -39,18 +67,7 @@ def set_data_pyodide(key: str, value: Any): | |
| key (str): The name under which data will be sent. | ||
| value (Any): The value to send to the host environment. | ||
| """ | ||
| serialized_data = json.dumps({key: value}) | ||
| js_code = f""" | ||
| (function() {{ | ||
| if (window.sendDataToHost) {{ | ||
| window.sendDataToHost({serialized_data}); | ||
| console.log('Data sent to host:', {serialized_data}); | ||
| }} else {{ | ||
| console.error('sendDataToHost function is not defined on the window object.'); | ||
| }} | ||
| }})(); | ||
| """ | ||
| display(Javascript(js_code)) | ||
| send_data_pyodide({key: value}) | ||
| log(f"Data for {key} sent to host.") | ||
| set_data_python(key, value) | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -114,8 +114,12 @@ def package_has_version_specifier(pkg: str) -> bool: | |
| return any(op in spec for op in VERSION_SPECIFIERS) | ||
|
|
||
|
|
||
| def should_reinstall_package(pkg: str, profile_changed: bool) -> bool: | ||
| return profile_changed and package_has_version_specifier(pkg) and not is_url_package(remove_nodeps_prefix(pkg)) | ||
| def should_reinstall_package(pkg: str, previous_packages: List[str]) -> bool: | ||
| package_name = get_package_name(pkg) | ||
| if not package_name or not package_has_version_specifier(pkg) or is_url_package(remove_nodeps_prefix(pkg)): | ||
| return False | ||
| previous_spec = next((item for item in previous_packages if get_package_name(item) == package_name), None) | ||
| return previous_spec is not None and previous_spec != pkg | ||
|
Comment on lines
+117
to
+122
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== locate target file =="
git ls-files | rg 'install\.py$|config\.yml|pyodide|notebooks_utils'
echo "== target outlines =="
ast-grep outline src/py/mat3ra/notebooks_utils/pyodide/packages/install.py --view expanded || true
echo "== relevant source lines =="
cat -n src/py/mat3ra/notebooks_utils/pyodide/packages/install.py | sed -n '1,180p'
echo "== search for should_reinstall_package/install_package_pyodide/usages =="
rg -n "should_reinstall_package|install_package_pyodide|package_has_version_specifier|is_url_package|remove_nodeps_prefix|emfs:/drive/packages|micropip\.install" src/py/mat3ra/notebooks_utils/pyodide/packages/install.py . || trueRepository: mat3ra/api-examples Length of output: 20999 🌐 Web query:
💡 Result: In Pyodide's micropip library, the install function includes a reinstall parameter that controls how the system handles packages that are already present in the environment [1][2]. The behavior is as follows: reinstall=False (Default): If a requested package is already installed but is incompatible with the version being requested, micropip will raise an error [1][3][4]. reinstall=True: If a requested package is already installed but is incompatible, micropip will uninstall the existing incompatible package and then install the new version [1][2]. Important considerations when using the reinstall parameter: Imported Modules: Micropip does not automatically reload modules that have already been imported into the Python namespace [1][2]. After a successful reinstallation, you must manually reload any modules you have already imported, for example, by using importlib.reload(module) [1][3]. Package Compatibility: Micropip can only install pure Python wheels or wheels specifically built for the wasm32/emscripten platform (Pyodide) [1][5]. If a package is not found in the Pyodide repository, it will attempt to load it from PyPI, provided it meets these wheel requirements [1][2]. Uninstalling: The separate micropip.uninstall function is also available and behaves similarly regarding imported modules—it removes the package from the environment but not from the active namespace [1][2]. It only supports uninstalling packages that were installed using a wheel file (i.e., those with distribution metadata) [1][6]. Citations:
🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "== install_packages_pyodide implementation =="
cat -n src/py/mat3ra/notebooks_utils/pyodide/packages/install.py | sed -n '180,260p'
echo "== package install tests =="
cat -n tests/py/unit/test_pyodide_packages_install.py | sed -n '1,140p'
echo "== deterministic guard behavior for URL wheels =="
python3 - <<'PY'
import re
from typing import List, Union
NODEPS_PREFIX = "nodeps:"
URL_PREFIXES = ("http://", "https://", "emfs:/")
VERSION_SPECIFIERS = ("==", ">=", "<=", "!=", "~=", ">", "<")
def get_package_name(pkg: str) -> Union[str, None]:
spec = pkg.replace(NODEPS_PREFIX, "", 1) if pkg.startswith(NODEPS_PREFIX) else pkg
match = re.match(r"^[A-Za-z0-9_.-]+", spec)
return match.group(0) if match else None
def is_url_package(pkg: str) -> bool:
return pkg.startswith(URL_PREFIXES)
def package_has_version_specifier(pkg: str) -> bool:
spec = pkg.replace(NODEPS_PREFIX, "", 1) if pkg.startswith(NODEPS_PREFIX) else pkg
return any(op in spec for op in VERSION_SPECIFIERS)
def should_reinstall_package(pkg: str, previous_packages: List[str]) -> bool:
package_name = get_package_name(pkg)
if not package_name or not package_has_version_specifier(pkg) or is_url_package(pkg):
return False
previous_spec = next((item for item in previous_packages if get_package_name(item) == package_name), None)
return previous_spec is not None and previous_spec != pkg
cases = [
["emfs:/drive/packages/pymatgen-A.whl", ["emfs:/drive/packages/pymatgen-B.whl"]],
["https://example.com/specific.whl", ["https://example.com/changed.whl"]],
["emfs:/drive/packages/pymatgen-A.whl", []],
]
for pkg, previous_packages in cases:
print(pkg, previous_packages, "=>", should_reinstall_package(pkg, previous_packages))
PYRepository: mat3ra/api-examples Length of output: 2647 Reinstall changed URL wheels.
🤖 Prompt for AI Agents |
||
|
|
||
|
|
||
| def get_package_name(pkg: str) -> Union[str, None]: | ||
|
|
@@ -187,17 +191,18 @@ async def install_packages_pyodide(notebook_name_pattern: str, verbose: bool = T | |
| packages = await get_package_list_from_config(get_config_yml_file_path(""), notebook_name_pattern) | ||
| requirements_hash = str(hash(json.dumps(packages))) | ||
| previous_hash = os.environ.get("requirements_hash") | ||
| profile_changed = previous_hash is not None and previous_hash != requirements_hash | ||
| previous_packages = json.loads(os.environ.get("requirements_packages", "[]")) | ||
| if should_install_packages(previous_hash, requirements_hash): | ||
| for pkg in packages: | ||
| await install_package_pyodide( | ||
| pkg, | ||
| verbose, | ||
| reinstall=should_reinstall_package(pkg, profile_changed), | ||
| reinstall=should_reinstall_package(pkg, previous_packages), | ||
| ) | ||
| if verbose: | ||
| log("Packages installed successfully.", force_verbose=verbose) | ||
| os.environ["requirements_hash"] = requirements_hash | ||
| os.environ["requirements_packages"] = json.dumps(packages) | ||
|
Comment on lines
+194
to
+205
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo "Files:"
git ls-files | rg 'src/py/mat3ra/notebooks_utils/pyodide/packages/install.py|requirements_packages|requirements_hash|should_reinstall_package|should_install_packages' || true
echo
echo "install.py outline:"
ast-grep outline src/py/mat3ra/noteboards_utils/pyodide/packages/install.py --view expanded 2>/dev/null || ast-grep outline src/py/mat3ra/notebooks_utils/pyodide/packages/install.py --view expanded || true
echo
echo "install.py relevant lines:"
cat -n src/py/mat3ra/notebooks_utils/pyodide/packages/install.py | sed -n '1,260p'
echo
echo "Search package-state helpers/usages:"
rg -n 'requirements_packages|requirements_hash|should_reinstall_package|should_install_packages|previous_packages' src/py/mat3ra/notebooks_utils/pyodide/packages/install.py src -SRepository: mat3ra/api-examples Length of output: 12320 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
def should_install_packages(previous_hash, requirements_hash):
return previous_hash != requirements_hash
def get_package_name(pkg):
spec = pkg.removeprefix("nodeps:")
import re
match = re.match(r"^[A-Za-z0-9_.-]+", spec)
return match.group(0) if match else None
def package_has_version_specifier(pkg):
spec = pkg.removeprefix("nodeps:")
return any(op in spec for op in ["==", ">=", "<=", "!=", "~=", ">", "<"])
def is_url_package(pkg):
return pkg.startswith(("http://", "https://", "emfs:/"))
def should_reinstall_package(pkg, previous_packages):
package_name = get_package_name(pkg)
if not package_name or not package_has_version_specifier(pkg) or is_url_package(pkg.removeprefix("nodeps:")):
return False
previous_spec = next((item for item in previous_packages if get_package_name(item) == package_name), None)
return previous_spec is not None and previous_spec != pkg
# Original hash-change behavior
current_hash = "current-hash"
previous_packages_json_missing = os.environ.get("requirements_packages", "[]") if False else "[]"
previous_packages_expected_false = json.loads(previous_packages_json_missing)
print("current_should_install_packages_missing_hash=True:", should_install_packages(None, current_hash))
print("current_should_install_packages_matching_empty_packages:", should_install_packages(current_hash, current_hash))
print("should_reinstall_package_with_empty_previous_packages:", should_reinstall_package("numpy==1.0", json.loads("[]"))
def main():
pass
PYRepository: mat3ra/api-examples Length of output: 233 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
import os
import re
from typing import List, Union
def should_install_packages(previous_hash: Union[str, None], requirements_hash: str) -> bool:
return previous_hash != requirements_hash
def remove_nodeps_prefix(pkg: str) -> str:
return pkg.replace("nodeps:", "", 1) if pkg.startswith("nodeps:") else pkg
VERSION_SPECIFIERS = ("==", ">=", "<=", "!=", "~=", ">", "<")
def package_has_version_specifier(pkg: str) -> bool:
spec = remove_nodeps_prefix(pkg)
return any(op in spec for op in VERSION_SPECIFIERS)
def is_url_package(pkg: str) -> bool:
return pkg.startswith(("http://", "https://", "emfs:/"))
def get_package_name(pkg: str) -> Union[str, None]:
spec = remove_nodeps_prefix(pkg)
match = re.match(r"^[A-Za-z0-9_.-]+", spec)
return match.group(0) if match else None
def should_reinstall_package(pkg: str, previous_packages: List[str]) -> bool:
package_name = get_package_name(pkg)
if not package_name or not package_has_version_specifier(pkg) or is_url_package(remove_nodeps_prefix(pkg)):
return False
previous_spec = next((item for item in previous_packages if get_package_name(item) == package_name), None)
return previous_spec is not None and previous_spec != pkg
os.environ["requirements_hash"] = "current-hash"
os.environ.pop("requirements_packages", None)
packages = ["numpy==1.0", "scipy==2.0", pandas"]
requirements_hash = os.environ["requirements_hash"]
previous_package_state_missing = json.loads(os.environ.get("requirements_packages", "[]"))
print("previous_packages_missing_state:", previous_package_state_missing)
print("match_hash_skips_reinstall:", should_install_packages(requirements_hash, requirements_hash))
for pkg in packages:
print("should_reinstall_package", pkg, "=>", should_reinstall_package(pkg, previous_package_state_missing))
current_packages = ["numpy==2.0"]
missing_hash_should_install = should_install_packages(None, "current-hash")
print("new_hash_installs_all_with_initial_state:", missing_hash_should_install)
print("reinstall_flags_with_initial_state:", [(pkg, should_reinstall_package(pkg, json.loads("[]"))) for pkg in current_packages])
PYRepository: mat3ra/api-examples Length of output: 337 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
import os
import re
from typing import List, Union
def remove_nodeps_prefix(pkg: str) -> str:
return pkg.replace("nodeps:", "", 1) if pkg.startswith("nodeps:") else pkg
VERSION_SPECIFIERS = ("==", ">=", "<=", "!=", "~=", ">", "<")
def packet_should_reinstall_package(pkg: str, previous_packages: List[str]) -> bool:
def get_package_name(p: str) -> Union[str, None]:
spec = remove_nodeps_prefix(p)
match = re.match(r"^[A-Za-z0-9_.-]+", spec)
return match.group(0) if match else None
def package_has_version_specifier(p: str) -> bool:
spec = remove_nodeps_prefix(p)
return any(op in spec for op in VERSION_SPECIFIERS)
for is_url_package in [lambda p: p.startswith(("http://", "https://", "emfs:/"))]
package_name = get_package_name(pkg)
if not package_name or not package_has_version_specifier(pkg) or is_url_package(remove_nodeps_prefix(pkg)):
return False
previous_spec = next((item for item in previous_packages if get_package_name(item) == package_name), None)
return previous_spec is not None and previous_spec != pkg
os.environ["requirements_hash"] = "current-hash"
os.environ.pop("requirements_packages", None)
packages = ["numpy==1.0", "scipy==2.0"]
requirements_hash = os.environ["requirements_hash"]
previous_package_state_missing = json.loads(os.environ.get("requirements_packages", "[]"))
print("previous_packages_missing_state:", previous_package_state_missing)
print("match_hash_skips_reinstall:", packet_should_reinstall_package(requirements_hash, requirements_hash))
for pkg in packages:
print("should_reinstall_package", pkg, "=>", packet_should_reinstall_package(pkg, previous_package_state_missing))
PYRepository: mat3ra/api-examples Length of output: 384 🏁 Script executed: #!/bin/bash
set -euo pipefail
python3 - <<'PY'
import json
import os
import re
from typing import List, Union
def remove_nodeps_prefix(pkg: str) -> str:
return pkg.replace("nodeps:", "", 1) if pkg.startswith("nodeps:") else pkg
VERSION_SPECIFIERS = ("==", ">=", "<=", "!=", "~=", ">", "<")
def get_package_name(pkg: str) -> Union[str, None]:
spec = remove_nodeps_prefix(pkg)
match = re.match(r"^[A-Za-z0-9_.-]+", spec)
return match.group(0) if match else None
def package_has_version_specifier(pkg: str) -> bool:
spec = remove_nodeps_prefix(pkg)
return any(op in spec for op in VERSION_SPECIFIERS)
def is_url_package(pkg: str) -> bool:
return pkg.startswith(("http://", "https://", "emfs:/"))
def should_reinstall_package(pkg: str, previous_packages: List[str]) -> bool:
package_name = get_package_name(pkg)
if not package_name or not package_has_version_specifier(pkg) or is_url_package(remove_nodeps_prefix(pkg)):
return False
previous_spec = next((item for item in previous_packages if get_package_name(item) == package_name), None)
return previous_spec is not None and previous_spec != pkg
os.environ["requirements_hash"] = "current-hash"
os.environ.pop("requirements_packages", None)
requirements_hash = os.environ["requirements_hash"]
previous_package_state_missing = json.loads(os.environ.get("requirements_packages", "[]"))
print("previous_packages_missing_state:", previous_package_state_missing)
print("same_hash_skips_block:", previous_package_state_missing == [])
for pkg in ["numpy==1.0", "scipy==2.0"]:
print("should_reinstall_package", pkg, "=", should_reinstall_package(pkg, previous_package_state_missing))
current_packages = ["numpy==2.0"]
print("new_hash_installs_all_without_initial_state:", should_reinstall_package(current_packages[0], json.loads("[]")))
PYRepository: mat3ra/api-examples Length of output: 360 Treat missing If 🧰 Tools🪛 ast-grep (0.45.0)[info] 201-201: use jsonify instead of json.dumps for JSON output (use-jsonify) 🤖 Prompt for AI Agents |
||
| else: | ||
| if verbose: | ||
| log("Packages are already installed.", force_verbose=verbose) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| from unittest.mock import patch | ||
|
|
||
| from mat3ra.made.material import Material | ||
| from mat3ra.notebooks_utils.core.entity.material.io import sync_materials | ||
| from mat3ra.standata.materials import Materials | ||
|
|
||
|
|
||
| def material(name: str) -> Material: | ||
| return Material.create({**Materials.get_by_name_first_match("Silicon"), "name": name}) | ||
|
|
||
|
|
||
| def test_sync_materials_collects_public_bindings_and_one_container_level(): | ||
| direct = material("direct") | ||
| listed = material("listed") | ||
| mapped = material("mapped") | ||
|
|
||
| namespace = { | ||
| "direct": direct, | ||
| "group": [listed, 3, [material("too-deep")]], | ||
| "mapping": {"value": mapped}, | ||
| "materials_in": [material("input")], | ||
| "material": material("selected"), | ||
| "_private": material("private"), | ||
| "number": 4, | ||
| } | ||
|
|
||
| with patch("mat3ra.notebooks_utils.core.entity.material.io.send_data") as send: | ||
| sync_materials(namespace) | ||
|
|
||
| payload = send.call_args.args[0] | ||
| assert payload["syncScope"] == "python-repl" | ||
| assert [(entity["type"], entity["name"]) for entity in payload["entities"]] == [ | ||
| ("material", "direct"), | ||
| ("material", "group"), | ||
| ("material", "mapping"), | ||
| ] | ||
| assert [entity["config"]["name"] for entity in payload["entities"]] == [ | ||
| "direct", | ||
| "listed", | ||
| "mapped", | ||
| ] | ||
|
|
||
|
|
||
| def test_sync_materials_sends_an_empty_batch_to_clear_the_scope(): | ||
| with patch("mat3ra.notebooks_utils.core.entity.material.io.send_data") as send: | ||
| sync_materials({"x": 1}, sync_scope="test-scope") | ||
|
|
||
| send.assert_called_once_with({"syncScope": "test-scope", "entities": []}) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| import json | ||
| from types import SimpleNamespace | ||
| from unittest.mock import patch | ||
|
|
||
| from mat3ra.notebooks_utils.pyodide.io import send_data_pyodide, set_data_pyodide | ||
|
|
||
|
|
||
| def test_send_data_pyodide_calls_same_page_bridge_directly(): | ||
| received = [] | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.JSON", SimpleNamespace(parse=json.loads)): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.sendDataToHost", received.append): | ||
| send_data_pyodide({"syncScope": "python-repl", "entities": []}) | ||
|
|
||
| assert received == [{"syncScope": "python-repl", "entities": []}] | ||
|
|
||
|
|
||
| def test_send_data_pyodide_uses_display_for_jupyterlite(): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.JSON", None): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.Javascript", side_effect=lambda source: source): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.display") as display: | ||
| send_data_pyodide({"syncScope": "python-repl", "entities": []}) | ||
|
|
||
| assert display.call_args.args[0] == 'window.sendDataToHost({"syncScope": "python-repl", "entities": []});' | ||
|
|
||
|
|
||
| def test_set_data_pyodide_updates_python_data(): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.JSON", SimpleNamespace(parse=json.loads)): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.sendDataToHost"): | ||
| with patch("mat3ra.notebooks_utils.pyodide.io.set_data_python") as set_data_python: | ||
| set_data_pyodide("materials", [{"name": "Si"}]) | ||
|
|
||
| set_data_python.assert_called_once_with("materials", [{"name": "Si"}]) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| from mat3ra.notebooks_utils.pyodide.packages.install import should_reinstall_package | ||
|
|
||
|
|
||
| def test_reinstalls_only_when_the_same_package_version_changes(): | ||
| previous = ["networkx==3.2.1", "scipy==1.11.2"] | ||
|
|
||
| assert should_reinstall_package("networkx==3.2.2", previous) | ||
| assert not should_reinstall_package("networkx==3.2.1", previous) | ||
| assert not should_reinstall_package("tabulate==0.9.0", previous) | ||
|
|
||
|
|
||
| def test_does_not_reinstall_url_or_emfs_requirements(): | ||
| assert not should_reinstall_package( | ||
| "emfs:/drive/packages/example.whl", | ||
| ["emfs:/drive/packages/old.whl"], | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: mat3ra/api-examples
Length of output: 1863
🏁 Script executed:
Repository: mat3ra/api-examples
Length of output: 2149
Use a known string list for
__all__.Current Ruff setup flags
__all__on line 14 with PLE0604 because the starred expansion comes from dynamic helper imports. Either define__all__with explicit string names, or reassign it after module load; keep thenoqa: F403here since the wildcard import is used.🧰 Tools
🪛 Ruff (0.16.1)
[error] 14-14: Invalid object in
__all__, must contain only strings(PLE0604)
🤖 Prompt for AI Agents
Source: Linters/SAST tools