furu 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. furu-0.0.1/.github/workflows/ci.yml +33 -0
  2. furu-0.0.1/.github/workflows/release.yml +99 -0
  3. furu-0.0.1/.gitignore +231 -0
  4. furu-0.0.1/.vscode/settings.json +3 -0
  5. furu-0.0.1/AGENTS.md +265 -0
  6. furu-0.0.1/CHANGELOG.md +5 -0
  7. furu-0.0.1/Makefile +156 -0
  8. furu-0.0.1/PKG-INFO +502 -0
  9. furu-0.0.1/README.md +484 -0
  10. furu-0.0.1/TODO.md +131 -0
  11. furu-0.0.1/dashboard-frontend/bun.lock +1234 -0
  12. furu-0.0.1/dashboard-frontend/index.html +21 -0
  13. furu-0.0.1/dashboard-frontend/orval.config.ts +34 -0
  14. furu-0.0.1/dashboard-frontend/package-lock.json +7628 -0
  15. furu-0.0.1/dashboard-frontend/package.json +40 -0
  16. furu-0.0.1/dashboard-frontend/postcss.config.js +9 -0
  17. furu-0.0.1/dashboard-frontend/public/favicon.svg +10 -0
  18. furu-0.0.1/dashboard-frontend/src/api.test.ts +314 -0
  19. furu-0.0.1/dashboard-frontend/src/components/DAGVisualization.tsx +392 -0
  20. furu-0.0.1/dashboard-frontend/src/components/EmptyState.tsx +27 -0
  21. furu-0.0.1/dashboard-frontend/src/components/StatsCard.tsx +63 -0
  22. furu-0.0.1/dashboard-frontend/src/components/StatusBadge.tsx +40 -0
  23. furu-0.0.1/dashboard-frontend/src/components/ui/badge.tsx +35 -0
  24. furu-0.0.1/dashboard-frontend/src/components/ui/button.tsx +56 -0
  25. furu-0.0.1/dashboard-frontend/src/components/ui/card.tsx +70 -0
  26. furu-0.0.1/dashboard-frontend/src/components/ui/input.tsx +20 -0
  27. furu-0.0.1/dashboard-frontend/src/components/ui/table.tsx +96 -0
  28. furu-0.0.1/dashboard-frontend/src/index.css +78 -0
  29. furu-0.0.1/dashboard-frontend/src/lib/api-client.ts +26 -0
  30. furu-0.0.1/dashboard-frontend/src/lib/utils.ts +6 -0
  31. furu-0.0.1/dashboard-frontend/src/main.tsx +52 -0
  32. furu-0.0.1/dashboard-frontend/src/routes/__root.tsx +68 -0
  33. furu-0.0.1/dashboard-frontend/src/routes/dag.tsx +113 -0
  34. furu-0.0.1/dashboard-frontend/src/routes/experiments.tsx +444 -0
  35. furu-0.0.1/dashboard-frontend/src/routes/experiments_.$namespace.$furu_hash.tsx +804 -0
  36. furu-0.0.1/dashboard-frontend/src/routes/index.tsx +195 -0
  37. furu-0.0.1/dashboard-frontend/tailwind.config.js +41 -0
  38. furu-0.0.1/dashboard-frontend/tsconfig.json +24 -0
  39. furu-0.0.1/dashboard-frontend/tsconfig.node.json +14 -0
  40. furu-0.0.1/dashboard-frontend/vite.config.ts +21 -0
  41. furu-0.0.1/e2e/bun.lock +21 -0
  42. furu-0.0.1/e2e/generate_data.py +347 -0
  43. furu-0.0.1/e2e/global-setup.ts +37 -0
  44. furu-0.0.1/e2e/global-teardown.ts +14 -0
  45. furu-0.0.1/e2e/package.json +15 -0
  46. furu-0.0.1/e2e/playwright.config.ts +66 -0
  47. furu-0.0.1/e2e/tests/api.spec.ts +182 -0
  48. furu-0.0.1/e2e/tests/experiments.spec.ts +163 -0
  49. furu-0.0.1/e2e/tests/home.spec.ts +36 -0
  50. furu-0.0.1/e2e/tests/navigation.spec.ts +435 -0
  51. furu-0.0.1/examples/README.md +21 -0
  52. furu-0.0.1/examples/my_project/__init__.py +3 -0
  53. furu-0.0.1/examples/my_project/pipelines.py +65 -0
  54. furu-0.0.1/examples/run_logging.py +26 -0
  55. furu-0.0.1/examples/run_nested.py +26 -0
  56. furu-0.0.1/examples/run_train.py +26 -0
  57. furu-0.0.1/pyproject.toml +56 -0
  58. furu-0.0.1/src/furu/__init__.py +82 -0
  59. furu-0.0.1/src/furu/adapters/__init__.py +3 -0
  60. furu-0.0.1/src/furu/adapters/submitit.py +195 -0
  61. furu-0.0.1/src/furu/config.py +98 -0
  62. furu-0.0.1/src/furu/core/__init__.py +4 -0
  63. furu-0.0.1/src/furu/core/furu.py +999 -0
  64. furu-0.0.1/src/furu/core/list.py +123 -0
  65. furu-0.0.1/src/furu/dashboard/__init__.py +9 -0
  66. furu-0.0.1/src/furu/dashboard/__main__.py +7 -0
  67. furu-0.0.1/src/furu/dashboard/api/__init__.py +7 -0
  68. furu-0.0.1/src/furu/dashboard/api/models.py +170 -0
  69. furu-0.0.1/src/furu/dashboard/api/routes.py +135 -0
  70. furu-0.0.1/src/furu/dashboard/frontend/dist/assets/index-CbdDfSOZ.css +1 -0
  71. furu-0.0.1/src/furu/dashboard/frontend/dist/assets/index-DDv_TYB_.js +67 -0
  72. furu-0.0.1/src/furu/dashboard/frontend/dist/favicon.svg +10 -0
  73. furu-0.0.1/src/furu/dashboard/frontend/dist/index.html +22 -0
  74. furu-0.0.1/src/furu/dashboard/main.py +134 -0
  75. furu-0.0.1/src/furu/dashboard/scanner.py +931 -0
  76. furu-0.0.1/src/furu/errors.py +76 -0
  77. furu-0.0.1/src/furu/migrate.py +48 -0
  78. furu-0.0.1/src/furu/migration.py +926 -0
  79. furu-0.0.1/src/furu/runtime/__init__.py +27 -0
  80. furu-0.0.1/src/furu/runtime/env.py +8 -0
  81. furu-0.0.1/src/furu/runtime/logging.py +301 -0
  82. furu-0.0.1/src/furu/runtime/tracebacks.py +64 -0
  83. furu-0.0.1/src/furu/serialization/__init__.py +20 -0
  84. furu-0.0.1/src/furu/serialization/migrations.py +246 -0
  85. furu-0.0.1/src/furu/serialization/serializer.py +233 -0
  86. furu-0.0.1/src/furu/storage/__init__.py +32 -0
  87. furu-0.0.1/src/furu/storage/metadata.py +282 -0
  88. furu-0.0.1/src/furu/storage/migration.py +81 -0
  89. furu-0.0.1/src/furu/storage/state.py +1107 -0
  90. furu-0.0.1/tasks/migration.md +360 -0
  91. furu-0.0.1/tests/conftest.py +25 -0
  92. furu-0.0.1/tests/dashboard/__init__.py +4 -0
  93. furu-0.0.1/tests/dashboard/conftest.py +448 -0
  94. furu-0.0.1/tests/dashboard/pipelines.py +192 -0
  95. furu-0.0.1/tests/dashboard/test_api.py +865 -0
  96. furu-0.0.1/tests/dashboard/test_scanner.py +786 -0
  97. furu-0.0.1/tests/test_config.py +10 -0
  98. furu-0.0.1/tests/test_errors.py +83 -0
  99. furu-0.0.1/tests/test_furu_core.py +158 -0
  100. furu-0.0.1/tests/test_furu_inheritance.py +44 -0
  101. furu-0.0.1/tests/test_furu_inheritance_polymorphic.py +118 -0
  102. furu-0.0.1/tests/test_furu_list.py +30 -0
  103. furu-0.0.1/tests/test_furu_typing.py +208 -0
  104. furu-0.0.1/tests/test_logger.py +141 -0
  105. furu-0.0.1/tests/test_metadata.py +46 -0
  106. furu-0.0.1/tests/test_migrations.py +848 -0
  107. furu-0.0.1/tests/test_raw_dir.py +15 -0
  108. furu-0.0.1/tests/test_serializer.py +71 -0
  109. furu-0.0.1/tests/test_state_manager.py +464 -0
  110. furu-0.0.1/tests/test_submitit_path.py +71 -0
  111. furu-0.0.1/tests/test_tracebacks.py +5 -0
  112. furu-0.0.1/uv.lock +745 -0
  113. furu-0.0.1/version.txt +1 -0
