Skip to content

Commit 85fff64

Browse files
author
Валенчиц Александр Александрович
committed
pylint
1 parent fa38e7e commit 85fff64

10 files changed

Lines changed: 143 additions & 109 deletions

File tree

.cursor/scratchpad.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# Scratchpad
22

3-
PART 2: weight_fields/__all__, min_score+score+text, ASYNC_INDEXING+tasks, pgvector, OpenAI/Cohere embeddings, docs, tests.
3+
Pylint CI: `_parse_float_param` один return + `math.isnan`; `_stringify_numeric_param`; переносы строк в views; фикстуры через `fixture(name=...)` (W0621); pgvector/settings/tasks длина строк; `tasks.delete_instance_task` сигнатура.
44

5-
DONE
5+
DONE`pylint $(git ls-files '*.py')` 10/10; pytest по затронутым модулям 16 passed. Полный pytest: 2 fail в `test_langgraph_search` (Searcher + LANGGRAPH), вне этого PR.

src/django_graph_search/backends/pgvector.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,10 @@ def search(
160160
results: List[SearchResult] = []
161161
for row in rows:
162162
doc_id, metadata_raw, score = row
163-
meta = metadata_raw if isinstance(metadata_raw, dict) else json.loads(metadata_raw or "{}")
163+
if isinstance(metadata_raw, dict):
164+
meta = metadata_raw
165+
else:
166+
meta = json.loads(metadata_raw or "{}")
164167
s = max(0.0, min(1.0, float(score)))
165168
results.append(SearchResult(id=str(doc_id), score=s, metadata=meta))
166169
return results

src/django_graph_search/permissions.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,7 @@ def _parse_rate(rate: str) -> Tuple[int, float]:
185185
"minute": 60.0,
186186
"hour": 3600.0,
187187
}.get(unit, 60.0)
188-
if num < 1:
189-
num = 1
188+
num = max(num, 1)
190189
return num, period
191190
except (ValueError, AttributeError):
192191
log.warning("Invalid THROTTLE_RATES entry %r — ignoring.", rate)

src/django_graph_search/settings.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,9 @@ def _build_async_indexing_config(payload: Dict[str, Any]) -> AsyncIndexingConfig
361361
merged = _merge_dicts(defaults, payload)
362362
backend = str(merged.get("BACKEND") or "celery").lower()
363363
if backend not in {"celery", "django_q", "thread"}:
364-
raise ConfigurationError("ASYNC_INDEXING.BACKEND must be 'celery', 'django_q', or 'thread'.")
364+
raise ConfigurationError(
365+
"ASYNC_INDEXING.BACKEND must be 'celery', 'django_q', or 'thread'."
366+
)
365367
task_path = merged.get("CELERY_TASK_PATH") or defaults["CELERY_TASK_PATH"]
366368
if not isinstance(task_path, str):
367369
raise ConfigurationError("ASYNC_INDEXING.CELERY_TASK_PATH must be a string.")

src/django_graph_search/tasks.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -121,9 +121,14 @@ def index_instance_task(app_label: str, model_name: str, pk: Any) -> None: # ty
121121
)
122122
index_instance_task_fn(app_label, model_name, pk)
123123

