diff --git a/.claude/skills/tina4-developer-python/SKILL.md b/.claude/skills/tina4-developer-python/SKILL.md index 253bc55b..bfb9d59d 100644 --- a/.claude/skills/tina4-developer-python/SKILL.md +++ b/.claude/skills/tina4-developer-python/SKILL.md @@ -230,8 +230,11 @@ the developer can correct. ### 1. Keep the main session free — delegate to a worker When the developer gives an instruction, don't do the work inline. **Allocate it to a plan, then spawn a separate worker to execute it**, so the main session is always free for the next input. -Tina4 **hot-reloads on save** (DevReload), so as the worker edits routes, models, and templates the -developer watches the interface change **live in the browser** — keeping the main session open is +Under `tina4 serve` the file watcher reloads on save: templates and static files refresh the browser, +and an edited or NEW file under `src/` (routes, models) is re-imported through `POST /__dev/api/reload` +with no restart. A bare `python app.py` has no watcher, so a route or model file added there needs a +restart. The MCP `route_list` tool reads the live route registry, so it reflects a new file once the reload +has run. As the worker edits, the developer watches the interface change **live in the browser** — keeping the main session open is what lets them observe and steer while the work happens. The main agent scopes, dispatches, and reports; workers build and update the plan. When a worker finishes an item, surface it to the developer. Whoever builds updates `plan/.md` in the **same turn** they claim progress: @@ -415,13 +418,13 @@ whenever the dev server is running (`tina4 serve` with `TINA4_DEBUG=true`): - **`api_search("render template")`** — ranked search across framework + your own code; returns fqn, signature, file:line. Run it BEFORE assuming a method exists. - **`api_class("Frond")`** — every method on a class, with signatures. A bare name (`Frond`), an import path, or the full fqn all resolve. -- **`api_method("Frond", "add_test")`** — exact signature, params, return type, file and line for one method. +- **`api_method`** with arguments `class` and `name` (e.g. `class="Frond"`, `name="add_test"`) — exact signature, a structured `params` list (`name`, `type`, `required`, `default`), `return` type, file and line for one method. Omit an argument and it answers `missing required argument 'name' (api_method takes class, name)`. - **`code_search("where is the auth token issued?")`** — fuzzy/semantic full-text search over **THIS project's own source + docs** (the native `Context` FTS5 index — zero-dep, kept live on every file save). Ranks the file that *defines* a symbol above tests that merely mention it. The in-repo, semantic counterpart to `api_*`. ``` api_search("queue consume") -> finds Queue.consume and its signature api_class("Database") -> every method on Database, with signatures -api_method("Frond", "add_test") -> add_test(name, fn) +api_method(class="Frond", name="add_test") -> add_test(name, fn) code_search("send an email") -> the routes/services in YOUR app that already do it ``` diff --git a/AGENTS.md b/AGENTS.md index 410dab07..2f1122cb 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # Tina4 Python — Agent Instructions -v3.13.146. 140 cataloged features, zero dependencies. Python 3.12+. +v3.13.147. 140 cataloged features, zero dependencies. Python 3.12+. ## AI Skills diff --git a/CHANGELOG.md b/CHANGELOG.md index 11646b17..7058e7c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,14 @@ https://tina4.com/python/36-releases This file records framework-specific changes. The release notes above remain the authority for shipped versions. +## 3.13.147 — 2026-10-05 +### Dev MCP tools (fixes tina4-php#271) +- `database_columns` reads schema metadata, so an empty table returns its columns; a missing table returns a clear `table not found` error. +- Tool argument validation returns an actionable `missing/unknown argument` error instead of a raw language error or 500; `api_method` now populates `params` and `return`. +- `route_list` entries include each route's `middleware`, so a middleware-guarded route is distinguishable from an open one. +- A new route file added while the server runs surfaces a signal instead of silence. +- Developer skill corrected: per-language hot-reload reality, and `api_method` shown with argument names. + ## 3.13.146 — 2026-10-04 ### AI skills - Every skill now opens with a content list (table of contents) and a degrees-of-freedom legend (what is inviolable, what is a default, what is judgement). diff --git a/CLAUDE.md b/CLAUDE.md index e7cafbb4..37bc88c4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -15,7 +15,7 @@ The full discipline lives in `.claude/skills/tina4-maintainer/SKILL.md`; this bl # Tina4 Python -Version 3.13.146 - Lightweight Python web framework. See https://tina4.com for full documentation. +Version 3.13.147 - Lightweight Python web framework. See https://tina4.com for full documentation. ## Build & Test @@ -939,7 +939,7 @@ uv run tina4python test # Discovers @tests in src/**/*.py - SSE/Streaming via `response.stream()` — Server-Sent Events support for real-time data push. Pass an async generator; framework handles chunked transfer encoding, `text/event-stream` content type, and connection keep-alive - MCP server (`tina4_python.mcp`): built-in dev tools auto-start when MCP is a capability of the deployment. Developer API: `McpServer`, `@mcp_tool`, `@mcp_resource`. JSON-RPC 2.0 over SSE. **Security is a two-layer gate (v3.13.40):** `is_enabled()` is a pure capability gate (explicit `TINA4_MCP` wins, else `TINA4_DEBUG`; host-independent), and `is_request_allowed(remote_ip, has_valid_token)` authorises each request on the RAW socket peer (`request.remote_ip`, never X-Forwarded-For): loopback always; a remote caller needs `TINA4_MCP_REMOTE=true` AND a token matching `TINA4_MCP_TOKEN` (never `TINA4_API_KEY`, ADR-0078; sent as Authorization Bearer / X-MCP-Token; no configured token means remote is always denied). Every `/__dev` request (reads too), the MCP endpoints and the `/__dev_reload` socket also require a loopback Host (`localhost`, `127.0.0.1`, `[::1]` or `TINA4_HOST`) and refuse `Sec-Fetch-Site: same-site`/`cross-site` (ADR-0078). `/health` omits `version` outside debug. All MCP surfaces (REST shim, JSON-RPC, SSE) 404 a disallowed caller. `database_query` is SELECT/WITH-only and rejects stacked statements; the file tools are sandboxed to the project root. `is_localhost()` is informational only, not the gate - Tests: 6,088 passing, 0 failures, **0 skipped** — measured 2026-09-17 on the lab (Ubuntu 24.04.4 LTS x86_64, Python 3.13.13, live services, `TINA4_REQUIRE_SERVICES=1`, 604s, run as root). **Firebird is NOT excluded.** The lab provisions a live Firebird 5 (`firebirdsql/firebird:5` on :3050, `TINA4_TEST_FIREBIRD_URL`) and those tests run. What IS deliberate is Firebird's absence from the `TINA4_REQUIRE_SERVICES` keyword gate in `tests/conftest.py`: GitHub CI does not provision Firebird, so a Firebird skip has to stay green *there*. Those are two different things and this line used to conflate them into "excluded by design", which read as "the Firebird tests do not run" — they do. **Nothing skips any more.** Reaching 0 skips now also needs the graph engines (Neo4j, Memgraph, ArangoDB, Ultipa, wired via `TINA4_TEST_NEO4J_URL`/`_MEMGRAPH_URL`/`_ARANGO_URL`/`_ULTIPA_URL` + the `graph` extra) and the lab Keycloak OIDC realm (`TINA4_REQUIRE_OIDC=1`), both added after the August baseline; without them `test_graph.py` (Feature 139) and the real OIDC contract test skip. The last skip was `tests/test_session_backend_failure.py` `[needs:no-dac-override]`: the suite runs as root, root holds `CAP_DAC_OVERRIDE`, so a write went straight through a 0400 file and no real `EACCES` was reachable — and as root the second `save()` returned `True`, so that skip was hiding a FAILING assertion, not merely an unrunnable one. The test now stops being root for the length of the failing write (`os.seteuid` to `nobody`; the saved uid stays 0, so a `finally` always restores it) and the kernel raises the genuine errno-13 the test asserts. Two parts of that are load-bearing: the log directory is handed to the same uid, because dropping the uid otherwise denies `Log.error` as well and the EACCES under test goes unrecorded (`_LogWriter.write` swallows `OSError` unless `TINA4_LOG_STRICT`); and the fixture root is its own 0755 `mkdtemp` rather than `tmp_path`, whose 0700 root-owned parents make every denial a directory traversal, which would pass for the wrong reason. Re-measure with `.venv/bin/python -m pytest tests/ -q` and quote the summary line, never the exit code -- Version: 3.13.146 +- Version: 3.13.147 ## Links diff --git a/pyproject.toml b/pyproject.toml index d597155c..3bafe3e3 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "tina4-python" -version = "3.13.146" +version = "3.13.147" description = "Tina4 Python v3 — Zero-dependency, lightweight web framework" authors = [ {name = "Andre van Zuydam", email = "andrevanzuydam@gmail.com"} diff --git a/tests/test_mcp_dev_tools_271.py b/tests/test_mcp_dev_tools_271.py new file mode 100644 index 00000000..aa01bf90 --- /dev/null +++ b/tests/test_mcp_dev_tools_271.py @@ -0,0 +1,175 @@ +# Copyright (c) 2026 Code Infinity +# SPDX-License-Identifier: MPL-2.0 +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + +""" +Regression tests for tina4-php#271 (dev MCP tool rough edges), Python reference. + +No mocks: a real McpServer with the real dev tools, driven through the real +JSON-RPC ``tools/call`` path (``handle_message``), against a real SQLite +database that includes an EMPTY table and real registered routes. + + P1 database_columns reports schema columns for an empty table; missing table errors. + P2 tool arguments are validated against the input schema before the call. + P3 route_list carries each route's middleware names. + P4 a route file added while running registers and shows up in route_list. +""" +import json +import os +import sys +import textwrap + +import pytest + + +class AuditMiddleware: + """Real middleware class attached to a noauth route in the tests.""" + + @staticmethod + def before_audit(request, response): + return request, response + + +@pytest.fixture +def mcp(tmp_path, monkeypatch): + import tina4_python.core.router as router_mod + import tina4_python.orm.model as orm_model + from tina4_python.core.router import get, noauth, middleware + from tina4_python.database import Database + from tina4_python.mcp import McpServer + from tina4_python.mcp.tools import register_dev_tools + from tina4_python.orm import bind_database + + old_cwd = os.getcwd() + old_database = orm_model._database + old_databases = dict(orm_model._databases) + old_routes = list(router_mod._routes) + monkeypatch.setenv("TINA4_DEBUG", "true") + monkeypatch.setenv("TINA4_MCP", "true") + monkeypatch.delenv("TINA4_DATABASE_URL", raising=False) + monkeypatch.syspath_prepend(str(tmp_path)) + (tmp_path / "src" / "routes").mkdir(parents=True) + (tmp_path / "src" / "__init__.py").write_text("") + (tmp_path / "src" / "routes" / "__init__.py").write_text("") + os.chdir(tmp_path) + + db = Database(f"sqlite:///{tmp_path / 'm271.db'}") + bind_database(db) + db.execute("CREATE TABLE empty_widgets (id INTEGER PRIMARY KEY, label TEXT NOT NULL, qty INTEGER)") + db.commit() + + @noauth() + @middleware(AuditMiddleware) + @get("/m271/guarded") + async def _guarded(request, response): + return response({"ok": True}) + + @get("/m271/plain") + async def _plain(request, response): + return response({"ok": True}) + + server = McpServer("/__dev/mcp", name="m271") + register_dev_tools(server) + request_id = [0] + + def call(tool, arguments=None): + request_id[0] += 1 + raw = server.handle_message({ + "jsonrpc": "2.0", "id": request_id[0], "method": "tools/call", + "params": {"name": tool, "arguments": arguments if arguments is not None else {}}, + }) + message = json.loads(raw) + assert "error" not in message, f"{tool} escaped as a JSON-RPC error: {message}" + return json.loads(message["result"]["content"][0]["text"]) + + call.server_tools = lambda: server._handle_tools_list({})["tools"] + yield call + + os.chdir(old_cwd) + orm_model._database = old_database + orm_model._databases.clear() + orm_model._databases.update(old_databases) + router_mod._routes[:] = old_routes + for module_name in list(sys.modules): + if module_name == "src" or module_name.startswith("src."): + del sys.modules[module_name] + + +# ── P1 ────────────────────────────────────────────────────────── + +def test_database_columns_reports_columns_of_an_empty_table(mcp): + columns = mcp("database_columns", {"table": "empty_widgets"}) + by_name = {column["name"]: column for column in columns} + assert list(by_name) == ["id", "label", "qty"] + assert by_name["label"]["type"] == "TEXT" and by_name["label"]["nullable"] is False + assert by_name["id"]["primary_key"] is True + + +def test_database_columns_missing_table_is_a_clear_error_not_empty(mcp): + result = mcp("database_columns", {"table": "no_such_table"}) + assert isinstance(result, dict) and "no_such_table" in result["error"] + + +# ── P2 ────────────────────────────────────────────────────────── + +def test_missing_required_argument_is_actionable(mcp): + assert mcp("api_method", {"class": "Database"}) == { + "error": "missing required argument 'name' (api_method takes class, name)"} + assert mcp("database_columns", {}) == { + "error": "missing required argument 'table' (database_columns takes table)"} + + +def test_unknown_argument_is_rejected_with_same_shape(mcp): + assert mcp("api_method", {"class": "Database", "name": "get_columns", "method": "x"}) == { + "error": "unknown argument 'method' (api_method takes class, name)"} + assert mcp("route_list", {"bogus": 1}) == { + "error": "unknown argument 'bogus' (route_list takes no arguments)"} + + +def test_every_tool_with_required_args_rejects_an_empty_call_actionably(mcp): + listing = mcp.server_tools() + with_required = [tool for tool in listing if tool["inputSchema"].get("required")] + assert len(with_required) > 10 + for tool in with_required: + first = tool["inputSchema"]["required"][0] + result = mcp(tool["name"], {}) + assert result["error"].startswith(f"missing required argument '{first}' ({tool['name']} takes "), tool["name"] + + +def test_api_method_populates_params_and_return(mcp): + spec = mcp("api_method", {"class": "Database", "name": "get_columns"}) + assert spec["params"] == [{"name": "table", "type": "str", "required": True, "default": None}] + assert spec["return"] == "list[dict]" + + +def test_api_method_unknown_method_still_errors(mcp): + assert "method not found" in mcp("api_method", {"class": "Database", "name": "nope"})["error"] + + +# ── P3 ────────────────────────────────────────────────────────── + +def test_route_list_reports_middleware_names(mcp): + routes = {route["path"]: route for route in mcp("route_list")} + guarded, plain = routes["/m271/guarded"], routes["/m271/plain"] + assert guarded["middleware"] == ["AuditMiddleware"] + assert guarded["auth_required"] is False and guarded["method"] == "GET" + assert plain["middleware"] == [] + + +# ── P4 ────────────────────────────────────────────────────────── + +def test_route_file_added_at_runtime_shows_in_route_list(mcp, tmp_path): + from tina4_python.core import server as server_module + server_module._auto_discover("src") + assert "/m271/late" not in [route["path"] for route in mcp("route_list")] + (tmp_path / "src" / "routes" / "late.py").write_text(textwrap.dedent(''' + from tina4_python.core.router import get + + @get("/m271/late") + async def late(request, response): + return response({"ok": True}) + ''')) + server_module._auto_discover("src") # what POST /__dev/api/reload runs + assert "/m271/late" in [route["path"] for route in mcp("route_list")] diff --git a/tests/test_mcp_dev_tools_conformance.py b/tests/test_mcp_dev_tools_conformance.py index b5c21024..230ab76f 100644 --- a/tests/test_mcp_dev_tools_conformance.py +++ b/tests/test_mcp_dev_tools_conformance.py @@ -209,7 +209,7 @@ async def _echo(request, response): "plan_flesh": {"name": "fixture-plan.md", "prompt": "probe"}, "api_search": {"query": "ORM", "k": 3}, "api_class": {"name": "ORM"}, - "api_method": {"class_": "Database", "name": "fetch"}, + "api_method": {"class": "Database", "name": "fetch"}, "code_search": {"query": "route", "k": 3}, } diff --git a/tina4_python/.tina4-metrics.json b/tina4_python/.tina4-metrics.json index 161dde00..a0a71a84 100644 --- a/tina4_python/.tina4-metrics.json +++ b/tina4_python/.tina4-metrics.json @@ -1,14 +1,14 @@ { "schema": 2, - "scan_root": "/Users/mac/projects/tina4/tina4-python/tina4_python", + "scan_root": "tina4_python", "last": { - "at": 1790875218, - "tool_version": "3.8.95", + "at": 1791188484, + "tool_version": "3.8.96", "files_analyzed": 128, - "total_functions": 3197, - "avg_complexity": 3.89, + "total_functions": 3202, + "avg_complexity": 3.9, "avg_maintainability": 28.0, - "total_offenders": 354, + "total_offenders": 355, "duplicate_blocks": 13, "duplicate_lines": 107 }, @@ -125,11 +125,11 @@ "offenders": 9, "worst_cc": 34, "maintainability": 7.4, - "loc": 762, - "complexity": 198, - "avg_complexity": 5.66, - "functions": 35, - "halstead_volume": 13530.06, + "loc": 767, + "complexity": 199, + "avg_complexity": 5.53, + "functions": 36, + "halstead_volume": 13689.57, "has_referencing_test": true, "dep_count": 16, "coupling_efferent": 1, @@ -844,12 +844,12 @@ "docs.py": { "offenders": 11, "worst_cc": 20, - "maintainability": 7.4, - "loc": 747, - "complexity": 229, - "avg_complexity": 5.73, - "functions": 40, - "halstead_volume": 14665.1, + "maintainability": 6.7, + "loc": 785, + "complexity": 242, + "avg_complexity": 5.63, + "functions": 43, + "halstead_volume": 15723.31, "has_referencing_test": true, "dep_count": 10, "coupling_efferent": 0, @@ -1292,14 +1292,14 @@ "duplicate_lines": 0 }, "mcp/__init__.py": { - "offenders": 1, - "worst_cc": 6, - "maintainability": 14.0, - "loc": 497, - "complexity": 108, - "avg_complexity": 2.77, - "functions": 39, - "halstead_volume": 6740.8, + "offenders": 2, + "worst_cc": 10, + "maintainability": 13.1, + "loc": 524, + "complexity": 123, + "avg_complexity": 3.08, + "functions": 40, + "halstead_volume": 7506.46, "has_referencing_test": true, "dep_count": 12, "coupling_efferent": 2, @@ -1330,12 +1330,12 @@ "mcp/tools.py": { "offenders": 6, "worst_cc": 14, - "maintainability": 4.8, - "loc": 943, - "complexity": 218, - "avg_complexity": 3.46, + "maintainability": 4.5, + "loc": 959, + "complexity": 224, + "avg_complexity": 3.56, "functions": 63, - "halstead_volume": 18476.15, + "halstead_volume": 18941.34, "has_referencing_test": true, "dep_count": 28, "coupling_efferent": 15, @@ -2450,6 +2450,17 @@ "total_offenders": 354, "duplicate_blocks": 13, "duplicate_lines": 107 + }, + { + "at": 1790875218, + "tool_version": "3.8.95", + "files_analyzed": 128, + "total_functions": 3197, + "avg_complexity": 3.89, + "avg_maintainability": 28.0, + "total_offenders": 354, + "duplicate_blocks": 13, + "duplicate_lines": 107 } ], "runs": [ @@ -31015,6 +31026,2325 @@ "duplicate_lines": 0 } } + }, + { + "snapshot": { + "at": 1791188484, + "tool_version": "3.8.96", + "files_analyzed": 128, + "total_functions": 3202, + "avg_complexity": 3.9, + "avg_maintainability": 28.0, + "total_offenders": 355, + "duplicate_blocks": 13, + "duplicate_lines": 107 + }, + "files": { + "HtmlElement.py": { + "offenders": 0, + "worst_cc": 9, + "maintainability": 32.0, + "loc": 109, + "complexity": 25, + "avg_complexity": 3.57, + "functions": 7, + "halstead_volume": 1989.1, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "Testing.py": { + "offenders": 1, + "worst_cc": 11, + "maintainability": 29.3, + "loc": 147, + "complexity": 35, + "avg_complexity": 3.18, + "functions": 11, + "halstead_volume": 1953.58, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "__init__.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 27.7, + "loc": 167, + "complexity": 16, + "avg_complexity": 4.0, + "functions": 4, + "halstead_volume": 2107.96, + "has_referencing_test": true, + "dep_count": 12, + "coupling_efferent": 6, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "_import_helper.py": { + "offenders": 2, + "worst_cc": 12, + "maintainability": 34.6, + "loc": 97, + "complexity": 29, + "avg_complexity": 5.8, + "functions": 5, + "halstead_volume": 1094.65, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "ai/__init__.py": { + "offenders": 1, + "worst_cc": 10, + "maintainability": 12.9, + "loc": 508, + "complexity": 91, + "avg_complexity": 4.55, + "functions": 20, + "halstead_volume": 8471.99, + "has_referencing_test": true, + "dep_count": 9, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "ai/client.py": { + "offenders": 16, + "worst_cc": 25, + "maintainability": 3.0, + "loc": 1010, + "complexity": 347, + "avg_complexity": 8.46, + "functions": 41, + "halstead_volume": 21536.77, + "has_referencing_test": true, + "dep_count": 10, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "api/__init__.py": { + "offenders": 9, + "worst_cc": 34, + "maintainability": 7.4, + "loc": 767, + "complexity": 199, + "avg_complexity": 5.53, + "functions": 36, + "halstead_volume": 13689.57, + "has_referencing_test": true, + "dep_count": 16, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "auth/__init__.py": { + "offenders": 5, + "worst_cc": 14, + "maintainability": 11.5, + "loc": 615, + "complexity": 127, + "avg_complexity": 2.76, + "functions": 46, + "halstead_volume": 8019.28, + "has_referencing_test": true, + "dep_count": 15, + "coupling_efferent": 2, + "coupling_afferent": 7, + "instability": 0.222, + "parse_health": 1.0, + "duplicate_groups": 2, + "duplicate_lines": 14 + }, + "cache/__init__.py": { + "offenders": 5, + "worst_cc": 20, + "maintainability": 1.4, + "loc": 1300, + "complexity": 355, + "avg_complexity": 3.38, + "functions": 105, + "halstead_volume": 21019.93, + "has_referencing_test": true, + "dep_count": 14, + "coupling_efferent": 2, + "coupling_afferent": 3, + "instability": 0.4, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "cli/__init__.py": { + "offenders": 10, + "worst_cc": 21, + "maintainability": 0.0, + "loc": 3094, + "complexity": 418, + "avg_complexity": 5.29, + "functions": 79, + "halstead_volume": 66157.29, + "has_referencing_test": true, + "dep_count": 30, + "coupling_efferent": 14, + "coupling_afferent": 1, + "instability": 0.933, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "container/__init__.py": { + "offenders": 0, + "worst_cc": 4, + "maintainability": 39.4, + "loc": 81, + "complexity": 14, + "avg_complexity": 2.0, + "functions": 7, + "halstead_volume": 466.15, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "context/__init__.py": { + "offenders": 1, + "worst_cc": 10, + "maintainability": 20.6, + "loc": 282, + "complexity": 82, + "avg_complexity": 3.73, + "functions": 22, + "halstead_volume": 4304.26, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "context/chunker.py": { + "offenders": 0, + "worst_cc": 9, + "maintainability": 35.6, + "loc": 84, + "complexity": 29, + "avg_complexity": 4.83, + "functions": 6, + "halstead_volume": 1269.65, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/__init__.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 53.2, + "loc": 27, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 166.47, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 7, + "coupling_afferent": 1, + "instability": 0.875, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/cache.py": { + "offenders": 0, + "worst_cc": 8, + "maintainability": 31.3, + "loc": 131, + "complexity": 35, + "avg_complexity": 2.92, + "functions": 12, + "halstead_volume": 1451.39, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 0, + "coupling_afferent": 3, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/constants.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 49.1, + "loc": 34, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 317.97, + "has_referencing_test": true, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/csp.py": { + "offenders": 2, + "worst_cc": 14, + "maintainability": 36.6, + "loc": 90, + "complexity": 22, + "avg_complexity": 3.14, + "functions": 7, + "halstead_volume": 816.6, + "has_referencing_test": false, + "dep_count": 4, + "coupling_efferent": 0, + "coupling_afferent": 7, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/events.py": { + "offenders": 0, + "worst_cc": 5, + "maintainability": 33.0, + "loc": 127, + "complexity": 27, + "avg_complexity": 2.08, + "functions": 13, + "halstead_volume": 940.57, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 1, + "coupling_afferent": 4, + "instability": 0.2, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/middleware.py": { + "offenders": 3, + "worst_cc": 29, + "maintainability": 10.1, + "loc": 694, + "complexity": 144, + "avg_complexity": 4.36, + "functions": 33, + "halstead_volume": 8003.08, + "has_referencing_test": true, + "dep_count": 12, + "coupling_efferent": 7, + "coupling_afferent": 3, + "instability": 0.7, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/port_takeover.py": { + "offenders": 2, + "worst_cc": 18, + "maintainability": 23.3, + "loc": 236, + "complexity": 63, + "avg_complexity": 4.5, + "functions": 14, + "halstead_volume": 3028.63, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/rate_limiter.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 32.2, + "loc": 119, + "complexity": 26, + "avg_complexity": 2.89, + "functions": 9, + "halstead_volume": 1449.87, + "has_referencing_test": true, + "dep_count": 3, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/request.py": { + "offenders": 5, + "worst_cc": 23, + "maintainability": 13.1, + "loc": 504, + "complexity": 145, + "avg_complexity": 4.26, + "functions": 34, + "halstead_volume": 8132.88, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 2, + "coupling_afferent": 3, + "instability": 0.4, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "core/response.py": { + "offenders": 6, + "worst_cc": 15, + "maintainability": 14.8, + "loc": 443, + "complexity": 123, + "avg_complexity": 4.56, + "functions": 27, + "halstead_volume": 6806.63, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 2, + "coupling_afferent": 4, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 2, + "duplicate_lines": 20 + }, + "core/router.py": { + "offenders": 6, + "worst_cc": 18, + "maintainability": 8.1, + "loc": 780, + "complexity": 188, + "avg_complexity": 2.51, + "functions": 75, + "halstead_volume": 11608.39, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 3, + "coupling_afferent": 16, + "instability": 0.158, + "parse_health": 1.0, + "duplicate_groups": 3, + "duplicate_lines": 18 + }, + "core/server.py": { + "offenders": 17, + "worst_cc": 28, + "maintainability": 0.0, + "loc": 3386, + "complexity": 718, + "avg_complexity": 4.82, + "functions": 149, + "halstead_volume": 56731.18, + "has_referencing_test": true, + "dep_count": 48, + "coupling_efferent": 22, + "coupling_afferent": 8, + "instability": 0.733, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "crud/__init__.py": { + "offenders": 2, + "worst_cc": 17, + "maintainability": 18.7, + "loc": 310, + "complexity": 70, + "avg_complexity": 5.83, + "functions": 12, + "halstead_volume": 5497.39, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 3, + "coupling_afferent": 1, + "instability": 0.75, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/__init__.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 62.4, + "loc": 19, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 24.0, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 4, + "coupling_afferent": 4, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/adapter.py": { + "offenders": 7, + "worst_cc": 22, + "maintainability": 4.4, + "loc": 1042, + "complexity": 249, + "avg_complexity": 3.77, + "functions": 66, + "halstead_volume": 15154.48, + "has_referencing_test": true, + "dep_count": 10, + "coupling_efferent": 2, + "coupling_afferent": 11, + "instability": 0.154, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/async_api.py": { + "offenders": 2, + "worst_cc": 6, + "maintainability": 28.5, + "loc": 170, + "complexity": 41, + "avg_complexity": 1.64, + "functions": 25, + "halstead_volume": 1727.43, + "has_referencing_test": false, + "dep_count": 5, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/connection.py": { + "offenders": 6, + "worst_cc": 17, + "maintainability": 1.5, + "loc": 1346, + "complexity": 268, + "avg_complexity": 2.95, + "functions": 91, + "halstead_volume": 18214.46, + "has_referencing_test": true, + "dep_count": 20, + "coupling_efferent": 8, + "coupling_afferent": 6, + "instability": 0.571, + "parse_health": 1.0, + "duplicate_groups": 2, + "duplicate_lines": 12 + }, + "database/database_url.py": { + "offenders": 2, + "worst_cc": 12, + "maintainability": 23.2, + "loc": 248, + "complexity": 57, + "avg_complexity": 5.7, + "functions": 10, + "halstead_volume": 2483.92, + "has_referencing_test": true, + "dep_count": 3, + "coupling_efferent": 0, + "coupling_afferent": 11, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/firebird.py": { + "offenders": 2, + "worst_cc": 31, + "maintainability": 13.4, + "loc": 500, + "complexity": 130, + "avg_complexity": 4.64, + "functions": 28, + "halstead_volume": 7386.78, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/mongodb.py": { + "offenders": 7, + "worst_cc": 26, + "maintainability": 9.8, + "loc": 627, + "complexity": 170, + "avg_complexity": 5.48, + "functions": 31, + "halstead_volume": 11707.92, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/mssql.py": { + "offenders": 2, + "worst_cc": 23, + "maintainability": 22.0, + "loc": 255, + "complexity": 71, + "avg_complexity": 3.74, + "functions": 19, + "halstead_volume": 3692.51, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/mysql.py": { + "offenders": 1, + "worst_cc": 17, + "maintainability": 24.2, + "loc": 211, + "complexity": 64, + "avg_complexity": 3.56, + "functions": 18, + "halstead_volume": 3268.6, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/odbc.py": { + "offenders": 2, + "worst_cc": 15, + "maintainability": 23.8, + "loc": 226, + "complexity": 75, + "avg_complexity": 3.95, + "functions": 19, + "halstead_volume": 2978.56, + "has_referencing_test": true, + "dep_count": 3, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/pool.py": { + "offenders": 2, + "worst_cc": 17, + "maintainability": 20.1, + "loc": 316, + "complexity": 88, + "avg_complexity": 3.67, + "functions": 24, + "halstead_volume": 3526.31, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 2, + "coupling_afferent": 2, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/postgres.py": { + "offenders": 2, + "worst_cc": 18, + "maintainability": 16.8, + "loc": 423, + "complexity": 93, + "avg_complexity": 3.88, + "functions": 24, + "halstead_volume": 4218.17, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 3, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/sql_translator.py": { + "offenders": 5, + "worst_cc": 33, + "maintainability": 11.8, + "loc": 530, + "complexity": 139, + "avg_complexity": 4.48, + "functions": 31, + "halstead_volume": 10423.53, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "database/sqlite.py": { + "offenders": 3, + "worst_cc": 17, + "maintainability": 23.4, + "loc": 227, + "complexity": 80, + "avg_complexity": 4.21, + "functions": 19, + "halstead_volume": 3342.65, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "debug/__init__.py": { + "offenders": 6, + "worst_cc": 18, + "maintainability": 8.0, + "loc": 731, + "complexity": 197, + "avg_complexity": 3.72, + "functions": 53, + "halstead_volume": 13824.23, + "has_referencing_test": true, + "dep_count": 11, + "coupling_efferent": 0, + "coupling_afferent": 29, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "debug/error_overlay.py": { + "offenders": 1, + "worst_cc": 12, + "maintainability": 21.1, + "loc": 276, + "complexity": 40, + "avg_complexity": 3.64, + "functions": 11, + "halstead_volume": 3935.1, + "has_referencing_test": true, + "dep_count": 12, + "coupling_efferent": 2, + "coupling_afferent": 3, + "instability": 0.4, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "dev_admin/__init__.py": { + "offenders": 21, + "worst_cc": 33, + "maintainability": 0.0, + "loc": 2867, + "complexity": 804, + "avg_complexity": 6.09, + "functions": 132, + "halstead_volume": 67456.39, + "has_referencing_test": true, + "dep_count": 44, + "coupling_efferent": 18, + "coupling_afferent": 2, + "instability": 0.9, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "dev_admin/metrics.py": { + "offenders": 2, + "worst_cc": 15, + "maintainability": 32.1, + "loc": 103, + "complexity": 37, + "avg_complexity": 7.4, + "functions": 5, + "halstead_volume": 1921.32, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "dev_admin/plan.py": { + "offenders": 5, + "worst_cc": 28, + "maintainability": 13.0, + "loc": 480, + "complexity": 146, + "avg_complexity": 5.84, + "functions": 25, + "halstead_volume": 9148.51, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "dev_admin/project_index.py": { + "offenders": 6, + "worst_cc": 16, + "maintainability": 17.3, + "loc": 337, + "complexity": 106, + "avg_complexity": 5.05, + "functions": 21, + "halstead_volume": 6846.3, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "docs.py": { + "offenders": 11, + "worst_cc": 20, + "maintainability": 6.7, + "loc": 785, + "complexity": 242, + "avg_complexity": 5.63, + "functions": 43, + "halstead_volume": 15723.31, + "has_referencing_test": true, + "dep_count": 10, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "docstore/__init__.py": { + "offenders": 6, + "worst_cc": 19, + "maintainability": 8.8, + "loc": 704, + "complexity": 238, + "avg_complexity": 3.17, + "functions": 75, + "halstead_volume": 12405.88, + "has_referencing_test": true, + "dep_count": 13, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "dotenv/__init__.py": { + "offenders": 1, + "worst_cc": 11, + "maintainability": 26.0, + "loc": 196, + "complexity": 43, + "avg_complexity": 3.58, + "functions": 12, + "halstead_volume": 2290.57, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 0, + "coupling_afferent": 7, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "env.py": { + "offenders": 0, + "worst_cc": 4, + "maintainability": 37.6, + "loc": 88, + "complexity": 14, + "avg_complexity": 2.8, + "functions": 5, + "halstead_volume": 637.76, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "frond/__init__.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 40.2, + "loc": 69, + "complexity": 15, + "avg_complexity": 3.75, + "functions": 4, + "halstead_volume": 544.62, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 3, + "coupling_afferent": 3, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "frond/compiler.py": { + "offenders": 1, + "worst_cc": 9, + "maintainability": 24.5, + "loc": 183, + "complexity": 28, + "avg_complexity": 5.6, + "functions": 5, + "halstead_volume": 4260.12, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "frond/engine.py": { + "offenders": 23, + "worst_cc": 26, + "maintainability": 0.0, + "loc": 2294, + "complexity": 828, + "avg_complexity": 3.94, + "functions": 210, + "halstead_volume": 53231.11, + "has_referencing_test": true, + "dep_count": 17, + "coupling_efferent": 4, + "coupling_afferent": 2, + "instability": 0.667, + "parse_health": 1.0, + "duplicate_groups": 6, + "duplicate_lines": 50 + }, + "frond/parser.py": { + "offenders": 6, + "worst_cc": 21, + "maintainability": 10.1, + "loc": 611, + "complexity": 123, + "avg_complexity": 7.69, + "functions": 16, + "halstead_volume": 10133.58, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/auth/src/routes/api/gallery_auth.py": { + "offenders": 1, + "worst_cc": 4, + "maintainability": 36.1, + "loc": 128, + "complexity": 6, + "avg_complexity": 2.0, + "functions": 3, + "halstead_volume": 335.77, + "has_referencing_test": false, + "dep_count": 3, + "coupling_efferent": 3, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/database/src/routes/api/gallery_db.py": { + "offenders": 1, + "worst_cc": 3, + "maintainability": 46.6, + "loc": 42, + "complexity": 7, + "avg_complexity": 2.33, + "functions": 3, + "halstead_volume": 336.89, + "has_referencing_test": false, + "dep_count": 2, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/error-overlay/src/routes/api/gallery_crash.py": { + "offenders": 1, + "worst_cc": 1, + "maintainability": 62.2, + "loc": 15, + "complexity": 1, + "avg_complexity": 1.0, + "functions": 1, + "halstead_volume": 51.89, + "has_referencing_test": false, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/orm/src/orm/Product.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 69.1, + "loc": 8, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 39.86, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/orm/src/routes/api/gallery_products.py": { + "offenders": 1, + "worst_cc": 2, + "maintainability": 60.4, + "loc": 13, + "complexity": 3, + "avg_complexity": 1.5, + "functions": 2, + "halstead_volume": 142.13, + "has_referencing_test": false, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/queue/src/routes/api/gallery_queue.py": { + "offenders": 1, + "worst_cc": 4, + "maintainability": 27.1, + "loc": 230, + "complexity": 13, + "avg_complexity": 1.86, + "functions": 7, + "halstead_volume": 1033.59, + "has_referencing_test": false, + "dep_count": 4, + "coupling_efferent": 3, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/rest-api/src/routes/api/gallery_hello.py": { + "offenders": 1, + "worst_cc": 2, + "maintainability": 60.8, + "loc": 13, + "complexity": 4, + "avg_complexity": 1.33, + "functions": 3, + "halstead_volume": 125.34, + "has_referencing_test": false, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "gallery/templates/src/routes/gallery_page.py": { + "offenders": 1, + "worst_cc": 1, + "maintainability": 61.2, + "loc": 13, + "complexity": 1, + "avg_complexity": 1.0, + "functions": 1, + "halstead_volume": 112.37, + "has_referencing_test": false, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/__init__.py": { + "offenders": 0, + "worst_cc": 4, + "maintainability": 37.1, + "loc": 84, + "complexity": 23, + "avg_complexity": 1.92, + "functions": 12, + "halstead_volume": 895.54, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 3, + "coupling_afferent": 3, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/adapter.py": { + "offenders": 0, + "worst_cc": 3, + "maintainability": 36.8, + "loc": 85, + "complexity": 16, + "avg_complexity": 1.23, + "functions": 13, + "halstead_volume": 974.23, + "has_referencing_test": true, + "dep_count": 3, + "coupling_efferent": 1, + "coupling_afferent": 4, + "instability": 0.2, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/adapters/__init__.py": { + "offenders": 1, + "worst_cc": 0, + "maintainability": 100.0, + "loc": 0, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 0.0, + "has_referencing_test": false, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/adapters/arango.py": { + "offenders": 1, + "worst_cc": 10, + "maintainability": 30.3, + "loc": 124, + "complexity": 50, + "avg_complexity": 3.33, + "functions": 15, + "halstead_volume": 2305.22, + "has_referencing_test": false, + "dep_count": 4, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/adapters/bolt.py": { + "offenders": 1, + "worst_cc": 8, + "maintainability": 32.0, + "loc": 108, + "complexity": 41, + "avg_complexity": 3.15, + "functions": 13, + "halstead_volume": 2076.24, + "has_referencing_test": false, + "dep_count": 4, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/adapters/ultipa.py": { + "offenders": 1, + "worst_cc": 6, + "maintainability": 30.0, + "loc": 129, + "complexity": 40, + "avg_complexity": 2.86, + "functions": 14, + "halstead_volume": 2296.51, + "has_referencing_test": false, + "dep_count": 5, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graph/graph_url.py": { + "offenders": 1, + "worst_cc": 11, + "maintainability": 41.7, + "loc": 53, + "complexity": 16, + "avg_complexity": 5.33, + "functions": 3, + "halstead_volume": 702.1, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "graphql/__init__.py": { + "offenders": 10, + "worst_cc": 19, + "maintainability": 6.8, + "loc": 785, + "complexity": 246, + "avg_complexity": 4.47, + "functions": 55, + "halstead_volume": 16272.75, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 2, + "coupling_afferent": 1, + "instability": 0.667, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "i18n/__init__.py": { + "offenders": 2, + "worst_cc": 12, + "maintainability": 24.6, + "loc": 217, + "complexity": 63, + "avg_complexity": 3.0, + "functions": 21, + "halstead_volume": 2681.69, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "mcp/__init__.py": { + "offenders": 2, + "worst_cc": 10, + "maintainability": 13.1, + "loc": 524, + "complexity": 123, + "avg_complexity": 3.08, + "functions": 40, + "halstead_volume": 7506.46, + "has_referencing_test": true, + "dep_count": 12, + "coupling_efferent": 2, + "coupling_afferent": 1, + "instability": 0.667, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "mcp/protocol.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 41.4, + "loc": 57, + "complexity": 12, + "avg_complexity": 3.0, + "functions": 4, + "halstead_volume": 701.84, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "mcp/tools.py": { + "offenders": 6, + "worst_cc": 14, + "maintainability": 4.5, + "loc": 959, + "complexity": 224, + "avg_complexity": 3.56, + "functions": 63, + "halstead_volume": 18941.34, + "has_referencing_test": true, + "dep_count": 28, + "coupling_efferent": 15, + "coupling_afferent": 1, + "instability": 0.938, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "messenger/__init__.py": { + "offenders": 10, + "worst_cc": 27, + "maintainability": 5.1, + "loc": 935, + "complexity": 227, + "avg_complexity": 5.4, + "functions": 42, + "halstead_volume": 15644.79, + "has_referencing_test": true, + "dep_count": 21, + "coupling_efferent": 4, + "coupling_afferent": 1, + "instability": 0.8, + "parse_health": 1.0, + "duplicate_groups": 2, + "duplicate_lines": 12 + }, + "migration/__init__.py": { + "offenders": 0, + "worst_cc": 1, + "maintainability": 53.2, + "loc": 31, + "complexity": 3, + "avg_complexity": 1.0, + "functions": 3, + "halstead_volume": 104.25, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 4, + "instability": 0.2, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "migration/runner.py": { + "offenders": 5, + "worst_cc": 19, + "maintainability": 6.9, + "loc": 855, + "complexity": 202, + "avg_complexity": 3.88, + "functions": 52, + "halstead_volume": 12251.06, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "mqtt/__init__.py": { + "offenders": 4, + "worst_cc": 17, + "maintainability": 10.0, + "loc": 620, + "complexity": 168, + "avg_complexity": 3.82, + "functions": 44, + "halstead_volume": 11852.34, + "has_referencing_test": true, + "dep_count": 15, + "coupling_efferent": 5, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "mqtt/message.py": { + "offenders": 0, + "worst_cc": 5, + "maintainability": 39.4, + "loc": 75, + "complexity": 13, + "avg_complexity": 1.44, + "functions": 9, + "halstead_volume": 615.09, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "orm/__init__.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 51.7, + "loc": 35, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 122.21, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 4, + "coupling_afferent": 8, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "orm/async_api.py": { + "offenders": 1, + "worst_cc": 1, + "maintainability": 39.0, + "loc": 73, + "complexity": 20, + "avg_complexity": 1.0, + "functions": 20, + "halstead_volume": 772.42, + "has_referencing_test": false, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "orm/collection.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 41.1, + "loc": 68, + "complexity": 13, + "avg_complexity": 4.33, + "functions": 3, + "halstead_volume": 411.2, + "has_referencing_test": true, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "orm/fields.py": { + "offenders": 4, + "worst_cc": 24, + "maintainability": 12.3, + "loc": 574, + "complexity": 154, + "avg_complexity": 3.42, + "functions": 45, + "halstead_volume": 7297.4, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 2, + "coupling_afferent": 4, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "orm/model.py": { + "offenders": 9, + "worst_cc": 37, + "maintainability": 0.0, + "loc": 1465, + "complexity": 360, + "avg_complexity": 4.56, + "functions": 79, + "halstead_volume": 24626.31, + "has_referencing_test": true, + "dep_count": 15, + "coupling_efferent": 9, + "coupling_afferent": 8, + "instability": 0.529, + "parse_health": 1.0, + "duplicate_groups": 2, + "duplicate_lines": 14 + }, + "orm/point.py": { + "offenders": 4, + "worst_cc": 14, + "maintainability": 18.7, + "loc": 335, + "complexity": 76, + "avg_complexity": 3.8, + "functions": 20, + "halstead_volume": 4646.58, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 0, + "coupling_afferent": 3, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "public/__feedback/widget.js": { + "offenders": 1, + "worst_cc": 9, + "maintainability": 31.1, + "loc": 101, + "complexity": 39, + "avg_complexity": 2.44, + "functions": 16, + "halstead_volume": 3591.7, + "has_referencing_test": false, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "public/js/frond.js": { + "offenders": 8, + "worst_cc": 16, + "maintainability": 8.0, + "loc": 681, + "complexity": 273, + "avg_complexity": 3.29, + "functions": 83, + "halstead_volume": 17663.24, + "has_referencing_test": true, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 6, + "duplicate_lines": 54 + }, + "push/__init__.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 22.4, + "loc": 223, + "complexity": 65, + "avg_complexity": 3.42, + "functions": 19, + "halstead_volume": 5074.78, + "has_referencing_test": true, + "dep_count": 15, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "push/_base.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 62.7, + "loc": 31, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 4.75, + "has_referencing_test": true, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 3, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "push/_cryptography.py": { + "offenders": 0, + "worst_cc": 2, + "maintainability": 43.5, + "loc": 51, + "complexity": 9, + "avg_complexity": 1.13, + "functions": 8, + "halstead_volume": 541.11, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "push/_libcrypto.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 22.6, + "loc": 228, + "complexity": 46, + "avg_complexity": 3.54, + "functions": 13, + "halstead_volume": 4408.46, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "query_builder/__init__.py": { + "offenders": 4, + "worst_cc": 14, + "maintainability": 12.3, + "loc": 592, + "complexity": 96, + "avg_complexity": 3.0, + "functions": 32, + "halstead_volume": 6685.37, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 3, + "coupling_afferent": 1, + "instability": 0.75, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/__init__.py": { + "offenders": 3, + "worst_cc": 12, + "maintainability": 19.0, + "loc": 355, + "complexity": 72, + "avg_complexity": 3.27, + "functions": 22, + "halstead_volume": 3679.03, + "has_referencing_test": true, + "dep_count": 10, + "coupling_efferent": 6, + "coupling_afferent": 4, + "instability": 0.6, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/amqp_url.py": { + "offenders": 0, + "worst_cc": 9, + "maintainability": 42.5, + "loc": 59, + "complexity": 9, + "avg_complexity": 9.0, + "functions": 1, + "halstead_volume": 334.36, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/job.py": { + "offenders": 0, + "worst_cc": 1, + "maintainability": 39.7, + "loc": 78, + "complexity": 10, + "avg_complexity": 1.0, + "functions": 10, + "halstead_volume": 501.48, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 0, + "coupling_afferent": 5, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/kafka_backend.py": { + "offenders": 0, + "worst_cc": 3, + "maintainability": 28.7, + "loc": 174, + "complexity": 21, + "avg_complexity": 1.62, + "functions": 13, + "halstead_volume": 1491.76, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 3, + "coupling_afferent": 1, + "instability": 0.75, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/lite_backend.py": { + "offenders": 1, + "worst_cc": 10, + "maintainability": 14.7, + "loc": 475, + "complexity": 104, + "avg_complexity": 3.25, + "functions": 32, + "halstead_volume": 6095.02, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/mongo_backend.py": { + "offenders": 0, + "worst_cc": 4, + "maintainability": 24.3, + "loc": 222, + "complexity": 36, + "avg_complexity": 2.0, + "functions": 18, + "halstead_volume": 2935.6, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 2, + "coupling_afferent": 1, + "instability": 0.667, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/rabbitmq_backend.py": { + "offenders": 0, + "worst_cc": 3, + "maintainability": 27.6, + "loc": 184, + "complexity": 24, + "avg_complexity": 1.85, + "functions": 13, + "halstead_volume": 1752.8, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 4, + "coupling_afferent": 1, + "instability": 0.8, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue/topic_dead_letter.py": { + "offenders": 0, + "worst_cc": 7, + "maintainability": 41.5, + "loc": 57, + "complexity": 13, + "avg_complexity": 6.5, + "functions": 2, + "halstead_volume": 578.25, + "has_referencing_test": true, + "dep_count": 0, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue_backends/__init__.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 63.0, + "loc": 18, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 24.0, + "has_referencing_test": true, + "dep_count": 3, + "coupling_efferent": 3, + "coupling_afferent": 3, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue_backends/kafka_backend.py": { + "offenders": 3, + "worst_cc": 13, + "maintainability": 12.9, + "loc": 499, + "complexity": 107, + "avg_complexity": 3.24, + "functions": 33, + "halstead_volume": 9510.68, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue_backends/mongo_backend.py": { + "offenders": 0, + "worst_cc": 6, + "maintainability": 22.5, + "loc": 260, + "complexity": 35, + "avg_complexity": 2.33, + "functions": 15, + "halstead_volume": 3142.08, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "queue_backends/rabbitmq_backend.py": { + "offenders": 1, + "worst_cc": 5, + "maintainability": 17.5, + "loc": 356, + "complexity": 79, + "avg_complexity": 2.14, + "functions": 37, + "halstead_volume": 6139.11, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 0, + "coupling_afferent": 1, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/__init__.py": { + "offenders": 3, + "worst_cc": 17, + "maintainability": 15.7, + "loc": 413, + "complexity": 107, + "avg_complexity": 4.28, + "functions": 25, + "halstead_volume": 6487.42, + "has_referencing_test": true, + "dep_count": 13, + "coupling_efferent": 5, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/models/Attachment.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 55.8, + "loc": 22, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 136.16, + "has_referencing_test": true, + "dep_count": 3, + "coupling_efferent": 3, + "coupling_afferent": 1, + "instability": 0.75, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/models/Channel.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 60.5, + "loc": 15, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 93.45, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 2, + "coupling_afferent": 4, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/models/ChannelMember.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 59.9, + "loc": 16, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 93.45, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 2, + "coupling_afferent": 1, + "instability": 0.667, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/models/Message.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 59.0, + "loc": 17, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 103.73, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 2, + "coupling_afferent": 2, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/models/Workspace.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 69.7, + "loc": 7, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 50.19, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/models/__init__.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 61.8, + "loc": 17, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 42.0, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 5, + "coupling_afferent": 1, + "instability": 0.833, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "realtime/storage.py": { + "offenders": 0, + "worst_cc": 9, + "maintainability": 30.4, + "loc": 147, + "complexity": 41, + "avg_complexity": 2.05, + "functions": 20, + "halstead_volume": 1391.68, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "seeder/__init__.py": { + "offenders": 8, + "worst_cc": 38, + "maintainability": 6.8, + "loc": 756, + "complexity": 258, + "avg_complexity": 4.3, + "functions": 60, + "halstead_volume": 17965.57, + "has_referencing_test": true, + "dep_count": 9, + "coupling_efferent": 3, + "coupling_afferent": 3, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "service/__init__.py": { + "offenders": 1, + "worst_cc": 10, + "maintainability": 15.9, + "loc": 448, + "complexity": 76, + "avg_complexity": 2.92, + "functions": 26, + "halstead_volume": 4890.01, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 1, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "session/__init__.py": { + "offenders": 2, + "worst_cc": 9, + "maintainability": 12.4, + "loc": 591, + "complexity": 138, + "avg_complexity": 2.65, + "functions": 52, + "halstead_volume": 6587.33, + "has_referencing_test": true, + "dep_count": 10, + "coupling_efferent": 3, + "coupling_afferent": 4, + "instability": 0.429, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "session_handlers/__init__.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 64.3, + "loc": 18, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 15.51, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 4, + "coupling_afferent": 1, + "instability": 0.8, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "session_handlers/memcached_handler.py": { + "offenders": 0, + "worst_cc": 8, + "maintainability": 32.2, + "loc": 120, + "complexity": 25, + "avg_complexity": 2.78, + "functions": 9, + "halstead_volume": 1401.21, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "session_handlers/mongodb_handler.py": { + "offenders": 4, + "worst_cc": 13, + "maintainability": 17.1, + "loc": 351, + "complexity": 81, + "avg_complexity": 3.24, + "functions": 25, + "halstead_volume": 6927.01, + "has_referencing_test": true, + "dep_count": 8, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 2, + "duplicate_lines": 14 + }, + "session_handlers/redis_handler.py": { + "offenders": 0, + "worst_cc": 9, + "maintainability": 26.6, + "loc": 182, + "complexity": 51, + "avg_complexity": 3.64, + "functions": 14, + "halstead_volume": 2357.66, + "has_referencing_test": true, + "dep_count": 5, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "session_handlers/valkey_handler.py": { + "offenders": 0, + "worst_cc": 0, + "maintainability": 61.7, + "loc": 22, + "complexity": 0, + "avg_complexity": 0.0, + "functions": 0, + "halstead_volume": 19.65, + "has_referencing_test": true, + "dep_count": 1, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "sso.py": { + "offenders": 3, + "worst_cc": 13, + "maintainability": 17.8, + "loc": 312, + "complexity": 129, + "avg_complexity": 5.16, + "functions": 25, + "halstead_volume": 7489.79, + "has_referencing_test": true, + "dep_count": 11, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "ssrf.py": { + "offenders": 2, + "worst_cc": 13, + "maintainability": 31.8, + "loc": 121, + "complexity": 31, + "avg_complexity": 5.17, + "functions": 6, + "halstead_volume": 1396.56, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 1, + "coupling_afferent": 2, + "instability": 0.333, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "swagger/__init__.py": { + "offenders": 6, + "worst_cc": 16, + "maintainability": 8.4, + "loc": 721, + "complexity": 213, + "avg_complexity": 4.1, + "functions": 52, + "halstead_volume": 12506.77, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 0, + "coupling_afferent": 2, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "test_client/__init__.py": { + "offenders": 1, + "worst_cc": 13, + "maintainability": 26.9, + "loc": 172, + "complexity": 41, + "avg_complexity": 2.73, + "functions": 15, + "halstead_volume": 2623.89, + "has_referencing_test": true, + "dep_count": 4, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "validator/__init__.py": { + "offenders": 0, + "worst_cc": 5, + "maintainability": 29.5, + "loc": 148, + "complexity": 41, + "avg_complexity": 3.42, + "functions": 12, + "halstead_volume": 1728.0, + "has_referencing_test": true, + "dep_count": 2, + "coupling_efferent": 0, + "coupling_afferent": 0, + "instability": 0.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "websocket/__init__.py": { + "offenders": 6, + "worst_cc": 20, + "maintainability": 7.3, + "loc": 805, + "complexity": 244, + "avg_complexity": 3.54, + "functions": 69, + "halstead_volume": 13131.12, + "has_referencing_test": true, + "dep_count": 16, + "coupling_efferent": 4, + "coupling_afferent": 2, + "instability": 0.667, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "websocket/backplane.py": { + "offenders": 1, + "worst_cc": 4, + "maintainability": 28.9, + "loc": 171, + "complexity": 36, + "avg_complexity": 1.64, + "functions": 22, + "halstead_volume": 1488.69, + "has_referencing_test": true, + "dep_count": 7, + "coupling_efferent": 1, + "coupling_afferent": 1, + "instability": 0.5, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + }, + "wsdl/__init__.py": { + "offenders": 3, + "worst_cc": 20, + "maintainability": 18.8, + "loc": 304, + "complexity": 92, + "avg_complexity": 5.41, + "functions": 17, + "halstead_volume": 5640.91, + "has_referencing_test": true, + "dep_count": 6, + "coupling_efferent": 2, + "coupling_afferent": 0, + "instability": 1.0, + "parse_health": 1.0, + "duplicate_groups": 0, + "duplicate_lines": 0 + } + } } ] } \ No newline at end of file diff --git a/tina4_python/__init__.py b/tina4_python/__init__.py index 2c2a8fc1..972875d4 100644 --- a/tina4_python/__init__.py +++ b/tina4_python/__init__.py @@ -57,7 +57,7 @@ def _resolve_version() -> str: # # test_version_constant.py now asserts this literal equals the pyproject # version, so the release bump cannot leave it behind again. - return "3.13.146" + return "3.13.147" __version__ = _resolve_version() diff --git a/tina4_python/docs.py b/tina4_python/docs.py index faec8ddb..70b94b2d 100644 --- a/tina4_python/docs.py +++ b/tina4_python/docs.py @@ -151,6 +151,48 @@ def _safe_signature(obj) -> str: _RECEIVER_PARAMS = {"self", "cls", "mcs", "metacls", "instance", "owner"} +def _parse_signature(signature: str): + """Parse a rendered ``name(a: int = 1) -> str`` signature into an ast node.""" + import ast + try: + return ast.parse(f"def {signature}: pass").body[0] + except SyntaxError: + return None + + +def _params_from_signature(signature: str) -> list[dict[str, Any]]: + """Structured params: [{name, type, required, default}] (type/default are source text).""" + import ast + node = _parse_signature(signature) + if node is None: + return [] + args = node.args + positional = list(args.posonlyargs) + list(args.args) + defaults = [None] * (len(positional) - len(args.defaults)) + list(args.defaults) + entries = list(zip(positional, defaults)) + list(zip(args.kwonlyargs, args.kw_defaults)) + params = [ + { + "name": arg.arg, + "type": ast.unparse(arg.annotation) if arg.annotation else "", + "required": default is None, + "default": ast.unparse(default) if default is not None else None, + } + for arg, default in entries + ] + for star, prefix in ((args.vararg, "*"), (args.kwarg, "**")): + if star: + params.append({"name": prefix + star.arg, + "type": ast.unparse(star.annotation) if star.annotation else "", + "required": False, "default": None}) + return params + + +def _return_from_signature(signature: str) -> str: + import ast + node = _parse_signature(signature) + return ast.unparse(node.returns) if node is not None and node.returns else "" + + def _render_signature(func, display_name: str) -> str: """Public signature for `display_name`, dropping leading receiver params.""" try: @@ -728,6 +770,8 @@ def method_spec( "name": e.name, "kind": "method", "signature": e.signature, + "params": _params_from_signature(e.signature), + "return": _return_from_signature(e.signature), "summary": e.summary, "docstring": e.docstring, "file": e.file, diff --git a/tina4_python/mcp/__init__.py b/tina4_python/mcp/__init__.py index c53a099f..acb4b443 100644 --- a/tina4_python/mcp/__init__.py +++ b/tina4_python/mcp/__init__.py @@ -70,8 +70,12 @@ def _schema_from_signature(func) -> dict: properties = {} required = [] + accepts_any_keyword = False for name, param in sig.parameters.items(): - if name == "self": + if name == "self" or param.kind is inspect.Parameter.VAR_POSITIONAL: + continue + if param.kind is inspect.Parameter.VAR_KEYWORD: + accepts_any_keyword = True continue annotation = param.annotation prop = {"type": _TYPE_MAP.get(annotation, "string")} @@ -86,9 +90,32 @@ def _schema_from_signature(func) -> dict: schema = {"type": "object", "properties": properties} if required: schema["required"] = required + if accepts_any_keyword: + schema["additionalProperties"] = True return schema +def _validate_tool_arguments(tool_name: str, schema: dict, arguments) -> str | None: + """Check call arguments against the tool's input schema BEFORE invoking it. + + Returns None when valid, else the actionable message + ``missing required argument '

