Skip to content

Commit b963e3b

Browse files
committed
style: exclude vendor/ from black/isort and reformat our sources
CI lint failed because `black --check app rosemary core` walks into the `app/modules/generator/vendor/` tree we don't own. Added `extend-exclude` for `vendor/`, `node_modules/` and `migrations/` to both black and isort in pyproject.toml (flake8 was already excluded in the previous commit). Ran `black` + `isort` on the files we do own — routes.py and the four new generator test modules — so the CI `--check-only` pass is clean. No behaviour changes.
1 parent 404e550 commit b963e3b

6 files changed

Lines changed: 87 additions & 69 deletions

File tree

app/modules/generator/routes.py

Lines changed: 15 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -388,9 +388,15 @@ def step2():
388388
# dataclass (note: PROB_SUM/PROB_SUBSTRACT are the arithmetic +/−,
389389
# while PROB_SUM_FUNCTION/PROB_AVG_FUNCTION are the aggregates).
390390
_ARITH_KEYS = [
391-
"PROB_SUM", "PROB_SUBSTRACT", "PROB_MULTIPLY", "PROB_DIVIDE",
392-
"PROB_EQUALS", "PROB_LESS", "PROB_GREATER",
393-
"PROB_LESS_EQUALS", "PROB_GREATER_EQUALS",
391+
"PROB_SUM",
392+
"PROB_SUBSTRACT",
393+
"PROB_MULTIPLY",
394+
"PROB_DIVIDE",
395+
"PROB_EQUALS",
396+
"PROB_LESS",
397+
"PROB_GREATER",
398+
"PROB_LESS_EQUALS",
399+
"PROB_GREATER_EQUALS",
394400
]
395401
_AGG_KEYS = ["PROB_SUM_FUNCTION", "PROB_AVG_FUNCTION"]
396402
_STR_KEYS = ["PROB_LEN_FUNCTION"]
@@ -450,9 +456,7 @@ def step2():
450456
if _dist_total > 0:
451457
for k in _dist_keys:
452458
params_dict[k] = round(params_dict[k] / _dist_total, 6)
453-
params_dict[_dist_keys[-1]] += round(
454-
1.0 - sum(params_dict[k] for k in _dist_keys), 6
455-
)
459+
params_dict[_dist_keys[-1]] += round(1.0 - sum(params_dict[k] for k in _dist_keys), 6)
456460

457461
try:
458462
from fm_generator.FMGenerator.models.config import Params
@@ -794,28 +798,22 @@ def step3():
794798
request.form.get("prob_avg", params_dict.get("PROB_AVG_FUNCTION", 0.0))
795799
)
796800

797-
params_dict["PROB_SUM"] = _safe_float(
798-
request.form.get("prob_plus"), params_dict.get("PROB_SUM", 0.7)
799-
)
801+
params_dict["PROB_SUM"] = _safe_float(request.form.get("prob_plus"), params_dict.get("PROB_SUM", 0.7))
800802
params_dict["PROB_SUBSTRACT"] = _safe_float(
801803
request.form.get("prob_minus"), params_dict.get("PROB_SUBSTRACT", 0.2)
802804
)
803805
params_dict["PROB_MULTIPLY"] = _safe_float(
804806
request.form.get("prob_times"), params_dict.get("PROB_MULTIPLY", 0.1)
805807
)
806-
params_dict["PROB_DIVIDE"] = _safe_float(
807-
request.form.get("prob_div"), params_dict.get("PROB_DIVIDE", 0.0)
808-
)
808+
params_dict["PROB_DIVIDE"] = _safe_float(request.form.get("prob_div"), params_dict.get("PROB_DIVIDE", 0.0))
809809

810810
arithmetic_level_enabled = bool(params_dict.get("ARITHMETIC_LEVEL", False))
811811

812812
if arithmetic_level_enabled:
813813
params_dict["PROB_EQUALS"] = _safe_float(
814814
request.form.get("prob_eq"), params_dict.get("PROB_EQUALS", 0.1)
815815
)
816-
params_dict["PROB_LESS"] = _safe_float(
817-
request.form.get("prob_lt"), params_dict.get("PROB_LESS", 0.2)
818-
)
816+
params_dict["PROB_LESS"] = _safe_float(request.form.get("prob_lt"), params_dict.get("PROB_LESS", 0.2))
819817
params_dict["PROB_GREATER"] = _safe_float(
820818
request.form.get("prob_gt"), params_dict.get("PROB_GREATER", 0.7)
821819
)
@@ -934,9 +932,7 @@ def step3():
934932
if _bool_total > 0:
935933
for k in _bool_keys:
936934
params_dict[k] = round(params_dict[k] / _bool_total, 6)
937-
params_dict[_bool_keys[-1]] += round(
938-
1.0 - sum(params_dict[k] for k in _bool_keys), 6
939-
)
935+
params_dict[_bool_keys[-1]] += round(1.0 - sum(params_dict[k] for k in _bool_keys), 6)
940936

