mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
focus_mode_daemon.py has been 509 lines since 22e22c1, over the repo's
cap. It went unnoticed because the Python tests workflow only triggers on
python_pkg/** changes, so that commit never ran CI; the next commit to
touch python_pkg/ exposed it and turned main red.
Extract configuration, logging, notification, /proc process inspection and
the killers into _focus_mode_lib.py, leaving the daemon its policy: the
whitelist, the FocusMode state machine, and main(). log/notify move with
the process code that calls them, so the dependency runs one way and there
is no cycle. The helper is underscore-prefixed and registered in pylint's
init-hook path, mirroring the existing usage_report.py / _usage_report_*.py
sibling-import precedent.
The daemon runs from a symlink in /usr/local/bin, so a sibling import only
works if Python resolves that symlink before setting sys.path[0]; verified
that it does, and the installer symlinks rather than copies, so the sibling
travels with it. Confirmed against the running service: restarts clean and
resumes enforcing, process detection unchanged, status output matches the
pre-split version.
Structural only; no behaviour change.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RG3eEn8rwZbcXaJrKJxdv8
390 lines
15 KiB
TOML
390 lines
15 KiB
TOML
[project]
|
|
name = "testsandmisc"
|
|
version = "0.1.0"
|
|
description = "Collection of miscellaneous tests and scripts"
|
|
requires-python = ">=3.10"
|
|
|
|
# ============================================================================
|
|
# RUFF - Extremely fast Python linter and formatter (written in Rust)
|
|
# ============================================================================
|
|
[tool.ruff]
|
|
target-version = "py310"
|
|
# Include all Python files
|
|
include = ["*.py", "**/*.py"]
|
|
# Exclude vendored/build directories
|
|
exclude = [
|
|
".git",
|
|
".venv",
|
|
"__pycache__",
|
|
"build",
|
|
"dist",
|
|
".eggs",
|
|
"Bash/ffmpeg-build", # Vendored FFmpeg tools
|
|
]
|
|
|
|
[tool.ruff.lint]
|
|
# AGGRESSIVE: Select ALL rules from all categories
|
|
select = ["ALL"]
|
|
# Ignores for rules that are too strict for this mixed script repository
|
|
ignore = [
|
|
# D203 vs D211 conflict - we use D211 (no blank line before class docstring)
|
|
"D203", # 1 blank line required before class docstring (conflicts with D211)
|
|
# D212 vs D213 conflict - we use D212 (summary on first line after """)
|
|
"D213", # Multi-line docstring summary should start at second line (conflicts with D212)
|
|
# Formatter conflicts - recommended to disable when using ruff format
|
|
# https://docs.astral.sh/ruff/formatter/#conflicting-lint-rules
|
|
"COM812", # Trailing comma missing - formatter handles this automatically
|
|
"ISC001", # Implicit string concatenation - formatter may create these when wrapping
|
|
# Security audit - prone to false positives with validated input
|
|
# https://github.com/astral-sh/ruff/issues/4045
|
|
"S603", # subprocess call without shell - prone to false positives as it is
|
|
# difficult to determine whether the passed arguments have been validated
|
|
]
|
|
|
|
# Allow ALL rules to be auto-fixed
|
|
fixable = ["ALL"]
|
|
unfixable = []
|
|
|
|
# Per-file ignores — only rules that FUNDAMENTALLY conflict with test code remain.
|
|
# Every other rule was fixed in source. See justifications below.
|
|
[tool.ruff.lint.per-file-ignores]
|
|
"**/code_tutor/cli.py" = [
|
|
"B008", # typer.Argument(...) as a parameter default is the standard Typer
|
|
# dependency-injection pattern, not a mutable-default bug.
|
|
"S607", # invoking "systemctl" by name (not full path) is fine for a
|
|
# local developer tool that assumes a normal PATH.
|
|
"PERF203", # the try/except inside the Ollama readiness-poll loop is
|
|
# intentional: retry once per second until the server answers.
|
|
]
|
|
"**/tests/**/*.py" = [
|
|
"ARG", # @patch decorators inject mock params that aren't always referenced;
|
|
# the patch side-effect is needed, not the mock object itself.
|
|
"D", # Test names like test_sub_cards_no_answer_text are self-documenting;
|
|
# docstrings would be redundant noise on every test method.
|
|
"PLC0415", # Test isolation requires importing AFTER mocking sys.modules;
|
|
# top-level imports would bypass the mocks entirely.
|
|
"PLR2004", # assert count == 5 is clearer than assert count == EXPECTED_COUNT;
|
|
# named constants for test expectations add indirection without value.
|
|
"S101", # assert IS what tests do — every Python test suite suppresses this.
|
|
"SLF001", # Unit tests must exercise private internals (_method, _attr) to reach
|
|
# 100% branch coverage; only integration tests can avoid this.
|
|
]
|
|
"**/test_*.py" = [
|
|
"ARG",
|
|
"D",
|
|
"PLC0415",
|
|
"PLR2004",
|
|
"S101",
|
|
"SLF001",
|
|
]
|
|
|
|
|
|
[tool.ruff.lint.pylint]
|
|
max-args = 6 # code_tutor challenge flows genuinely need 6 params
|
|
max-returns = 8 # challenge state machines have multiple exit paths
|
|
|
|
[tool.ruff.lint.pydocstyle]
|
|
convention = "google" # Use Google docstring convention
|
|
|
|
[tool.ruff.lint.isort]
|
|
force-single-line = false
|
|
force-sort-within-sections = true
|
|
known-first-party = ["python_pkg"]
|
|
|
|
[tool.ruff.lint.flake8-quotes]
|
|
docstring-quotes = "double"
|
|
inline-quotes = "double"
|
|
|
|
[tool.ruff.lint.flake8-tidy-imports]
|
|
ban-relative-imports = "all"
|
|
|
|
[tool.ruff.format]
|
|
quote-style = "double"
|
|
indent-style = "space"
|
|
skip-magic-trailing-comma = false
|
|
line-ending = "auto"
|
|
docstring-code-format = true
|
|
|
|
# ============================================================================
|
|
# MYPY - Static type checker (most aggressive settings)
|
|
# ============================================================================
|
|
[tool.mypy]
|
|
python_version = "3.10"
|
|
# Strict mode enables most checks
|
|
strict = true
|
|
# Additional aggressive settings
|
|
warn_return_any = true
|
|
warn_unused_configs = true
|
|
disallow_untyped_defs = true
|
|
disallow_incomplete_defs = true
|
|
check_untyped_defs = true
|
|
disallow_untyped_decorators = true
|
|
no_implicit_optional = true
|
|
warn_redundant_casts = true
|
|
warn_unused_ignores = true
|
|
warn_no_return = true
|
|
warn_unreachable = true
|
|
# Extra strict settings
|
|
disallow_any_unimported = true
|
|
disallow_any_explicit = false # Too aggressive for practical use
|
|
disallow_any_generics = true
|
|
disallow_subclassing_any = true
|
|
strict_equality = true
|
|
extra_checks = true
|
|
# Allow missing imports for third-party packages
|
|
ignore_missing_imports = true
|
|
# Show error codes
|
|
show_error_codes = true
|
|
# Enable colored output
|
|
color_output = true
|
|
# Exclude vendored directories
|
|
exclude = [
|
|
"Bash/ffmpeg-build/",
|
|
".venv/",
|
|
"linux_configuration/scripts/single_use/misc/testsAndMisc-bash/tools/", # Avoid duplicate module named 'tools'
|
|
]
|
|
|
|
# Standalone tool scripts outside python_pkg/ are not held to strict mypy rules
|
|
[[tool.mypy.overrides]]
|
|
module = "linux_configuration.*"
|
|
ignore_errors = true
|
|
|
|
# External libraries without bundled stubs - suppress import-untyped errors
|
|
[[tool.mypy.overrides]]
|
|
module = ["requests.*", "rich.*", "anthropic.*", "typer.*"]
|
|
ignore_missing_imports = true
|
|
|
|
# code_tutor uses TYPE_CHECKING-only imports for rich/typer; types propagate as
|
|
# Any at call sites because those libs lack complete stubs in this env.
|
|
# untyped-decorator fires for @app.command() which is a Typer pattern.
|
|
[[tool.mypy.overrides]]
|
|
module = "python_pkg.code_tutor.*"
|
|
disallow_any_unimported = false
|
|
disallow_untyped_decorators = false
|
|
|
|
# ============================================================================
|
|
# PYLINT - Comprehensive Python linter
|
|
# ============================================================================
|
|
[tool.pylint.main]
|
|
# Make standalone script dirs importable so tests that import their modules by
|
|
# bare name (the same dirs linux_configuration/tests/conftest.py adds to
|
|
# sys.path at runtime) resolve under static analysis instead of raising E0401.
|
|
# Paths are relative to the repo root, which is pre-commit's working directory.
|
|
init-hook = "import sys; sys.path[:0] = ['meta/scripts', 'python_pkg', 'phone_focus_mode', 'phone_focus_mode/lib', 'linux_configuration/scripts/single_use/utils', 'linux_configuration/scripts/periodic_background/system-maintenance/bin', 'linux_configuration/scripts/periodic_background/digital_wellbeing', 'linux_configuration/scripts/single_use/misc/testsAndMisc-bash/tools']"
|
|
# Analyse import fallback blocks
|
|
analyse-fallback-blocks = true
|
|
# Pickle collected data for later comparisons
|
|
persistent = true
|
|
# Jobs to use for parallel execution (0 = auto)
|
|
jobs = 0
|
|
# Minimum Python version
|
|
py-version = "3.10"
|
|
# Ignore vendored directories. "tests" and "conftest.py" are basename
|
|
# matches: test suites and pytest fixtures intentionally use patterns
|
|
# (protected-access, missing docstrings, fixture-arg shadowing) that don't
|
|
# apply to source code, so they are linted separately (not by this hook).
|
|
ignore = ["Bash", ".venv", "__pycache__", "tests", "conftest.py"]
|
|
# Ignore patterns
|
|
ignore-patterns = [".*\\.pyi$"]
|
|
# Allow C extension modules to be introspected
|
|
extension-pkg-allow-list = ["cv2", "pygame", "lxml"]
|
|
|
|
[tool.pylint.messages_control]
|
|
# Enable all checks by disabling disable
|
|
enable = "all"
|
|
# Globally disabled checks. Each is either a stylistic preference that conflicts
|
|
# with deliberate, clearer code, or a structural false positive that cannot be
|
|
# rewritten without harming readability. Everything else stays at max strictness.
|
|
disable = [
|
|
# use-implicit-booleaness family (C1803/C1804/C1805): pylint wants
|
|
# `not x` / `not s` instead of `x == 0` / `s == ""`. Explicit comparisons
|
|
# against 0 and "" state numeric/string intent more clearly than truthiness
|
|
# (and are not equivalent when the value may be None), so we keep them.
|
|
"use-implicit-booleaness-not-comparison",
|
|
"use-implicit-booleaness-not-comparison-to-string",
|
|
"use-implicit-booleaness-not-comparison-to-zero",
|
|
# consider-using-with (R1732): several subprocess.Popen calls are
|
|
# intentionally fire-and-forget — the process must outlive the calling
|
|
# scope and is polled/killed later, so a `with` block is wrong here.
|
|
"consider-using-with",
|
|
# unnecessary-ellipsis (W2301): Protocol abstract method bodies use `...`
|
|
# by Python convention (PEP 544); `pass` is equivalent but less idiomatic.
|
|
"unnecessary-ellipsis",
|
|
]
|
|
|
|
[tool.pylint.design]
|
|
# Mixins and single-entry-point classes may have zero public methods
|
|
min-public-methods = 0
|
|
# Test modules can be large
|
|
max-module-lines = 1000
|
|
# UI/mixin classes accumulate attributes across multiple mixins
|
|
max-attributes = 12
|
|
# code_tutor challenge flows genuinely need 6 params (matches ruff max-args)
|
|
max-args = 6
|
|
max-positional-arguments = 6
|
|
# Complex lesson/challenge functions need up to 20 locals
|
|
max-locals = 20
|
|
|
|
[tool.pylint.basic]
|
|
# Allow standard snake_case methods AND AST NodeVisitor visit_CapWords methods
|
|
method-rgx = "[a-z_][a-z0-9_]{2,}$|visit_[A-Za-z][A-Za-z0-9_]*"
|
|
|
|
[tool.pylint.similarities]
|
|
# 6 lines: CodeItem and ItemData share id/file/type/name/start_line/end_line fields
|
|
# intentionally (mirror the same domain schema). Raise the threshold so pylint
|
|
# doesn't flag this intentional structural duplication.
|
|
min-similarity-lines = 10
|
|
|
|
[tool.pylint.spelling]
|
|
# No spelling dictionary to avoid false positives
|
|
spelling-dict = ""
|
|
|
|
[tool.pylint.typecheck]
|
|
# cv2 (OpenCV) dynamically loads members from C extension at runtime.
|
|
# unittest.mock.MagicMock generates assertion/introspection methods at runtime.
|
|
# wave.open(path, "wb") returns a Wave_write, but pylint's stdlib stub infers the
|
|
# read-mode Wave_read overload and wrongly reports its setter/writer methods as
|
|
# missing — list them so the write API is recognised.
|
|
generated-members = [
|
|
"cv2.*",
|
|
".*\\.assert_called_once_with",
|
|
".*\\.assert_called_once",
|
|
".*\\.assert_called",
|
|
".*\\.assert_not_called",
|
|
".*\\.assert_any_call",
|
|
".*\\.call_args",
|
|
".*\\.call_args_list",
|
|
".*\\.call_count",
|
|
".*\\.setnchannels",
|
|
".*\\.setsampwidth",
|
|
".*\\.setframerate",
|
|
".*\\.writeframes",
|
|
".*\\.writeframesraw",
|
|
]
|
|
|
|
# ============================================================================
|
|
# BANDIT - Security linter
|
|
# ============================================================================
|
|
[tool.bandit]
|
|
# Exclude test directories and vendored code
|
|
exclude_dirs = ["tests", ".venv", "Bash/ffmpeg-build"]
|
|
|
|
# ============================================================================
|
|
# BLACK & ISORT - Removed (ruff handles formatting and import sorting)
|
|
# ============================================================================
|
|
|
|
# ============================================================================
|
|
# PYTEST - Testing framework configuration
|
|
# ============================================================================
|
|
[tool.pytest.ini_options]
|
|
# linux_configuration/tests covers the standalone usage_report scripts. It adds
|
|
# no python_pkg coverage (those tests don't import python_pkg), so running it
|
|
# alongside leaves the 100%-on-python_pkg gate untouched.
|
|
testpaths = ["python_pkg", "linux_configuration/tests"]
|
|
python_files = ["test_*.py", "*_test.py"]
|
|
python_classes = ["Test*"]
|
|
python_functions = ["test_*"]
|
|
addopts = [
|
|
"-v",
|
|
"--strict-markers",
|
|
"--strict-config",
|
|
"-ra",
|
|
"--cov=python_pkg",
|
|
"--cov-branch",
|
|
"--cov-report=term-missing",
|
|
"--cov-report=lcov",
|
|
]
|
|
filterwarnings = [
|
|
"error",
|
|
"ignore::DeprecationWarning",
|
|
"default::pytest.PytestUnraisableExceptionWarning",
|
|
]
|
|
|
|
# ============================================================================
|
|
# COVERAGE - Code coverage configuration
|
|
# ============================================================================
|
|
[tool.coverage.run]
|
|
source = ["python_pkg"]
|
|
branch = true
|
|
omit = [
|
|
"*/__pycache__/*",
|
|
"*/tests/*",
|
|
"*/.venv/*",
|
|
]
|
|
|
|
[tool.coverage.report]
|
|
# Fail under this percentage
|
|
fail_under = 100
|
|
show_missing = true
|
|
skip_covered = false
|
|
exclude_lines = [
|
|
# Standard exclusions
|
|
"pragma: no cover",
|
|
# Unreachable defensive code
|
|
"raise NotImplementedError",
|
|
"raise AssertionError",
|
|
# Type checking imports
|
|
"if TYPE_CHECKING:",
|
|
# Main script entry point
|
|
'if __name__ == "__main__":',
|
|
]
|
|
# Partial branch exclusions for unreachable branches
|
|
partial_branches = [
|
|
"pragma: no branch",
|
|
]
|
|
|
|
# ============================================================================
|
|
# VULTURE - Dead code detection
|
|
# ============================================================================
|
|
# Note: Vulture uses command-line args, but we can document settings here
|
|
# vulture --min-confidence 80 --exclude ".venv,Bash" .
|
|
|
|
# ============================================================================
|
|
# FLAKE8 - Python linter (via Flake8-pyproject for pyproject.toml support)
|
|
# ============================================================================
|
|
[tool.flake8]
|
|
# Maximum line length (matches ruff/black)
|
|
max-line-length = 88
|
|
# Maximum McCabe complexity (matches ruff C901 threshold)
|
|
max-complexity = 10
|
|
# Maximum cognitive complexity (flake8-cognitive-complexity)
|
|
max-cognitive-complexity = 12
|
|
# Maximum function length (flake8-functions)
|
|
max-function-length = 20
|
|
# Maximum returns/arguments per function
|
|
max-returns-amount = 6
|
|
max-arguments = 5
|
|
# Docstring convention (matches ruff)
|
|
docstring-convention = "google"
|
|
# Select all error codes
|
|
select = ["E", "F", "W", "C", "B", "B950"]
|
|
# Extend with plugin codes
|
|
extend-select = ["B", "B9", "C4", "SIM", "PT", "TC", "ANN"]
|
|
# Ignore rules that conflict with ruff-format or are duplicated
|
|
extend-ignore = [
|
|
"E501", # Line too long - B950 from bugbear is smarter (allows 10% overflow)
|
|
"W503", # Line break before binary operator - contradicts PEP 8 update
|
|
"ANN101", # Missing type annotation for self
|
|
"ANN102", # Missing type annotation for cls
|
|
]
|
|
# Exclude directories
|
|
exclude = [
|
|
".git",
|
|
".venv",
|
|
"__pycache__",
|
|
"build",
|
|
"dist",
|
|
".eggs",
|
|
"Bash/ffmpeg-build",
|
|
]
|
|
# Per-file ignores
|
|
per-file-ignores = [
|
|
"**/tests/**/*.py:S101,ANN",
|
|
"**/test_*.py:S101,ANN",
|
|
]
|
|
|
|
# ============================================================================
|
|
# PYDOCSTYLE - Docstring style checker (ruff handles this, but for standalone)
|
|
# ============================================================================
|
|
# Configured in ruff.lint.pydocstyle above
|