' ( takes , )`` (or + ``unknown argument '' (...)``). Keeps a Python TypeError from escaping + as a 500 (tina4-php#271).""" + properties = schema.get("properties") or {} + takes = ", ".join(properties) if properties else "no arguments" + if not isinstance(arguments, dict): + return f"arguments must be an object ({tool_name} takes {takes})" + for required_name in schema.get("required") or []: + if required_name not in arguments: + return f"missing required argument '{required_name}' ({tool_name} takes {takes})" + if not schema.get("additionalProperties"): + for supplied_name in arguments: + if supplied_name not in properties: + return f"unknown argument '{supplied_name}' ({tool_name} takes {takes})" + return None + + def is_localhost() -> bool: """Whether the CONFIGURED host name looks local. Informational only — kept for back-compat. This is NOT the security gate: it reads the @@ -422,7 +449,10 @@ def _handle_tools_call(self, params: dict) -> dict: if not tool: raise ValueError(f"Unknown tool: {tool_name}") - arguments = params.get("arguments", {}) + arguments = params.get("arguments") or {} + invalid = _validate_tool_arguments(tool_name, tool["inputSchema"], arguments) + if invalid: + return {"content": [{"type": "text", "text": json.dumps({"error": invalid}, indent=2)}]} handler = tool["handler"] # Call the handler with the provided arguments diff --git a/tina4_python/mcp/tools.py b/tina4_python/mcp/tools.py index 6f3f1ba6..fd66ad00 100644 --- a/tina4_python/mcp/tools.py +++ b/tina4_python/mcp/tools.py @@ -286,12 +286,16 @@ def database_columns(table: str) -> list: db = ORM._get_db() if hasattr(ORM, "_get_db") else None if db is None: return {"error": "No database connection"} - return db.get_columns(table) + columns = db.get_columns(table) + if not columns and not db.table_exists(table): + return {"error": f"table not found: {table}"} + return columns # ── Route Tools ───────────────────────────────────────────── def route_list() -> list: - """List all registered routes with methods and paths.""" + """List the registered routes with method, path, auth_required and the + names of the middleware attached to each (as loaded in this process).""" from tina4_python.core.router import Router routes = [] for route in Router.get_routes(): @@ -299,6 +303,10 @@ def route_list() -> list: "method": route.get("method", ""), "path": route.get("path", ""), "auth_required": route.get("auth_required", False), + "middleware": [ + getattr(attached, "__name__", None) or type(attached).__name__ + for attached in route.get("middleware") or [] + ], }) return routes @@ -1071,13 +1079,21 @@ def api_class(name: str) -> dict: spec = Docs(project_root=str(project_root)).class_spec(name) return spec if spec is not None else {"error": f"class not found: {name}"} - def api_method(class_: str, name: str) -> dict: - # `class` is a Python keyword — accept it via `class_` and let - # the MCP layer pass either through to the wrapper. + def api_method(**arguments) -> dict: + # `class` is a Python keyword, so the handler takes **arguments; the + # published schema (below) names the properties `class` and `name`. from tina4_python.docs import Docs + class_ = arguments["class"] + name = arguments["name"] spec = Docs(project_root=str(project_root)).method_spec(class_, name) return spec if spec is not None else {"error": f"method not found: {class_}.{name}"} + api_method_schema = { + "type": "object", + "properties": {"class": {"type": "string"}, "name": {"type": "string"}}, + "required": ["class", "name"], + } + # ── Code/doc grounding (tina4_python.context) ─────────────── # Sibling of api_* but the DUAL of it: api_* is exact structural # reflection (class/method signatures); code_search is fuzzy/semantic @@ -1175,4 +1191,5 @@ def code_search(query: str, k: int = 5, rebuild: bool = False) -> list: ] for name, handler, description in tools: - server.register_tool(name, handler, description) + server.register_tool(name, handler, description, + api_method_schema if name == "api_method" else None) diff --git a/uv.lock b/uv.lock index 8ddf7edf..53e1c730 100644 --- a/uv.lock +++ b/uv.lock @@ -1648,7 +1648,7 @@ wheels = [ [[package]] name = "tina4-python" -version = "3.13.146" +version = "3.13.147" source = { editable = "." } [package.optional-dependencies]