[project] name = "noteflow" version = "0.1.0" description = "Intelligent Meeting Notetaker - Local-first capture + navigable recall + evidence-linked summaries" readme = "README.md" requires-python = ">=3.12" dependencies = [ # Core "pydantic>=2.0", # Spike 3: ASR "faster-whisper>=1.0", # Spike 4: Encryption "keyring>=25.0", "cryptography>=42.0", # gRPC Client-Server "grpcio>=1.60", "protobuf>=4.25", # Database (async PostgreSQL + pgvector) "sqlalchemy[asyncio]>=2.0", "asyncpg>=0.29", "pgvector>=0.3", "alembic>=1.13", # Settings "pydantic-settings>=2.0", "psutil>=7.1.3", # HTTP client for webhooks and integrations "httpx>=0.27", "authlib>=1.6.6", "rich>=14.2.0", # Structured logging "structlog>=24.0", "sounddevice>=0.5.3", "spacy>=3.8.11", "openai-whisper>=20250625", "langgraph>=1.0.6", "langgraph-checkpoint-postgres>=3.0.3", "psycopg>=3.3.2", ] [project.optional-dependencies] audio = [ "sounddevice>=0.4.6", "numpy>=1.26", ] dev = [ "pytest>=8.0", "pytest-cov>=4.0", "pytest-asyncio>=0.23", "mypy>=1.8", "ruff>=0.3", "basedpyright>=1.18", "grpcio-tools>=1.60", "pyrefly>=0.46.1", "sourcery; sys_platform == 'darwin'", "types-grpcio==1.0.0.20251001", "types-psutil>=7.2.0.20251228", "testcontainers[postgres]>=4.0", ] triggers = [ "pywinctl>=0.3", ] summarization = [ "ollama>=0.6.1", "openai>=2.13.0", "anthropic>=0.75.0", ] diarization = [ "pyannote.audio>=3.3", "diart>=0.9.2", "torch>=2.0", ] pdf = [ "weasyprint>=67.0", ] ner = [ "spacy>=3.8.11", ] ner-gliner = [ "gliner>=0.2.24", ] calendar = [ "google-api-python-client>=2.100", "google-auth>=2.23", "google-auth-oauthlib>=1.1", ] rocm = [ # ROCm GPU support for AMD GPUs # Requires PyTorch with ROCm support (install separately) # pip install torch --index-url https://download.pytorch.org/whl/rocm6.2 ] rocm-ctranslate2 = [ # Optional: CTranslate2-ROCm for faster inference # Install manually: pip install git+https://github.com/arlo-phoenix/CTranslate2-rocm.git "faster-whisper>=1.0", ] observability = [ "opentelemetry-api>=1.28", "opentelemetry-sdk>=1.28", "opentelemetry-instrumentation-grpc>=0.49b", "opentelemetry-exporter-otlp>=1.28", ] optional = [ # Audio "sounddevice>=0.4.6", "numpy>=1.26", # Triggers "pywinctl>=0.3", # Summarization "ollama>=0.6.1", "openai>=2.13.0", "anthropic>=0.75.0", # Diarization "pyannote.audio>=3.3", "diart>=0.9.2", "torch>=2.0", # PDF export "weasyprint>=67.0", # NER (spaCy backend) "spacy>=3.8.11", # NER (GLiNER backend) "gliner>=0.2.24", # Calendar "google-api-python-client>=2.100", "google-auth>=2.23", "google-auth-oauthlib>=1.1", # Observability "opentelemetry-api>=1.28", "opentelemetry-sdk>=1.28", "opentelemetry-instrumentation-grpc>=0.49b", "opentelemetry-exporter-otlp>=1.28", ] all = [ "noteflow[audio,dev,triggers,summarization,diarization,pdf,ner,ner-gliner,calendar,observability]", ] ollama = [ "anthropic>=0.75.0", "openai>=2.13.0", "pywinctl>=0.4.1", ] [build-system] requires = ["hatchling"] build-backend = "hatchling.build" [tool.hatch.build.targets.wheel] packages = ["src/noteflow", "spikes"] [tool.ruff] line-length = 100 target-version = "py312" extend-exclude = ["*_pb2.py", "*_pb2_grpc.py", "*_pb2.pyi", ".venv"] [tool.ruff.lint] select = [ "E", # pycodestyle errors "W", # pycodestyle warnings "F", # Pyflakes "I", # isort "B", # flake8-bugbear "C4", # flake8-comprehensions "UP", # pyupgrade "SIM", # flake8-simplify "RUF", # Ruff-specific rules ] ignore = [ "E501", # Line length handled by formatter ] [tool.ruff.lint.per-file-ignores] "**/grpc/service.py" = ["TC002", "TC003"] # numpy/Iterator used at runtime "tests/quality/*.py" = ["SIM102"] # AST pattern matching needs nested conditionals [tool.mypy] python_version = "3.12" strict = true warn_return_any = true warn_unused_configs = true exclude = [".venv", ".*_pb2\\.py$", ".*_pb2_grpc\\.py$", ".*_pb2\\.pyi$"] plugins = ["sqlalchemy.ext.mypy.plugin"] [[tool.mypy.overrides]] module = [ "diart.*", "gliner.*", "pyannote.*", "faster_whisper.*", "sounddevice.*", "pgvector.*", "asyncpg.*", "noteflow.grpc.proto.noteflow_pb2", "noteflow.grpc.proto.noteflow_pb2_grpc", ] ignore_missing_imports = true ignore_errors = true [[tool.mypy.overrides]] module = ["noteflow.infrastructure.persistence.models.*"] # SQLAlchemy 2.0 declarative models use ClassVar for __tablename__ and __table_args__ # The mypy plugin handles this but has edge cases with some patterns disable_error_code = ["misc"] [[tool.mypy.overrides]] module = ["noteflow.grpc._mixins.*", "noteflow.grpc._client_mixins.*"] # gRPC ServicerContext is a generic type but the type parameters are complex # and rarely needed in practice. Mixin protocols use structural typing. # attr-defined: Streaming mixin methods are implemented in service.py, not the protocol # no-any-return: Some methods return proto-generated types that resolve to Any # arg-type: Proto enums accept ints at runtime; generated stubs are overly strict # assignment: Proto response fields accept int values for enum types disable_error_code = ["type-arg", "attr-defined", "no-any-return", "arg-type", "assignment"] [[tool.mypy.overrides]] module = ["noteflow.grpc.client"] # Client mixin composition uses Protocol-based typing that mypy struggles with # The "Invalid self argument" errors are false positives for correctly typed mixins disable_error_code = ["misc", "no-untyped-call"] [[tool.mypy.overrides]] module = ["noteflow.grpc.service"] # ServicerContext generic type parameters are complex (request/response types) # and not needed for runtime behavior. The service works with ServicerContext as-is. disable_error_code = ["type-arg"] [[tool.mypy.overrides]] module = ["noteflow.grpc.server"] # add_NoteFlowServiceServicer_to_server is generated by grpc_tools.protoc # and lacks type annotations in the generated stubs disable_error_code = ["no-untyped-call"] [[tool.mypy.overrides]] module = ["noteflow.infrastructure.persistence.database"] # asyncpg is installed but lacks type stubs or py.typed marker disable_error_code = ["import-untyped"] [tool.basedpyright] pythonVersion = "3.12" typeCheckingMode = "strict" extraPaths = ["scripts", "src", "google"] stubPath = "typings" reportMissingTypeStubs = false reportUnknownMemberType = true reportUnknownArgumentType = true reportUnknownVariableType = true reportArgumentType = false # proto enums accept ints at runtime reportIncompatibleVariableOverride = false # SQLAlchemy __table_args__ reportAttributeAccessIssue = false # SQLAlchemy mapped column assignments reportMissingImports = "warning" # Optional deps (audio, summarization, triggers, etc.) may not be installed exclude = [ "**/proto/*_pb2*.py", "**/proto/*_pb2*.pyi", "**/node_modules", "**/node_modules/**", "client", "client/**", ".venv", ".venv/", ".venv/**", ".benchmarks", ".benchmarks/**", ".hygeine", ".hygeine/**", "e2e-native", "e2e-native/**", ".git", ".git/**", "**/__pycache__", "**/*.pyc", "**/dist", "**/build", "workspaces", ] venvPath = "." venv = ".venv" [tool.pyrefly] python-version = "3.12" python-interpreter-path = ".venv/bin/python" site-package-path = [".venv/lib/python3.12/site-packages"] search-path = [".", "src", "tests", "typings"] project-includes = ["src", "tests"] project-excludes = ["**/proto/*_pb2*.py", "**/proto/*_pb2*.pyi"] ignore-missing-imports = [] replace-imports-with-any = [] ignore-errors-in-generated-code = true untyped-def-behavior = "check-and-infer-return-type" use-ignore-files = true permissive-ignores = false [tool.pytest.ini_options] testpaths = ["tests"] python_files = ["test_*.py"] python_functions = ["test_*"] addopts = "-v --tb=short" asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" markers = [ "slow: marks tests as slow (model loading)", "integration: marks tests requiring external services", "stress: marks stress/concurrency tests", ] filterwarnings = [ "ignore:The @wait_container_is_ready decorator is deprecated.*:DeprecationWarning:testcontainers.core.waiting_utils", ] [dependency-groups] dev = [ "basedpyright>=1.36.1", "gliner>=0.2.24", "protobuf>=6.33.2", "pyrefly>=0.46.1", "pytest-benchmark>=5.2.3", "pytest-httpx>=0.36.0", "ruff>=0.14.9", "sourcery; sys_platform == 'darwin'", "spacy>=3.8.11", "types-grpcio==1.0.0.20251001", "watchfiles>=1.1.1", ]