@@ -0,0 +1,33 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+
6
+ jobs:
7
+ test:
8
+ runs-on: ubuntu-latest
9
+ steps:
10
+ - uses: actions/checkout@v4
11
+
12
+ - name: Install uv
13
+ uses: astral-sh/setup-uv@v4
14
+
15
+ - name: Set up Python
16
+ run: uv python install
17
+
18
+ - name: Install bun
19
+ uses: oven-sh/setup-bun@v2
20
+
21
+ - name: Install dependencies
22
+ run: |
23
+ uv sync --all-extras
24
+ cd dashboard-frontend && bun install
25
+
26
+ - name: Install e2e dependencies
27
+ run: make dashboard-install-e2e
28
+
29
+ - name: Build frontend artifacts
30
+ run: make frontend-build
31
+
32
+ - name: Run tests
33
+ run: make test-all
@@ -0,0 +1,99 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ paths:
8
+ - pyproject.toml
9
+
10
+ permissions:
11
+ contents: write
12
+ id-token: write
13
+
14
+ jobs:
15
+ release:
16
+ runs-on: ubuntu-latest
17
+ environment: pypi
18
+ steps:
19
+ - uses: actions/checkout@v4
20
+ with:
21
+ fetch-depth: 0
22
+
23
+ - name: Install uv
24
+ uses: astral-sh/setup-uv@v4
25
+
26
+ - name: Set up Python
27
+ run: uv python install
28
+
29
+ - name: Install bun
30
+ uses: oven-sh/setup-bun@v2
31
+
32
+ - name: Read version
33
+ id: version
34
+ run: |
35
+ python - <<'PY'
36
+ import tomllib
37
+ from pathlib import Path
38
+
39
+ data = tomllib.loads(Path("pyproject.toml").read_text())
40
+ version = data["project"]["version"]
41
+ print(f"version={version}")
42
+ Path("version.txt").write_text(version)
43
+ PY
44
+ echo "version=$(cat version.txt)" >> $GITHUB_OUTPUT
45
+
46
+ - name: Check release
47
+ id: tag
48
+ run: |
49
+ git fetch --tags
50
+ if git rev-parse "v${{ steps.version.outputs.version }}" >/dev/null 2>&1; then
51
+ echo "should_release=false" >> $GITHUB_OUTPUT
52
+ exit 0
53
+ fi
54
+
55
+ if git diff -U0 HEAD^ HEAD -- pyproject.toml | grep -E '^[+-]version = "' >/dev/null; then
56
+ echo "should_release=true" >> $GITHUB_OUTPUT
57
+ else
58
+ echo "should_release=false" >> $GITHUB_OUTPUT
59
+ fi
60
+
61
+ - name: Install dependencies
62
+ if: steps.tag.outputs.should_release == 'true'
63
+ run: |
64
+ uv sync --all-extras
65
+ cd dashboard-frontend && bun install
66
+
67
+ - name: Install e2e dependencies
68
+ if: steps.tag.outputs.should_release == 'true'
69
+ run: make dashboard-install-e2e
70
+
71
+ - name: Build frontend
72
+ if: steps.tag.outputs.should_release == 'true'
73
+ run: make frontend-build
74
+
75
+ - name: Run tests
76
+ if: steps.tag.outputs.should_release == 'true'
77
+ run: make test-all
78
+
79
+ - name: Build package
80
+ if: steps.tag.outputs.should_release == 'true'
81
+ run: uv build
82
+
83
+ - name: Create tag
84
+ if: steps.tag.outputs.should_release == 'true'
85
+ run: |
86
+ git tag "v${{ steps.version.outputs.version }}"
87
+ git push origin "v${{ steps.version.outputs.version }}"
88
+
89
+ - name: Publish to PyPI
90
+ if: steps.tag.outputs.should_release == 'true'
91
+ uses: pypa/gh-action-pypi-publish@release/v1
92
+
93
+ - name: Create GitHub Release
94
+ if: steps.tag.outputs.should_release == 'true'
95
+ uses: softprops/action-gh-release@v2
96
+ with:
97
+ tag_name: "v${{ steps.version.outputs.version }}"
98
+ files: dist/*
99
+ generate_release_notes: true
furu-0.0.1/.gitignore ADDED
@@ -0,0 +1,231 @@
1
+ # Byte-compiled / optimized / DLL files
2
+ __pycache__/
3
+ *.py[codz]
4
+ *$py.class
5
+
6
+ # C extensions
7
+ *.so
8
+
9
+ # Distribution / packaging
10
+ .Python
11
+ build/
12
+ develop-eggs/
13
+ dist/
14
+ downloads/
15
+ eggs/
16
+ .eggs/
17
+ lib64/
18
+ parts/
19
+ sdist/
20
+ var/
21
+ wheels/
22
+ share/python-wheels/
23
+ *.egg-info/
24
+ .installed.cfg
25
+ *.egg
26
+ MANIFEST
27
+
28
+ # PyInstaller
29
+ # Usually these files are written by a python script from a template
30
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .nox/
42
+ .coverage
43
+ .coverage.*
44
+ .cache
45
+ nosetests.xml
46
+ coverage.xml
47
+ *.cover
48
+ *.py.cover
49
+ .hypothesis/
50
+ .pytest_cache/
51
+ cover/
52
+
53
+ # Translations
54
+ *.mo
55
+ *.pot
56
+
57
+ # Django stuff:
58
+ *.log
59
+ local_settings.py
60
+ db.sqlite3
61
+ db.sqlite3-journal
62
+
63
+ # Flask stuff:
64
+ instance/
65
+ .webassets-cache
66
+
67
+ # Scrapy stuff:
68
+ .scrapy
69
+
70
+ # Sphinx documentation
71
+ docs/_build/
72
+
73
+ # PyBuilder
74
+ .pybuilder/
75
+ target/
76
+
77
+ # Jupyter Notebook
78
+ .ipynb_checkpoints
79
+
80
+ # IPython
81
+ profile_default/
82
+ ipython_config.py
83
+
84
+ # pyenv
85
+ # For a library or package, you might want to ignore these files since the code is
86
+ # intended to run in multiple environments; otherwise, check them in:
87
+ # .python-version
88
+
89
+ # pipenv
90
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
91
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
92
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
93
+ # install all needed dependencies.
94
+ # Pipfile.lock
95
+
96
+ # UV
97
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
98
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
99
+ # commonly ignored for libraries.
100
+ # uv.lock
101
+
102
+ # poetry
103
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
104
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
105
+ # commonly ignored for libraries.
106
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
107
+ # poetry.lock
108
+ # poetry.toml
109
+
110
+ # pdm
111
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
112
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
113
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
114
+ # pdm.lock
115
+ # pdm.toml
116
+ .pdm-python
117
+ .pdm-build/
118
+
119
+ # pixi
120
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
121
+ # pixi.lock
122
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
123
+ # in the .venv directory. It is recommended not to include this directory in version control.
124
+ .pixi
125
+
126
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
127
+ __pypackages__/
128
+
129
+ # Celery stuff
130
+ celerybeat-schedule
131
+ celerybeat.pid
132
+
133
+ # Redis
134
+ *.rdb
135
+ *.aof
136
+ *.pid
137
+
138
+ # RabbitMQ
139
+ mnesia/
140
+ rabbitmq/
141
+ rabbitmq-data/
142
+
143
+ # ActiveMQ
144
+ activemq-data/
145
+
146
+ # SageMath parsed files
147
+ *.sage.py
148
+
149
+ # Environments
150
+ .env
151
+ .envrc
152
+ .venv
153
+ env/
154
+ venv/
155
+ ENV/
156
+ env.bak/
157
+ venv.bak/
158
+
159
+ # Spyder project settings
160
+ .spyderproject
161
+ .spyproject
162
+
163
+ # Rope project settings
164
+ .ropeproject
165
+
166
+ # mkdocs documentation
167
+ /site
168
+
169
+ # mypy
170
+ .mypy_cache/
171
+ .dmypy.json
172
+ dmypy.json
173
+
174
+ # Pyre type checker
175
+ .pyre/
176
+
177
+ # pytype static type analyzer
178
+ .pytype/
179
+
180
+ # Cython debug symbols
181
+ cython_debug/
182
+
183
+ # PyCharm
184
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
185
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
186
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
187
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
188
+ # .idea/
189
+
190
+ # Abstra
191
+ # Abstra is an AI-powered process automation framework.
192
+ # Ignore directories containing user credentials, local state, and settings.
193
+ # Learn more at https://abstra.io/docs
194
+ .abstra/
195
+
196
+ # Visual Studio Code
197
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
198
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
199
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
200
+ # you could uncomment the following to ignore the entire vscode folder
201
+ # .vscode/
202
+
203
+ # Ruff stuff:
204
+ .ruff_cache/
205
+
206
+ # PyPI configuration file
207
+ .pypirc
208
+
209
+ # Marimo
210
+ marimo/_static/
211
+ marimo/_lsp/
212
+ __marimo__/
213
+
214
+ # Streamlit
215
+ .streamlit/secrets.toml
216
+ **/.furu/data
217
+
218
+ dashboard-frontend/node_modules/
219
+ dashboard-frontend/src/routeTree.gen.ts
220
+ dashboard-frontend/src/api/
221
+
222
+ e2e/node_modules/
223
+ e2e/test-results/
224
+ e2e/playwright-report/
225
+
226
+ *.tsbuildinfo
227
+ orval.config.js
228
+ vite.config.js
229
+
230
+ /openapi.json
231
+ data-furu/data
@@ -0,0 +1,3 @@
1
+ {
2
+ "cSpell.words": ["huldra"]
3
+ }
furu-0.0.1/AGENTS.md ADDED
@@ -0,0 +1,265 @@
1
+ # Repository Guidelines for AI Agents
2
+
3
+ ## Critical Rules
4
+
5
+ **Always update `CHANGELOG.md`** for any user-visible change.
6
+
7
+ **DO NOT USE** the following patterns in this codebase:
8
+ - `typing.Optional` - Use `X | None` instead
9
+ - `typing.Any` - Only acceptable when the type is truly unknowable at compile time (e.g., deserializing arbitrary JSON, interacting with untyped third-party libraries). Always prefer protocols, generics, or concrete types.
10
+ - `object` as a type annotation - Use specific types, protocols, or generics instead
11
+ - `dict` without specific value types - Use Pydantic models, dataclasses, or TypedDict instead of `dict[str, Any]` or `dict[str, object]`. Simple dicts like `dict[str, str]` or `dict[str, int]` should also be avoided if it is possible to know exactly what the keys and structure of the data is.
12
+ - `try/except` for error recovery - Prefer happy path and let errors crash; only use try/except for cleanup or resource management
13
+ - Backward compatibility shims or aliases - Do not add backward compatibility code; refactor all usages directly
14
+
15
+ **ALWAYS** use `make` commands rather than running tools directly:
16
+ - `make lint` not `uv run ruff check && uv run ty check`
17
+ - `make test` not `uv run pytest`
18
+ - `make check` for both lint and test
19
+
20
+ **AFTER** making changes, verify your work:
21
+ - For type/import changes: run `make lint`
22
+ - For logic/behavior changes: run `make test` or `make test-all` or `make dashboard-test` or `make dashboard-test-e2e`
23
+
24
+ - The project was renamed from huldra to furu. If I ever refer to huldra, assume I mean furu.
25
+
26
+ ---
27
+
28
+ ## Project Structure
29
+
30
+ ```
31
+ src/furu/ # Main package (src layout - import as `furu`)
32
+ core/ # Furu and FuruList classes
33
+ storage/ # StateManager, MetadataManager
34
+ serialization/ # FuruSerializer (+ pydantic support)
35
+ runtime/ # Scoped logging, .env loading, tracebacks
36
+ adapters/ # Integrations (SubmititAdapter)
37
+ dashboard/ # FastAPI dashboard (optional)
38
+ tests/ # pytest tests
39
+ examples/ # Runnable examples
40
+ dashboard-frontend/ # React/TypeScript dashboard frontend
41
+ e2e/ # Playwright end-to-end tests
42
+ ```
43
+
44
+ ---
45
+
46
+ ## Frontend Notes
47
+
48
+ - Use shadcn/ui for frontend components.
49
+
50
+ ## Build, Test, and Lint Commands
51
+
52
+ This project uses `uv` for dependency management.
53
+
54
+ ### Core Commands (use these)
55
+
56
+ | Command | Description |
57
+ |---------|-------------|
58
+ | `make lint` | Run ruff check + ty type checker |
59
+ | `make test` | Run pytest on `tests/` |
60
+ | `make check` | Run lint + test |
61
+ | `make build` | Build wheel/sdist (runs tests first) |
62
+ | `make clean` | Remove caches and build artifacts |
63
+
64
+ ### Running a Single Test
65
+
66
+ ```bash
67
+ # Run a specific test file
68
+ uv run pytest tests/test_furu_core.py -v
69
+
70
+ # Run a specific test function
71
+ uv run pytest tests/test_furu_core.py::test_exists_reflects_success_state -v
72
+
73
+ # Run tests matching a pattern
74
+ uv run pytest -k "test_load" -v
75
+ ```
76
+
77
+ ### Dashboard Commands
78
+
79
+ | Command | Description |
80
+ |---------|-------------|
81
+ | `make dashboard-dev` | Start dev servers (backend + frontend) |
82
+ | `make dashboard-test` | Run dashboard backend tests |
83
+ | `make dashboard-test-e2e` | Run Playwright e2e tests |
84
+ | `make dashboard-test-all` | Run all dashboard tests |
85
+
86
+ ### Frontend Commands
87
+
88
+ | Command | Description |
89
+ |---------|-------------|
90
+ | `make frontend-lint` | Run frontend TypeScript type checker |
91
+ | `make frontend-test` | Run frontend unit tests |
92
+ | `make frontend-build` | Build frontend for production |
93
+ | `make frontend-generate` | Generate OpenAPI spec and TypeScript client |
94
+
95
+ ---
96
+
97
+ ## Code Style
98
+
99
+ ### Imports
100
+
101
+ Order imports as: stdlib, third-party, local. Use absolute imports for cross-module references.
102
+
103
+ ```python
104
+ import contextlib
105
+ from pathlib import Path
106
+ import chz
107
+ from ..config import FURU_CONFIG
108
+ ```
109
+
110
+ ### Type Annotations
111
+
112
+ - **Required** on all public APIs (functions, methods, class attributes)
113
+ - Use modern syntax: `X | None` not `Optional[X]`
114
+ - Use concrete types, not `Any`
115
+ - Use generics where reasonable: `class Furu[T](ABC):`
116
+
117
+ ```python
118
+ # Good - specific types
119
+ def process(data: dict[str, str]) -> list[int] | None:
120
+ ...
121
+
122
+ # Good - use Pydantic models or dataclasses for structured data
123
+ class UserConfig(BaseModel):
124
+ name: str
125
+ settings: dict[str, str]
126
+
127
+ def load_config(path: Path) -> UserConfig:
128
+ ...
129
+
130
+ # Bad - DO NOT USE
131
+ def process(data: Dict[str, Any]) -> Optional[List[int]]:
132
+ ...
133
+
134
+ # Bad - DO NOT USE untyped dicts
135
+ def load_config(path: Path) -> dict[str, Any]: # NO - use a model
136
+ ...
137
+ ```
138
+
139
+ ### Naming Conventions
140
+
141
+ - `snake_case` for functions, variables, module names
142
+ - `PascalCase` for classes
143
+ - `UPPER_SNAKE_CASE` for constants
144
+ - Private/internal names prefixed with `_` (e.g., `_FuruState`, `_iso_now`)
145
+
146
+ ### Error Handling
147
+
148
+ **Prefer happy path with early errors over defensive checks.** Don't wrap code in if-statements to handle error cases - let it crash or raise explicitly.
149
+
150
+ ```python
151
+ # Good - assume happy path, crash if invariant violated
152
+ data = json.loads(path.read_text())
153
+
154
+ # Good - explicit early error instead of nested ifs
155
+ if not condition:
156
+ raise ValueError("condition must be true")
157
+ # proceed with happy path...
158
+
159
+ # Bad - defensive if-checks for non-happy paths
160
+ if path.exists():
161
+ data = json.loads(path.read_text())
162
+ else:
163
+ data = {} # NO - hides bugs, use happy path
164
+
165
+ # Bad - swallowing errors
166
+ try:
167
+ data = json.loads(path.read_text())
168
+ except Exception:
169
+ data = {} # NO - this hides bugs
170
+ ```
171
+
172
+ **Only use try/except for:**
173
+ 1. Resource cleanup (use `contextlib.suppress` for ignoring cleanup errors)
174
+ 2. Converting exceptions to domain-specific errors
175
+ 3. Explicit user-facing error messages
176
+
177
+ ### Formatting
178
+
179
+ - 4-space indentation
180
+ - Line length: follow ruff defaults
181
+ - Use trailing commas in multi-line structures
182
+ - Prefer small, focused functions over large ones
183
+
184
+ ---
185
+
186
+ ## Testing Guidelines
187
+
188
+ - All tests in `tests/` directory using pytest
189
+ - Use `tmp_path` fixture for temporary directories
190
+ - Use `furu_tmp_root` fixture (from `conftest.py`) for isolated Furu config
191
+ - Keep tests deterministic - no writing to project root
192
+ - Test functions named `test_<description>`
193
+
194
+ ```python
195
+ def test_exists_reflects_success_state(furu_tmp_root) -> None:
196
+ obj = Dummy()
197
+ assert obj.exists() is False
198
+ obj.load_or_create()
199
+ assert obj.exists() is True
200
+ ```
201
+
202
+ ### Test Coverage Requirements
203
+
204
+ **ALWAYS write extensive tests when adding new features.** Tests should cover:
205
+
206
+ 1. **Happy path** - The feature works as expected with valid inputs
207
+ 2. **Edge cases** - Empty inputs, boundary values, None/null values
208
+ 3. **Filter combinations** - When adding filters, test each filter individually AND in combination
209
+ 4. **Error cases** - Invalid inputs, missing data, malformed requests
210
+ 5. **Integration** - Test the full stack (API endpoints, not just internal functions)
211
+
212
+ For dashboard features specifically:
213
+ - Add tests in `tests/dashboard/test_scanner.py` for scanner/filtering logic
214
+ - Add tests in `tests/dashboard/test_api.py` for API endpoint behavior
215
+ - Update `dashboard-frontend/src/api.test.ts` for frontend schema validation
216
+
217
+ Example test structure for a new filter:
218
+ ```python
219
+ def test_filter_by_new_field(furu_tmp_root) -> None:
220
+ """Test filtering by the new field."""
221
+ # Setup: create experiments with different field values
222
+ # Test: filter returns only matching experiments
223
+ # Verify: correct count and correct experiments returned
224
+
225
+ def test_filter_by_new_field_no_match(furu_tmp_root) -> None:
226
+ """Test filter returns empty when no experiments match."""
227
+
228
+ def test_filter_by_new_field_combined(furu_tmp_root) -> None:
229
+ """Test new filter works in combination with existing filters."""
230
+ ```
231
+
232
+ ### Dashboard Test Performance
233
+
234
+ **Use module-scoped fixtures for read-only tests.** Creating experiments is slow, so:
235
+
236
+ 1. **Prefer `populated_furu_root`** (module-scoped) over `temp_furu_root` (function-scoped)
237
+ 2. **Extend `_create_populated_experiments()`** in `conftest.py` when you need new test data
238
+ 3. **Only use `temp_furu_root`** when tests must mutate state or need isolated data
239
+
240
+ ```python
241
+ # Good - uses shared fixture, fast
242
+ def test_filter_by_backend(client: TestClient, populated_furu_root: Path) -> None:
243
+ response = client.get("/api/experiments?backend=local")
244
+ assert response.json()["total"] == 3 # Uses pre-created data
245
+
246
+ # Slow - creates experiments per test (avoid unless necessary)
247
+ def test_something(client: TestClient, temp_furu_root: Path) -> None:
248
+ create_experiment_from_furu(...) # Slow!
249
+ ```
250
+
251
+ ---
252
+
253
+ ## Commit Guidelines
254
+
255
+ - Short, imperative subjects (often lowercase)
256
+ - Examples: `fix typing`, `add raw data path to furu config`
257
+ - Keep commits scoped; separate refactors from behavior changes
258
+
259
+ ---
260
+
261
+ ## Environment & Configuration
262
+
263
+ - Local config from `.env` (gitignored); don't commit secrets
264
+ - Storage defaults to `./data-furu/`; override with `FURU_PATH`
265
+ - Python version: >=3.12
@@ -0,0 +1,5 @@
1
+ # Changelog
2
+
3
+ ## v0.0.1
4
+ - Rename the project to furu and reset versioning to the v0.0.x alpha series.
5
+ - Update API surface, storage metadata, and documentation to match furu naming.