Skip to content

Refactor: package fafycat as an installable Python package#19

Merged
davidchris merged 10 commits intomainfrom
package
Apr 15, 2026
Merged

Refactor: package fafycat as an installable Python package#19
davidchris merged 10 commits intomainfrom
package

Conversation

@davidchris
Copy link
Copy Markdown
Owner

Summary

  • Moves all application code under src/fafycat/ (api, web, static, app.py, cli.py)
  • Adds uv_build build backend so uv tool install fafycat works end-to-end
  • Replaces run_dev.py, run_prod.py, and root cli.py with a single src/fafycat/cli.py entry point (fafycat serve, fafycat import, fafycat init)
  • User data (DB, models, exports) now defaults to the platform user-data directory via platformdirs; --data-dir or env vars still override
  • Updates all import paths across src/, tests/, scripts/, and simulations/
  • Fixes static file serving to use package-relative path
  • Adds pythonpath = ["."] to pytest config so simulations/ tests remain importable under the src layout
  • Excludes .claude/ worktrees from ty type checking

Test plan

  • uv sync succeeds
  • uv run fafycat --help works
  • uv run fafycat serve --dev starts the app
  • uv tool install . --force && fafycat --help works
  • All 188 tests pass (uv run pytest)

🤖 Generated with Claude Code

davidchris and others added 6 commits April 15, 2026 15:37
Centralize refactor-fragile imports in a single conftest touchpoint
(importlib block), add an app_factory fixture that replaces import-time
app construction, and autouse-isolate FAFYCAT_* env vars across tests.
Add parity tests (app factory, static serving, CLI via subprocess,
AppConfig env-var contract) that hold both pre- and post-refactor and
serve as the green light when packaging lands. Partition manual, perf,
and server-required tests behind opt-in markers so the default pytest
run is hermetic.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Remaining pre-refactor test-suite changes missed in the previous commit
(ede7d79): the conftest.py rewrite with importlib touchpoint, pytest
marker partitioning in pyproject.toml + manual/perf test modules, and
fixture rebases in test_analytics_api.py / test_ml_api.py /
test_ml_endpoints_simple.py / test_ml_e2e.py.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 971aa5e850

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fafycat/cli.py Outdated

resolved_data_dir = data_dir.resolve()
os.environ["FAFYCAT_DATA_DIR"] = str(resolved_data_dir)
os.environ.setdefault("FAFYCAT_DB_URL", f"sqlite:///{resolved_data_dir / 'fafycat.db'}")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve dev/prod DB split when --data-dir is provided

cmd_serve computes mode-specific database names (fafycat_dev.db / fafycat_prod.db), but _apply_data_dir_override sets FAFYCAT_DB_URL to .../fafycat.db first. Because the later mode-specific branch only runs when FAFYCAT_DB_URL is absent, both fafycat serve --dev --data-dir ... and fafycat serve --data-dir ... end up using the same database file, which can mix synthetic development data into production data whenever users rely on --data-dir.

Useful? React with 👍 / 👎.

@davidchris
Copy link
Copy Markdown
Owner Author

Code review

Found 2 issues:

  1. --data-dir silently bypasses dev/prod database naming in cmd_serve. _apply_data_dir_override calls os.environ.setdefault("FAFYCAT_DB_URL", ".../**fafycat.db**") on line 17 before cmd_serve picks fafycat_dev.db / fafycat_prod.db. The guard on line 60 (if "FAFYCAT_DB_URL" not in os.environ) is then always False when --data-dir is supplied, so db_name is set but never used. fafycat serve --dev --data-dir /custom connects to /custom/fafycat.db instead of /custom/fafycat_dev.db, breaking dev/prod isolation when a custom data dir is used.

_apply_data_dir_override(args.data_dir)
if args.dev:
os.environ.setdefault("FAFYCAT_ENV", "development")
db_name = "fafycat_dev.db"
else:
os.environ.setdefault("FAFYCAT_ENV", "production")
db_name = "fafycat_prod.db"
if "FAFYCAT_DB_URL" not in os.environ:
from platformdirs import user_data_dir
data_dir = Path(os.getenv("FAFYCAT_DATA_DIR", user_data_dir("fafycat")))
os.environ["FAFYCAT_DB_URL"] = f"sqlite:///{data_dir / db_name}"

  1. The root-level --data-dir (line 176) is silently ignored. Each subparser also registers --data-dir with default=None. When argparse dispatches to a subparser it applies the subparser's defaults to the shared namespace, overwriting the root-level value. Running fafycat --data-dir /foo serve yields args.data_dir = None — only fafycat serve --data-dir /foo works. The _add_data_dir_argument(parser) call at line 176 is dead code and misleading.

fafycat/src/fafycat/cli.py

Lines 174 to 200 in 971aa5e

description="FafyCat - Local-first transaction categorization with ML",
)
_add_data_dir_argument(parser)
subparsers = parser.add_subparsers(dest="command")
serve_parser = subparsers.add_parser("serve", help="Start the web server")
serve_parser.add_argument(
"--dev",
action="store_true",
help="Development mode (hot reload, synthetic data, port 8001)",
)
serve_parser.add_argument(
"--port",
type=int,
default=None,
help="Port number (default: 8001 for dev, 8000 for prod)",
)
serve_parser.add_argument("--host", default=None, help="Host to bind to (default: 127.0.0.1)")
_add_data_dir_argument(serve_parser)
import_parser = subparsers.add_parser("import", help="Import transactions from a CSV file")
import_parser.add_argument("file", type=Path, help="Path to the CSV file")
_add_data_dir_argument(import_parser)
init_parser = subparsers.add_parser("init", help="Initialize data directory and default categories")
_add_data_dir_argument(init_parser)

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

_apply_data_dir_override set FAFYCAT_DB_URL to the generic
fafycat.db before cmd_serve could determine the mode-specific
name, causing both modes to share one file when --data-dir
was given.

Also remove dead _add_data_dir_argument call on the root
parser — argparse subparser defaults silently overwrote it,
so `fafycat --data-dir /x serve` yielded args.data_dir=None.
@davidchris davidchris merged commit d4e6b27 into main Apr 15, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant