Files
meshcore-bot/pyproject.toml
agessaman 853f86d8a5 build(mypy): type-check the modules the refactor created, tracking included
Plan rule 3c said every module this refactor creates is type-checked from its first commit; web_viewer/tracking.py was on the ignore list because it moved out of app.py with app's existing errors, and mypy skipped the bodies of untyped helpers in all the new modules. tracking.py is off the ignore list, and the 30 new modules get check_untyped_defs. The 25 errors that surfaced were all missing annotations: container annotations (paths_by_key, tracking, server_stats, result, daily_by_role, feed params), casts for page/page_size where pagination guarantees ints, FeedSubscriptionsMixin's two host attributes, and an ignore for Flask-SocketIO's request.sid. No runtime change.
2026-10-03 22:45:42 -07:00

253 lines
8.7 KiB
TOML

# pyproject.toml
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "meshcore-bot"
version = "1.1.0"
description = "MeshCore Bot with commands for mesh testing, utilities, and service integration."
readme = "README.md"
# Table form rather than the SPDX string form: the string form needs
# setuptools>=77, above this project's declared build requirement.
license = { text = "MIT" }
authors = [{ name = "Adam Gessaman", email = "adam@gessaman.com" }]
requires-python = ">=3.10"
classifiers = [
"License :: OSI Approved :: MIT License",
"Programming Language :: Python :: 3",
"Operating System :: OS Independent",
]
dependencies = [
"pyserial>=3.5",
"bleak>=0.20.0",
"asyncio-mqtt>=0.11.0",
"configparser>=5.3.0",
"python-dateutil>=2.8.2",
"apscheduler>=3.10.0",
"colorlog>=6.7.0",
"requests>=2.31.0",
"urllib3>=2.0.0",
"pyephem>=4.1.4",
"geopy>=2.3.0",
"maidenhead>=1.4.0",
"pytz>=2023.3",
"aiohttp>=3.8.0",
# 2.3.14+ required for send_msg_with_retry's ACK handling (see requirements.txt)
"meshcore>=2.3.14",
"openmeteo-requests>=1.7.2",
"requests-cache>=1.1.1",
"retry-requests>=1.0.0",
"flask>=2.3.0",
"flask-socketio>=5.3.0",
"flask-compress>=1.14",
# Still required: `next_cmd` backs contact and channel operations that have no
# equivalent in the meshcore library yet (see requirements.txt).
"meshcore-cli>=1.4.0",
"feedparser>=6.0.10",
"paho-mqtt>=1.6.0",
"cryptography>=41.0.0",
"pynacl>=1.5.0",
"aiosqlite>=0.19.0",
]
[project.optional-dependencies]
profanity = ["better-profanity>=0.7.0", "unidecode>=1.3.0"]
geo = ["pycountry>=23.12.0", "us>=2.0.0"]
lang = ["langdetect>=1.0.9"]
test = ["pytest>=7.0", "pytest-asyncio>=0.21", "pytest-mock>=3.10", "pytest-cov>=4.0", "pytest-timeout>=2.1.0", "types-requests>=2.31"]
docs = ["mkdocs-material>=9.0.0", "mkdocs-exclude>=1.0.0"]
[project.scripts]
meshcore-bot = "meshcore_bot:main"
meshcore-viewer = "modules.web_viewer.app:main"
[tool.setuptools]
py-modules = ["meshcore_bot"]
include-package-data = false
license-files = ["LICENSE"]
[tool.setuptools.packages.find]
where = ["."]
include = ["modules*", "translations*", "data*"]
exclude = ["tests*", "local*"]
[tool.setuptools.package-data]
modules = [
"web_viewer/templates/*.html",
"web_viewer/templates/**/*.html",
"web_viewer/static/*",
"web_viewer/static/*/*",
"web_viewer/static/*/*/*",
"templates/*.ini",
]
translations = ["*.json"]
"data.randomlines" = ["*.txt"]
[tool.ruff]
# Match CI/Makefile installs so lint rules do not drift on unpinned `pip install ruff`.
required-version = "==0.15.15"
line-length = 120
target-version = "py310"
exclude = [".venv", "build", "dist"]
[tool.ruff.lint]
select = ["E", "F", "W", "I", "UP", "B", "C4", "SIM"]
ignore = [
"E501", # line too long (handled by formatter)
# Legacy-code tolerances — pervasive in pre-typed source, not worth a bulk churn:
"E701", # multiple-statements-on-one-line-colon
"E702", # multiple-statements-on-one-line-semicolon
"E711", # comparison-to-none (== None common in legacy)
"E712", # comparison-to-true (== True/False common in legacy)
"E722", # bare-except (used extensively in graceful-degradation patterns)
"E741", # ambiguous-variable-name (l, O, I in legacy loops)
"E402", # module-level import not at top (conditional imports pattern)
"F601", # multi-value-repeated-key-literal (legacy dict pattern)
"B007", # unused-loop-control-variable (rename to _ is a bulk churn)
"B023", # function-uses-loop-variable (legacy closures in packet capture)
"B904", # raise-without-from-in-except (legacy raise pattern)
"C408", # unnecessary-collection-call
"C414", # unnecessary-double-cast-or-process
# pyupgrade rules that would require a bulk typing rewrite — deferred to a code-quality PR:
"UP007", # non-pep604-annotation-union (Union[X, Y] -> X | Y)
"UP035", # deprecated-import (typing.List/Dict/... remain prevalent)
"UP041", # timeout-error-alias (socket.timeout -> TimeoutError)
"UP045", # non-pep604-annotation-optional (Optional[X] -> X | None)
# Simplification suggestions — stylistic, not enforced on legacy code:
"SIM102", # collapsible-if
"SIM103", # needless-bool
"SIM105", # suppressible-exception
"SIM108", # if-else-block-instead-of-if-exp (ternary preference is subjective)
"SIM109", # compare-with-tuple
"SIM110", # reimplemented-builtin
"SIM114", # if-with-same-arms
"SIM115", # open-file-with-context-handler
"SIM117", # multiple-with-statements
"SIM210", # if-expr-with-true-false
]
[tool.ruff.lint.per-file-ignores]
"tests/*" = ["S101"]
# ---------------------------------------------------------------------------
# mypy — incremental strict mode
# ---------------------------------------------------------------------------
# Global baseline: safe non-breaking options.
# Per-module overrides below tighten settings for fully-typed modules.
[tool.mypy]
python_version = "3.11"
ignore_missing_imports = true
warn_unused_ignores = true
warn_return_any = false # too noisy until all modules are annotated
warn_unused_configs = true
no_implicit_optional = true
strict_optional = true
# New modules written with full type annotations get strict treatment.
[[tool.mypy.overrides]]
module = [
"modules.alert_format",
"modules.commands.schedule_command",
"modules.service_plugins.webhook_service",
"modules.service_plugins.base_service",
"modules.message_handler",
"modules.utils",
"modules.plugin_loader",
"modules.security_utils",
"modules.commands.base_command",
"modules.commands.rain_command",
"modules.region_warning",
]
disallow_untyped_defs = true
disallow_incomplete_defs = true
check_untyped_defs = true
no_implicit_optional = true
# Modules with known type errors not yet brought up to strict standard.
# Suppress all mypy errors for these modules until they are fully annotated.
[[tool.mypy.overrides]]
module = [
"modules.core",
"modules.command_manager",
"modules.feed_manager",
"modules.mesh_graph",
"modules.scheduler",
"modules.maintenance",
"modules.transmission_tracker",
"modules.commands.alert_command",
"modules.commands.announcements_command",
"modules.commands.aurora_command",
"modules.commands.channels_command",
"modules.commands.greeter_command",
"modules.commands.help_command",
"modules.commands.joke_command",
"modules.commands.multitest_command",
"modules.commands.prefix_command",
"modules.commands.wx_command",
"modules.commands.alternatives.wx_international",
"modules.commands.trace_command",
"modules.commands.solarforecast_command",
"modules.commands.roll_command",
"modules.commands.repeater_command",
"modules.commands.path_command",
"modules.commands.sports_command",
"modules.commands.aqi_command",
"modules.service_plugins.discord_bridge_service",
"modules.service_plugins.packet_capture_service",
"modules.service_plugins.weather_service",
"modules.web_viewer.app",
"modules.web_viewer.integration",
]
ignore_errors = true
# Modules split out of the large files by the maintainability refactor: their
# untyped helper bodies are type-checked too, so a mixin's code is not silently
# skipped where the original method was.
[[tool.mypy.overrides]]
module = [
"modules.admin_server",
"modules.channel_message",
"modules.config_reload",
"modules.contact_events",
"modules.contacts_repo",
"modules.device_setup",
"modules.feed_parse",
"modules.joke_text",
"modules.logging_setup",
"modules.mesh_graph_recorder",
"modules.mesh_paths",
"modules.packet_decode",
"modules.radio_link",
"modules.radio_offline",
"modules.rf_correlation",
"modules.rf_log",
"modules.rf_match",
"modules.scope_gate",
"modules.service_plugins.channel_bridge_utils",
"modules.service_supervisor",
"modules.web_viewer.channels",
"modules.web_viewer.cleanup",
"modules.web_viewer.dashboard",
"modules.web_viewer.database_admin",
"modules.web_viewer.feeds",
"modules.web_viewer.live_stream",
"modules.web_viewer.mesh_evidence",
"modules.web_viewer.multibyte_rollout",
"modules.web_viewer.socket_clients",
"modules.web_viewer.tracking",
]
check_untyped_defs = true
# Pytest is configured in pytest.ini, not here. pytest.ini takes precedence over
# pyproject.toml, so a [tool.pytest.ini_options] block in this file would be dead
# config that silently drifts from the settings actually in effect.
[tool.coverage.run]
source = ["modules"]
omit = ["tests/*", ".venv/*"]
[tool.coverage.report]
fail_under = 50
show_missing = true