941937
# En step3 solo guardamos el estado; no reconstruimos Params aquí
942938
# porque params_dict puede contener todavía datos del step4.
@@ -1250,9 +1246,7 @@ def _collect_step4_attributes(form, params_dict):
12501246
use_in_constraints = raw_use and params_dict.get("ARITHMETIC_LEVEL", False)
12511247
elif type_ == "string":
12521248
use_in_constraints = (
1253-
raw_use
1254-
and params_dict.get("TYPE_LEVEL", False)
1255-
and params_dict.get("STRING_CONSTRAINTS", False)
1249+
raw_use and params_dict.get("TYPE_LEVEL", False) and params_dict.get("STRING_CONSTRAINTS", False)
12561250
)
12571251
else:
12581252
use_in_constraints = False

app/modules/generator/tests/test_params_contract.py

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
dict that Params can accept without raising. This is the contract test
1111
that would have caught 1.0007 and EXTRA_CONSTRAINT_REPRESENTATIVENESS=0.5.
1212
"""
13+
1314
from dataclasses import fields
1415

1516
from fm_generator.FMGenerator.models.config import Params
@@ -25,9 +26,15 @@ def test_arith_keys_exist_in_params():
2526
# function body in routes.py, not at module scope. We re-declare the
2627
# contract here so the test is self-documenting.
2728
arith = {
28-
"PROB_SUM", "PROB_SUBSTRACT", "PROB_MULTIPLY", "PROB_DIVIDE",
29-
"PROB_EQUALS", "PROB_LESS", "PROB_GREATER",
30-
"PROB_LESS_EQUALS", "PROB_GREATER_EQUALS",
29+
"PROB_SUM",
30+
"PROB_SUBSTRACT",
31+
"PROB_MULTIPLY",
32+
"PROB_DIVIDE",
33+
"PROB_EQUALS",
34+
"PROB_LESS",
35+
"PROB_GREATER",
36+
"PROB_LESS_EQUALS",
37+
"PROB_GREATER_EQUALS",
3138
}
3239
agg = {"PROB_SUM_FUNCTION", "PROB_AVG_FUNCTION"}
3340
str_keys = {"PROB_LEN_FUNCTION"}

app/modules/generator/tests/test_selenium.py

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
resolves, the browser-side stack is healthy — the actual `generate_models`
1010
call is already exercised server-side by `test_wizard_flow.py`.
1111
"""
12+
1213
import pytest
1314
from selenium.common.exceptions import TimeoutException
1415
from selenium.webdriver.common.by import By
@@ -25,14 +26,12 @@ def _submit_next(driver, expected_url_fragment, wait):
2526
We bypass `button.click()` because on steps 1-4 the Pyodide loading modal
2627
sits on top of the Next button and intercepts pointer events."""
2728
before = driver.current_url
28-
driver.execute_script(
29-
"""
29+
driver.execute_script("""
3030
const btn = document.querySelector("button[name='nav'][value='next']");
3131
const form = btn && btn.form;
3232
if (!form) throw new Error("next button or parent form missing");
3333
form.requestSubmit(btn);
34-
"""
35-
)
34+
""")
3635
try:
3736
wait.until(EC.url_contains(expected_url_fragment))
3837
except TimeoutException:
@@ -102,16 +101,12 @@ def test_wizard_reaches_step5_with_pyodide_ready():
102101
# Wait for Pyodide to finish booting. The bundle stores a Promise at
103102
# window.__generatorRuntime and resolves it once wheels + wrapper are
104103
# loaded; catching the resolution is the contract we care about.
105-
pyodide_wait.until(
106-
lambda d: d.execute_script(
107-
"""
104+
pyodide_wait.until(lambda d: d.execute_script("""
108105
const rt = window.__generatorRuntime;
109106
if (!rt) return false;
110107
return rt.then(() => window.__pyodideReady = true, () => window.__pyodideError = true),
111108
!!window.__pyodideReady || !!window.__pyodideError;
112-
"""
113-
)
114-
)
109+
"""))
115110
boot_error = driver.execute_script("return window.__pyodideError === true;")
116111
assert not boot_error, "Pyodide bootstrap rejected — check browser console"
117112
has_runtime = driver.execute_script(

app/modules/generator/tests/test_validators.py

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
and drive each branch with parametrised inputs. A regression here catches
55
the kind of locale/tolerance bugs that reached production.
66
"""
7+
78
from werkzeug.datastructures import MultiDict
89

910
from app.modules.generator.routes import (
@@ -13,9 +14,9 @@
1314
validate_step3_form,
1415
)
1516

16-
1717
# ── _safe_float ───────────────────────────────────────────────────────────
1818

