Skip to content

Commit 59b498d

Browse files
committed
Merge branch 'api-json-fix'
2 parents d40bdfc + d77d86d commit 59b498d

File tree

9 files changed

+689
-42
lines changed

9 files changed

+689
-42
lines changed

code/__init__.py

Lines changed: 125 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,125 @@
1-
# code package
1+
"""
2+
Compatibility helpers for the local ``code`` package.
3+
4+
The project reuses the stdlib module name ``code`` for its own package,
5+
which can break third-party imports that expect the built-in module
6+
(``from code import InteractiveConsole`` for example). We proxy those
7+
requests back to the real stdlib module so that our package can coexist
8+
with prebuilt libraries that rely on it.
9+
"""
10+
11+
from __future__ import annotations
12+
13+
import importlib
14+
import importlib.util
15+
import os
16+
import sys
17+
import sysconfig
18+
from functools import lru_cache
19+
from types import ModuleType
20+
from typing import Any, Iterable, Set
21+
22+
__all__: list[str] = []
23+
24+
_PACKAGE_DIR = os.path.dirname(__file__)
25+
_STDLIB_BOOTSTRAP_KEY = "code._stdlib_bootstrap"
26+
27+
28+
def _register_package_path() -> None:
29+
"""
30+
Guarantee that sibling modules remain importable via absolute paths
31+
even when the project is executed outside of an installed environment.
32+
"""
33+
project_root = os.path.dirname(_PACKAGE_DIR)
34+
candidates = [project_root, _PACKAGE_DIR]
35+
for path in candidates:
36+
if path and path not in sys.path:
37+
sys.path.insert(0, path)
38+
39+
40+
_register_package_path()
41+
42+
43+
def _stdlib_candidates() -> Iterable[str]:
44+
"""Yield plausible filesystem locations for the stdlib ``code`` module."""
45+
seen: Set[str] = set()
46+
for key in ("stdlib", "platstdlib"):
47+
path = sysconfig.get_path(key)
48+
if not path:
49+
continue
50+
candidate = os.path.join(path, "code.py")
51+
if candidate not in seen and os.path.exists(candidate):
52+
seen.add(candidate)
53+
yield candidate
54+
55+
56+
@lru_cache(maxsize=1)
57+
def _load_stdlib_code() -> ModuleType | None:
58+
"""Import the stdlib ``code`` module from disk without clobbering this package."""
59+
# If sitecustomize stashed the original module, prefer that.
60+
existing = sys.modules.get(_STDLIB_BOOTSTRAP_KEY)
61+
if existing:
62+
return existing
63+
64+
for module_path in _stdlib_candidates():
65+
spec = importlib.util.spec_from_file_location("code.__stdlib", module_path)
66+
if spec and spec.loader:
67+
module = importlib.util.module_from_spec(spec)
68+
spec.loader.exec_module(module)
69+
sys.modules.setdefault("code._stdlib", module)
70+
return module
71+
return None
72+
73+
74+
def __getattr__(name: str) -> Any:
75+
"""
76+
Lazily proxy missing attributes to the stdlib ``code`` module.
77+
78+
This keeps ``from code import InteractiveConsole`` working even though
79+
the project shadows the module name with a package.
80+
"""
81+
stdlib_module = _load_stdlib_code()
82+
if stdlib_module and hasattr(stdlib_module, name):
83+
value = getattr(stdlib_module, name)
84+
# Cache the attribute locally so repeated lookups are fast.
85+
globals()[name] = value
86+
return value
87+
raise AttributeError(f"module 'code' has no attribute '{name}'")
88+
89+
90+
def __dir__() -> list[str]:
91+
"""Combine local attributes with any public names from the stdlib module."""
92+
names = set(globals())
93+
stdlib_module = _load_stdlib_code()
94+
if stdlib_module:
95+
names.update(attr for attr in dir(stdlib_module) if not attr.startswith("_"))
96+
return sorted(names)
97+
98+
99+
# expose project submodules through attribute access
100+
def _expose_submodules() -> None:
101+
submodules = [
102+
"main_handler",
103+
"data_processing",
104+
"transfer.path_logic",
105+
]
106+
for dotted in submodules:
107+
short = dotted.split(".")[0]
108+
try:
109+
module = importlib.import_module(f"{__name__}.{dotted}")
110+
except ModuleNotFoundError:
111+
continue
112+
globals()[short] = importlib.import_module(f"{__name__}.{short}")
113+
sys.modules.setdefault(f"{__name__}.{short}", globals()[short])
114+
sys.modules.setdefault(f"{__name__}.{dotted}", module)
115+
116+
117+
_expose_submodules()
118+
119+
120+
# Populate __all__ with any names proxied from the stdlib module so
121+
# star-import behaviour mirrors the original as closely as possible.
122+
stdlib_module = _load_stdlib_code()
123+
if stdlib_module:
124+
proxy_names = [attr for attr in dir(stdlib_module) if not attr.startswith("_")]
125+
__all__.extend(sorted(proxy_names))

code/data_processing/cc_qc.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,12 @@ def cc_qc(self, df, threshold, TS=False):
8686
CATEGORY = 2
8787
print(f"FOR TASK SWITCHING -> Average accuracy at or below 0.5 across conditions and CATEGORY set to 2")
8888

89-
problematic_conditions = QC_UTILS.cond_block_not_reported(raw, self.ACC_COLUMN_NAME, self.COND_COLUMN_NAME, self.INCORRECT_SYMBOL)
89+
problematic_conditions = QC_UTILS.cond_block_not_reported(
90+
raw,
91+
self.COND_COLUMN_NAME,
92+
self.ACC_COLUMN_NAME,
93+
self.INCORRECT_SYMBOL,
94+
)
9095

9196
if len(problematic_conditions) != 0:
9297
CATEGORY = 3

code/data_processing/plot_utils.py

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,25 @@ def af_nf_plot(self, df):
2727
tuple: A tuple containing two Axes objects (count_plot, response_time_plot).
2828
"""
2929
# Filter to drop practice data
30-
test = df[df['block'] == 'test'].copy()
30+
block_series = df['block'].astype(str).str.strip().str.lower()
31+
test_mask = block_series == 'test'
32+
test = df[test_mask].copy()
33+
34+
if test.empty:
35+
subject_series = df.get('subject_id')
36+
if subject_series is not None:
37+
subjects = sorted(
38+
{str(value).strip() for value in subject_series.dropna() if str(value).strip()}
39+
)
40+
else:
41+
subjects = []
42+
43+
unique_blocks = sorted({value for value in block_series.unique() if value and value != 'nan'})
44+
raise ValueError(
45+
"No 'test' block rows available for plotting. "
46+
f"Observed block labels: {unique_blocks or '<none>'}. "
47+
f"Subjects in frame: {subjects or '<unknown>'}"
48+
)
3149

3250
# Generate count plot
3351
plt.figure(figsize=(10, 6))
@@ -402,7 +420,27 @@ def fn_plot(self, df):
402420
Returns:
403421
tuple: The scatter/box plot and bar chart plot objects.
404422
"""
405-
test = df[df['block'] == 'test']
423+
block_series = df['block'].astype(str).str.strip().str.lower()
424+
test_mask = block_series == 'test'
425+
test = df[test_mask].copy()
426+
427+
if test.empty:
428+
subject_series = df.get('subject_id')
429+
if subject_series is not None:
430+
subjects = sorted(
431+
{str(value).strip() for value in subject_series.dropna() if str(value).strip()}
432+
)
433+
else:
434+
subjects = []
435+
436+
unique_blocks = sorted({value for value in block_series.unique() if value and value != 'nan'})
437+
raise ValueError(
438+
"No 'test' block rows available for MEM plotting. "
439+
f"Observed block labels: {unique_blocks or '<none>'}. "
440+
f"Subjects in frame: {subjects or '<unknown>'}"
441+
)
442+
443+
test['block'] = test['block'].astype(str).str.strip()
406444
test['correct_label'] = test['correct'].map({0: 'Incorrect', 1: 'Correct'})
407445

408446
# Scatter and box plot
@@ -459,7 +497,27 @@ def sm_plot(self, df):
459497
Returns:
460498
The scatter and box plot object.
461499
"""
462-
test = df[df['block'] == 'test']
500+
block_series = df['block'].astype(str).str.strip().str.lower()
501+
test_mask = block_series == 'test'
502+
test = df[test_mask].copy()
503+
504+
if test.empty:
505+
subject_series = df.get('subject_id')
506+
if subject_series is not None:
507+
subjects = sorted(
508+
{str(value).strip() for value in subject_series.dropna() if str(value).strip()}
509+
)
510+
else:
511+
subjects = []
512+
513+
unique_blocks = sorted({value for value in block_series.unique() if value and value != 'nan'})
514+
raise ValueError(
515+
"No 'test' block rows available for MEM plotting. "
516+
f"Observed block labels: {unique_blocks or '<none>'}. "
517+
f"Subjects in frame: {subjects or '<unknown>'}"
518+
)
519+
520+
test['block'] = test['block'].astype(str).str.strip()
463521
mapping = {'no': 'Incongruent', 'yes': 'Congruent'}
464522
test['target_congruent'] = test['target_congruent'].map(mapping)
465523
test['correct_label'] = test['correct'].map({0: 'Incorrect', 1: 'Correct'})
@@ -595,9 +653,6 @@ def dwl_plot(self, df):
595653

596654

597655

598-
599-
600-
601656

602657

603658

0 commit comments

Comments
 (0)