# pyproject.toml [build-system] requires = ["setuptools>=61.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "meshcore-bot" version = "0.9.0" description = "MeshCore Bot with commands for mesh testing, utilities, and service integration." readme = "README.md" requires-python = ">=3.10" 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", "meshcore>=2.3.6", "openmeteo-requests>=1.7.2", "requests-cache>=1.1.1", "retry-requests>=1.0.0", "flask>=2.3.0", "flask-socketio>=5.3.0", "meshcore-cli", "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"] 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] # Include both the main module and the modules package py-modules = ["meshcore_bot"] packages = ["modules", "modules.commands", "modules.commands.alternatives", "modules.commands.alternatives.inactive", "modules.service_plugins", "modules.web_viewer"] [tool.setuptools.package-data] "*" = ["*.json"] modules = [ "web_viewer/templates/*.html", "web_viewer/static/*", "web_viewer/static/*/*", "web_viewer/static/*/*/*", ] [tool.ruff] 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.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", ] 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 [tool.pytest.ini_options] # Hard limit every test to 30 s; prevents hangs from blocking I/O or infinite loops. # Individual tests that legitimately need longer can use @pytest.mark.timeout(N). timeout = 30 timeout_method = "thread" asyncio_mode = "auto" [tool.coverage.run] source = ["modules"] omit = ["tests/*", ".venv/*"] [tool.coverage.report] fail_under = 35 show_missing = true