19+
1920
def test_safe_float_accepts_dot():
2021
assert _safe_float("0.5") == 0.5
2122

@@ -39,6 +40,7 @@ def test_safe_float_empty_returns_default():
3940

4041
# ── step1 ─────────────────────────────────────────────────────────────────
4142

43+
4244
def test_step1_happy_path():
4345
errors, _ = validate_step1_form(MultiDict({"num_models_val": "5", "seed": "42"}))
4446
assert errors == {}
@@ -61,6 +63,7 @@ def test_step1_rejects_negative_seed():
6163

6264
# ── step2 ─────────────────────────────────────────────────────────────────
6365

66+
6467
def _valid_step2(**overrides):
6568
base = {
6669
"num_features_min": "5",
@@ -81,9 +84,7 @@ def test_step2_happy_path():
8184

8285

8386
def test_step2_rejects_min_gt_max():
84-
errors, _ = validate_step2_form(
85-
_valid_step2(num_features_min="30", num_features_max="10")
86-
)
87+
errors, _ = validate_step2_form(_valid_step2(num_features_min="30", num_features_max="10"))
8788
assert "num_features_max" in errors
8889

8990

@@ -116,6 +117,7 @@ def test_step2_rejects_clearly_wrong_sum():
116117

117118
# ── step3 ─────────────────────────────────────────────────────────────────
118119

120+
119121
def _valid_step3(**overrides):
120122
base = {
121123
"num_constraints_min": "1",
@@ -146,9 +148,7 @@ def test_step3_rejects_extra_constraint_repr_as_decimal():
146148

147149

148150
def test_step3_rejects_extra_constraint_repr_gt_vars_max():
149-
errors, _ = validate_step3_form(
150-
_valid_step3(extra_constraint_repr="10", vars_per_ctc_max="3")
151-
)
151+
errors, _ = validate_step3_form(_valid_step3(extra_constraint_repr="10", vars_per_ctc_max="3"))
152152
assert "extra_constraint_repr" in errors
153153

154154

@@ -161,26 +161,25 @@ def test_step3_boolops_sum_within_tolerance():
161161

162162

163163
def test_step3_boolops_sum_wildly_wrong():
164-
errors, _ = validate_step3_form(
165-
_valid_step3(prob_and="0.9", prob_or="0.9", prob_implies="0", prob_equiv="0")
166-
)
164+
errors, _ = validate_step3_form(_valid_step3(prob_and="0.9", prob_or="0.9", prob_implies="0", prob_equiv="0"))
167165
assert "boolop_sum" in errors
168166
assert "Current sum" in errors["boolop_sum"]
169167

170168

171169
def test_step3_arithmetic_only_when_level_enabled():
172170
"""If arithmetic_level is off, the arithmetic-sum check should not fire."""
173-
errors, _ = validate_step3_form(
174-
_valid_step3(prob_plus="0", prob_minus="0", prob_times="0", prob_div="0")
175-
)
171+
errors, _ = validate_step3_form(_valid_step3(prob_plus="0", prob_minus="0", prob_times="0", prob_div="0"))
176172
assert "arithmetic_sum" not in errors
177173

178174

179175
def test_step3_arithmetic_sum_enforced_when_level_on():
180176
errors, _ = validate_step3_form(
181177
_valid_step3(
182178
arithmetic_level="on",
183-
prob_plus="0.9", prob_minus="0.9", prob_times="0", prob_div="0",
179+
prob_plus="0.9",
180+
prob_minus="0.9",
181+
prob_times="0",
182+
prob_div="0",
184183
)
185184
)
186185
assert "arithmetic_sum" in errors

app/modules/generator/tests/test_wizard_flow.py

Lines changed: 31 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,12 @@
1010
- Back navigation preserving state
1111
- /generator/random and /generator/llm routes
1212
"""
13+
1314
import json
1415

1516
import pytest
16-
1717
from fm_generator.FMGenerator.models.config import Params
1818

19-
2019
STEP1 = {"num_models_val": "3", "seed": "42", "name_prefix": "fm"}
2120
STEP2 = {
2221
"num_features_min": "5",
@@ -66,6 +65,7 @@ def _walk_happy_path(client, step2=None, step3=None, step4=None):
6665

6766
# ── Routes & top-level shape ─────────────────────────────────────────────
6867

68+
6969
def test_landing_is_reachable(client):
7070
assert client.get("/generator").status_code == 200
7171
assert client.get("/generator/").status_code == 200
@@ -85,6 +85,7 @@ def test_llm_placeholder_renders(client):
8585

8686
# ── Session recovery ─────────────────────────────────────────────────────
8787

88+
8889
def test_advancing_with_no_session_redirects_to_landing(client):
8990
"""Formerly returned a 400 "Error: Params missing in session" — now we
9091
redirect the user back to /generator so they can restart cleanly. This
@@ -97,6 +98,7 @@ def test_advancing_with_no_session_redirects_to_landing(client):
9798

9899
# ── Happy path + contract ────────────────────────────────────────────────
99100

101+
100102
def test_full_happy_path_produces_valid_params(client):
101103
_walk_happy_path(client)
102104
r = client.get("/generator/random/params-json")
@@ -114,49 +116,51 @@ def test_step5_renders_when_session_ready(client):
114116

115117
# ── Regression: 1.0007 slider sum ─────────────────────────────────────────
116118

119+
117120
def test_parent_child_slider_sum_1p0007_renormalises(client):
118121
"""The slider rounds each segment to 4 decimals, which can leave up to
119122
0.0007 residue. The route must renormalise to exactly 1.0 before
120123
constructing Params."""
121124
client.post("/generator/random/step1", data=STEP1)
122125
poisoned = dict(STEP2)
123-
poisoned.update({
124-
"dist_optional": "0.2502",
125-
"dist_mandatory": "0.2502",
126-
"dist_alternative": "0.2502",
127-
"dist_or": "0.2501",
128-
})
126+
poisoned.update(
127+
{
128+
"dist_optional": "0.2502",
129+
"dist_mandatory": "0.2502",
130+
"dist_alternative": "0.2502",
131+
"dist_or": "0.2501",
132+
}
133+
)
129134
r = client.post("/generator/random/step2", data=poisoned)
130135
assert r.status_code == 302 # reached step3, Params built OK
131136

132137
params = json.loads(client.get("/generator/random/params-json").data)
133138
total = (
134-
params["DIST_OPTIONAL"] + params["DIST_MANDATORY"]
135-
+ params["DIST_ALTERNATIVE"] + params["DIST_OR"]
139+
params["DIST_OPTIONAL"]
140+
+ params["DIST_MANDATORY"]
141+
+ params["DIST_ALTERNATIVE"]
142+
+ params["DIST_OR"]
136143
+ params["DIST_GROUP_CARDINALITY"]
137144
)
138145
assert abs(total - 1.0) < 1e-6
139146

140147

141148
# ── Regression: boolean-ops sum residue ───────────────────────────────────
142149

150+
143151
def test_boolean_ops_residue_renormalises(client):
144152
_walk_happy_path(
145153
client,
146-
step3={**STEP3,
147-
"prob_and": "0.3334", "prob_or": "0.3333",
148-
"prob_implies": "0.1667", "prob_equiv": "0.1666"},
154+
step3={**STEP3, "prob_and": "0.3334", "prob_or": "0.3333", "prob_implies": "0.1667", "prob_equiv": "0.1666"},
149155
)
150156
params = json.loads(client.get("/generator/random/params-json").data)
151-
total = (
152-
params["PROB_AND"] + params["PROB_OR_CT"]
153-
+ params["PROB_IMPLICATION"] + params["PROB_EQUIVALENCE"]
154-
)
157+
total = params["PROB_AND"] + params["PROB_OR_CT"] + params["PROB_IMPLICATION"] + params["PROB_EQUIVALENCE"]
155158
assert abs(total - 1.0) < 1e-6
156159

157160

158161
# ── Regression: EXTRA_CONSTRAINT_REPRESENTATIVENESS "0.5" ─────────────────
159162

163+
160164
def test_extra_constraint_representativeness_is_int_in_session(client):
161165
_walk_happy_path(client)
162166
params = json.loads(client.get("/generator/random/params-json").data)
@@ -166,20 +170,26 @@ def test_extra_constraint_representativeness_is_int_in_session(client):
166170

167171
# ── Regression: Spanish-locale decimal comma ──────────────────────────────
168172

173+
169174
def test_spanish_locale_decimal_comma_is_accepted(client):
170175
_walk_happy_path(
171176
client,
172-
step3={**STEP3,
173-
"prob_not": "0,3",
174-
"prob_and": "0,4", "prob_or": "0,2",
175-
"prob_implies": "0,2", "prob_equiv": "0,2"},
177+
step3={
178+
**STEP3,
179+
"prob_not": "0,3",
180+
"prob_and": "0,4",
181+
"prob_or": "0,2",
182+
"prob_implies": "0,2",
183+
"prob_equiv": "0,2",
184+
},
176185
)
177186
params = json.loads(client.get("/generator/random/params-json").data)
178187
assert params["PROB_NOT"] == pytest.approx(0.3)
179188

180189

181190
# ── Regression: back navigation keeps state ───────────────────────────────
182191

192+
183193
def test_back_nav_from_step3_preserves_step2_choices(client):
184194
# Advance to step3 with a specific max_features
185195
client.post("/generator/random/step1", data=STEP1)

0 commit comments

Comments
 (0)