124-
def delete_instance_task(app_label: str, model_name: str, pk: Any) -> None: # type: ignore[misc]
124+
def delete_instance_task( # type: ignore[misc]
125+
app_label: str, model_name: str, pk: Any
126+
) -> None:
125127
log.warning(
126-
"Celery not installed. Falling back to sync delete index for %s.%s pk=%s",
128+
(
129+
"Celery not installed. Falling back to sync delete index "
130+
"for %s.%s pk=%s"
131+
),
127132
app_label,
128133
model_name,
129134
pk,

src/django_graph_search/views.py

Lines changed: 82 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
import json
44
import logging
5+
import math
56
import queue
67
import threading
78
import uuid
@@ -87,47 +88,43 @@ def _parse_float_param(
8788
Returns:
8889
Кортеж ``(parsed, None)`` при успехе или ``(None, JsonResponse)`` при ошибке.
8990
"""
91+
bad = JsonResponse(
92+
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
93+
status=400,
94+
)
95+
out: Optional[float] = None
96+
err: Optional[JsonResponse] = None
97+
9098
if value is None or value == "":
91-
return default, None
92-
if isinstance(value, bool):
93-
return None, JsonResponse(
94-
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
95-
status=400,
96-
)
97-
if isinstance(value, (int, float)):
98-
parsed = float(value)
99+
out = default
100+
elif isinstance(value, bool):
101+
err = bad
102+
elif isinstance(value, (int, float)):
103+
out = float(value)
99104
elif isinstance(value, str):
100105
stripped = value.strip()
101106
if not stripped:
102-
return default, None
103-
try:
104-
parsed = float(stripped)
105-
except ValueError:
106-
return None, JsonResponse(
107-
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
108-
status=400,
109-
)
107+
out = default
108+
else:
109+
try:
110+
out = float(stripped)
111+
except ValueError:
112+
err = bad
110113
else:
111-
return None, JsonResponse(
112-
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
113-
status=400,
114-
)
115-
if parsed != parsed: # NaN
116-
return None, JsonResponse(
117-
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
118-
status=400,
119-
)
120-
if min_value is not None and parsed < min_value:
121-
return None, JsonResponse(
122-
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
123-
status=400,
124-
)
125-
if max_value is not None and parsed > max_value:
126-
return None, JsonResponse(
127-
{"error": f"'{param_name}' must be a float between 0.0 and 1.0."},
128-
status=400,
129-
)
130-
return parsed, None
114+
err = bad
115+
116+
if err is None and out is not None:
117+
if math.isnan(out):
118+
err = bad
119+
out = None
120+
elif min_value is not None and out < min_value:
121+
err = bad
122+
out = None
123+
elif max_value is not None and out > max_value:
124+
err = bad
125+
out = None
126+
127+
return out, err
131128

132129

133130
def _stringify_numeric_param(
@@ -144,31 +141,32 @@ def _stringify_numeric_param(
144141
Returns:
145142
``(строка_цифр_или_None, None)`` либо ``(None, JsonResponse)``.
146143
"""
147-
if value is None or value == "":
148-
return None, None
149-
if isinstance(value, bool):
150-
return None, JsonResponse(
151-
{"error": f"'{param_name}' must be a positive integer."},
152-
status=400,
153-
)
154-
if isinstance(value, int):
155-
return str(value), None
156-
if isinstance(value, float):
157-
if not value.is_integer():
158-
return None, JsonResponse(
159-
{"error": f"'{param_name}' must be a positive integer."},
160-
status=400,
161-
)
162-
return str(int(value)), None
163-
if isinstance(value, str):
164-
stripped = value.strip()
165-
if not stripped:
166-
return None, None
167-
return stripped, None
168-
return None, JsonResponse(
144+
invalid = JsonResponse(
169145
{"error": f"'{param_name}' must be a positive integer."},
170146
status=400,
171147
)
148+
out: Optional[str] = None
149+
err: Optional[JsonResponse] = None
150+
151+
if value is None or value == "":
152+
pass
153+
elif isinstance(value, bool):
154+
err = invalid
155+
elif isinstance(value, int):
156+
out = str(value)
157+
elif isinstance(value, float):
158+
if not value.is_integer():
159+
err = invalid
160+
else:
161+
out = str(int(value))
162+
elif isinstance(value, str):
163+
stripped = value.strip()
164+
if stripped:
165+
out = stripped
166+
else:
167+
err = invalid
168+
169+
return out, err
172170

173171

174172
class SearchPermissionMixin:
@@ -442,10 +440,20 @@ class StreamingSearchAPIView(SearchPermissionMixin, View):
442440
marker.
443441
"""
444442

445-
def get(self, request: HttpRequest, *args: Any, **kwargs: Any) -> StreamingHttpResponse | JsonResponse:
443+
def get(
444+
self,
445+
request: HttpRequest,
446+
*args: Any,
447+
**kwargs: Any,
448+
) -> StreamingHttpResponse | JsonResponse:
446449
return self._handle(request)
447450

448-
def post(self, request: HttpRequest, *args: Any, **kwargs: Any) -> StreamingHttpResponse | JsonResponse:
451+
def post(
452+
self,
453+
request: HttpRequest,
454+
*args: Any,
455+
**kwargs: Any,
456+
) -> StreamingHttpResponse | JsonResponse:
449457
return self._handle(request)
450458

451459
def _handle(self, request: HttpRequest) -> StreamingHttpResponse | JsonResponse:
@@ -557,12 +565,24 @@ def _extract_payload(request: HttpRequest) -> Dict[str, Any]:
557565
def _format_event(event: Dict[str, Any], fmt: str) -> bytes:
558566
payload = json.dumps(event, ensure_ascii=False, default=str)
559567
if fmt == "sse":
560-
return f"event: {event.get('type', 'message')}\ndata: {payload}\n\n".encode("utf-8")
568+
event_type = event.get("type", "message")
569+
lines = (
570+
f"event: {event_type}\n"
571+
f"data: {payload}\n\n"
572+
)
573+
return lines.encode("utf-8")
561574
return (payload + "\n").encode("utf-8")
562575

563576

564577
class SimilarAPIView(View):
565-
def get(self, request: HttpRequest, model: str, pk: str, *args: Any, **kwargs: Any) -> JsonResponse:
578+
def get(
579+
self,
580+
request: HttpRequest,
581+
model: str,
582+
pk: str,
583+
*args: Any,
584+
**kwargs: Any,
585+
) -> JsonResponse:
566586
if "." not in model:
567587
return JsonResponse({"error": "Model must be in 'app.Model' format."}, status=400)
568588
app_label, model_name = model.split(".", 1)

tests/test_conversational.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def _minimal_graph_search(extra: Dict[str, Any] | None = None) -> Dict[str, Any]
3434
return base
3535

3636

37-
@pytest.fixture
38-
def conv_settings():
37+
@pytest.fixture(name="apply_conv_settings")
38+
def _apply_conv_settings_fixture():
3939
original = getattr(django_settings, "GRAPH_SEARCH", None)
4040
original_debug = django_settings.DEBUG
4141
get_settings.cache_clear()
@@ -58,8 +58,8 @@ def _apply(payload: Dict[str, Any], *, debug: bool):
5858

5959

6060
@pytest.mark.django_db
61-
def test_inmemory_emits_runtime_warning_when_not_debug(conv_settings):
62-
conv_settings(_minimal_graph_search(), debug=False)
61+
def test_inmemory_emits_runtime_warning_when_not_debug(apply_conv_settings):
62+
apply_conv_settings(_minimal_graph_search(), debug=False)
6363
factory = RequestFactory()
6464
request = factory.post(
6565
"/api/search/conversation/",
@@ -78,8 +78,8 @@ def test_inmemory_emits_runtime_warning_when_not_debug(conv_settings):
7878

7979

8080
@pytest.mark.django_db
81-
def test_inmemory_no_warning_in_debug(conv_settings):
82-
conv_settings(_minimal_graph_search(), debug=True)
81+
def test_inmemory_no_warning_in_debug(apply_conv_settings):
82+
apply_conv_settings(_minimal_graph_search(), debug=True)
8383
factory = RequestFactory()
8484
request = factory.post(
8585
"/api/search/conversation/",
@@ -91,5 +91,10 @@ def test_inmemory_no_warning_in_debug(conv_settings):
9191
with mock.patch("django_graph_search.views.Searcher") as sc:
9292
sc.return_value.search.return_value = []
9393
ConversationalSearchAPIView.as_view()(request)
94-
runtime = [w for w in wrec if issubclass(w.category, RuntimeWarning) and "inmemory" in str(w.message)]
94+
runtime = [
95+
w
96+
for w in wrec
97+
if issubclass(w.category, RuntimeWarning)
98+
and "inmemory" in str(w.message)
99+
]
95100
assert not runtime

tests/test_permissions.py

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ def _minimal_graph_search(extra: Dict[str, Any] | None = None) -> Dict[str, Any]
3434
return base
3535

3636

37-
@pytest.fixture
38-
def api_settings():
37+
@pytest.fixture(name="apply_api_settings")
38+
def _apply_api_settings_fixture():
3939
original = getattr(django_settings, "GRAPH_SEARCH", None)
4040
get_settings.cache_clear()
4141
SimpleScopedRateThrottle._windows.clear()
@@ -65,15 +65,15 @@ def allow_permission(request):
6565
return True
6666

6767

68-
def test_check_permissions_empty_allows(api_settings):
69-
cfg = api_settings(_minimal_graph_search())
68+
def test_check_permissions_empty_allows(apply_api_settings):
69+
cfg = apply_api_settings(_minimal_graph_search())
7070
factory = RequestFactory()
7171
request = factory.get("/api/search/", {"q": "x"})
7272
check_permissions(request, cfg)
7373

7474

75-
def test_check_permissions_callable_deny(api_settings):
76-
cfg = api_settings(
75+
def test_check_permissions_callable_deny(apply_api_settings):
76+
cfg = apply_api_settings(
7777
_minimal_graph_search(
7878
{"API": {"PERMISSION_CLASSES": ["tests.test_permissions.deny_permission"]}}
7979
)
@@ -84,8 +84,8 @@ def test_check_permissions_callable_deny(api_settings):
8484
check_permissions(request, cfg)
8585

8686

87-
def test_check_permissions_callable_allow(api_settings):
88-
cfg = api_settings(
87+
def test_check_permissions_callable_allow(apply_api_settings):
88+
cfg = apply_api_settings(
8989
_minimal_graph_search(
9090
{"API": {"PERMISSION_CLASSES": ["tests.test_permissions.allow_permission"]}}
9191
)
@@ -95,15 +95,15 @@ def test_check_permissions_callable_allow(api_settings):
9595
check_permissions(request, cfg)
9696

9797

98-
def test_check_throttle_skipped_when_no_classes(api_settings):
99-
cfg = api_settings(_minimal_graph_search())
98+
def test_check_throttle_skipped_when_no_classes(apply_api_settings):
99+
cfg = apply_api_settings(_minimal_graph_search())
100100
factory = RequestFactory()
101101
request = factory.get("/api/search/", {"q": "x"})
102102
check_throttle(request, cfg)
103103

104104

105-
def test_simple_scoped_throttle_blocks_second_request(api_settings):
106-
cfg = api_settings(
105+
def test_simple_scoped_throttle_blocks_second_request(apply_api_settings):
106+
cfg = apply_api_settings(
107107
_minimal_graph_search(
108108
{
109109
"API": {
@@ -124,10 +124,10 @@ def test_simple_scoped_throttle_blocks_second_request(api_settings):
124124

125125

126126
@pytest.mark.django_db
127-
def test_search_api_view_returns_403_when_permission_denied(api_settings):
127+
def test_search_api_view_returns_403_when_permission_denied(apply_api_settings):
128128
from django_graph_search.views import SearchAPIView
129129

130-
api_settings(
130+
apply_api_settings(
131131
_minimal_graph_search(
132132
{"API": {"PERMISSION_CLASSES": ["tests.test_permissions.deny_permission"]}}
133133
)

tests/test_signals_async.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
from .test_app.models import Category, Product
1414

1515

16-
@pytest.fixture
17-
def graph_search_signal_settings():
16+
@pytest.fixture(name="graph_search_signal_settings")
17+
def _graph_search_signal_settings_fixture():
1818
original = getattr(django_settings, "GRAPH_SEARCH", None)
1919
get_settings.cache_clear()
2020

0 commit comments

Comments
 (0)