[project] name = "topic-watch" version = "1.5.3" description = "Self-hosted news watchdog with AI-powered novelty detection" license = "GPL-3.0-or-later" readme = "README.md" authors = [{ name = "0xzerolight" }] maintainers = [{ name = "0xzerolight" }] keywords = ["rss", "news", "llm", "notifications", "self-hosted", "novelty-detection"] requires-python = ">=3.11" classifiers = [ "Development Status :: 4 - Beta", "Environment :: Web Environment", "Framework :: FastAPI", "Intended Audience :: End Users/Desktop", "Intended Audience :: System Administrators", "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Operating System :: OS Independent", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "Topic :: Internet :: WWW/HTTP :: Indexing/Search", "Topic :: Communications", "Topic :: Office/Business :: News/Diary", ] dependencies = [ "fastapi>=0.115,<1.0", "uvicorn[standard]>=0.32,<1.0", "jinja2>=3.1,<4.0", "markdown-it-py>=3.0,<5.0", "litellm>=1.50,<2.0", "instructor>=1.7,<2.0", "tenacity>=8.2.3,<10.0", "feedparser>=6.0,<7.0", "httpx>=0.27,<1.0", "trafilatura>=2.0,<3.0", "apprise>=1.9,<2.0", "apscheduler>=3.10,<4.0", "pydantic>=2.10,<3.0", "pydantic-settings[yaml]>=2.7,<3.0", "python-multipart>=0.0.9,<1.0", ] [project.scripts] topic-watch = "app.cli:main" [project.urls] Homepage = "https://github.com/0xzerolight/topic_watch" Repository = "https://github.com/0xzerolight/topic_watch" Issues = "https://github.com/0xzerolight/topic_watch/issues" [project.optional-dependencies] dev = [ "pytest>=8.0,<10.0", "pytest-asyncio>=0.24,<2.0", "pytest-cov>=6.0,<8.0", "ruff>=0.8,<1.0", "mypy>=1.13,<3.0", "pre-commit>=4.0,<5.0", "types-PyYAML>=6.0", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["app"] [tool.ruff] target-version = "py311" line-length = 120 [tool.ruff.lint] select = [ "E", # pycodestyle errors "F", # pyflakes "W", # pycodestyle warnings "I", # isort (import sorting) "UP", # pyupgrade "B", # flake8-bugbear "SIM", # flake8-simplify "N", # pep8-naming "S", # flake8-bandit (security) "T20", # flake8-print (no print statements) ] ignore = [ "E501", # line too long (handled by formatter) "B008", # do not perform function calls in argument defaults (needed for FastAPI Depends()) "N818", # exception name should end in Error (not always appropriate) "S101", # assert usage (used intentionally in app for invariant checks) "S608", # SQL injection false positive (we use parameterized queries) "S704", # Markup usage is intentional and pre-escaped ] [tool.ruff.lint.per-file-ignores] "tests/**/*.py" = ["B011", "S101", "S105"] # allow asserts and test secrets in tests "app/cli.py" = ["T201"] # CLI is supposed to print output "evals/__main__.py" = ["T201"] # the eval harness CLI prints its output too "scripts/*.py" = ["T201"] # badge scripts write their output to stdout "app/migrations/**/*.py" = ["E501"] # migrations can have long lines [tool.ruff.lint.isort] known-first-party = ["app", "evals"] [tool.ruff.format] quote-style = "double" indent-style = "space" [tool.mypy] python_version = "3.11" warn_return_any = true warn_unused_configs = true ignore_missing_imports = true check_untyped_defs = true disallow_untyped_defs = false [tool.pytest.ini_options] testpaths = ["tests"] asyncio_mode = "auto" addopts = "--cov=app --cov-report=term-missing --cov-fail-under=85"