keyshield 2.0.0__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 (93) hide show
  1. keyshield-2.0.0/.github/ISSUE_TEMPLATE/bug_report.md +38 -0
  2. keyshield-2.0.0/.github/ISSUE_TEMPLATE/feature_request.md +20 -0
  3. keyshield-2.0.0/.github/workflows/develop.yml +107 -0
  4. keyshield-2.0.0/.github/workflows/release.yml +78 -0
  5. keyshield-2.0.0/.gitignore +253 -0
  6. keyshield-2.0.0/.pre-commit-config.yaml +30 -0
  7. keyshield-2.0.0/.python-version +1 -0
  8. keyshield-2.0.0/AGENTS.md +145 -0
  9. keyshield-2.0.0/CHANGELOG.md +320 -0
  10. keyshield-2.0.0/CODE_OF_CONDUCT.md +32 -0
  11. keyshield-2.0.0/CONTRIBUTING.md +105 -0
  12. keyshield-2.0.0/LICENSE +21 -0
  13. keyshield-2.0.0/Makefile +8 -0
  14. keyshield-2.0.0/PKG-INFO +460 -0
  15. keyshield-2.0.0/README.md +395 -0
  16. keyshield-2.0.0/SECURITY.md +16 -0
  17. keyshield-2.0.0/docs/index.md +196 -0
  18. keyshield-2.0.0/docs/mermaid-cache-api-key-system.png +0 -0
  19. keyshield-2.0.0/docs/quickstart.md +35 -0
  20. keyshield-2.0.0/docs/schema.mermaid +103 -0
  21. keyshield-2.0.0/docs/schema.svg +3 -0
  22. keyshield-2.0.0/docs/usage/cache.md +65 -0
  23. keyshield-2.0.0/docs/usage/database.md +22 -0
  24. keyshield-2.0.0/docs/usage/django.md +140 -0
  25. keyshield-2.0.0/docs/usage/dotenv.md +21 -0
  26. keyshield-2.0.0/docs/usage/fastapi.md +38 -0
  27. keyshield-2.0.0/docs/usage/litestar.md +134 -0
  28. keyshield-2.0.0/docs/usage/quart.md +126 -0
  29. keyshield-2.0.0/docs/usage/scopes.md +27 -0
  30. keyshield-2.0.0/docs/usage/typer.md +36 -0
  31. keyshield-2.0.0/examples/example_cached.py +58 -0
  32. keyshield-2.0.0/examples/example_cli.py +61 -0
  33. keyshield-2.0.0/examples/example_fastapi.py +81 -0
  34. keyshield-2.0.0/examples/example_fastapi_scopes.py +107 -0
  35. keyshield-2.0.0/examples/example_fastapi_v2.py +87 -0
  36. keyshield-2.0.0/examples/example_inmemory.py +30 -0
  37. keyshield-2.0.0/examples/example_inmemory_env.py +39 -0
  38. keyshield-2.0.0/examples/example_scopes.py +56 -0
  39. keyshield-2.0.0/examples/example_sql.py +46 -0
  40. keyshield-2.0.0/mkdocs.yml +80 -0
  41. keyshield-2.0.0/pyproject.toml +207 -0
  42. keyshield-2.0.0/ruff.toml +77 -0
  43. keyshield-2.0.0/src/keyshield/__init__.py +11 -0
  44. keyshield-2.0.0/src/keyshield/__main__.py +69 -0
  45. keyshield-2.0.0/src/keyshield/_schemas.py +201 -0
  46. keyshield-2.0.0/src/keyshield/_types.py +21 -0
  47. keyshield-2.0.0/src/keyshield/api.py +491 -0
  48. keyshield-2.0.0/src/keyshield/cli.py +420 -0
  49. keyshield-2.0.0/src/keyshield/django/__init__.py +13 -0
  50. keyshield-2.0.0/src/keyshield/django/apps.py +21 -0
  51. keyshield-2.0.0/src/keyshield/django/decorators.py +85 -0
  52. keyshield-2.0.0/src/keyshield/django/models.py +66 -0
  53. keyshield-2.0.0/src/keyshield/django/repository.py +231 -0
  54. keyshield-2.0.0/src/keyshield/django/urls.py +60 -0
  55. keyshield-2.0.0/src/keyshield/django/views.py +312 -0
  56. keyshield-2.0.0/src/keyshield/domain/__init__.py +0 -0
  57. keyshield-2.0.0/src/keyshield/domain/base.py +120 -0
  58. keyshield-2.0.0/src/keyshield/domain/entities.py +190 -0
  59. keyshield-2.0.0/src/keyshield/domain/errors.py +58 -0
  60. keyshield-2.0.0/src/keyshield/hasher/__init__.py +3 -0
  61. keyshield-2.0.0/src/keyshield/hasher/argon2.py +47 -0
  62. keyshield-2.0.0/src/keyshield/hasher/base.py +134 -0
  63. keyshield-2.0.0/src/keyshield/hasher/bcrypt.py +41 -0
  64. keyshield-2.0.0/src/keyshield/litestar_api.py +370 -0
  65. keyshield-2.0.0/src/keyshield/py.typed +0 -0
  66. keyshield-2.0.0/src/keyshield/quart_api.py +378 -0
  67. keyshield-2.0.0/src/keyshield/repositories/__init__.py +0 -0
  68. keyshield-2.0.0/src/keyshield/repositories/base.py +150 -0
  69. keyshield-2.0.0/src/keyshield/repositories/in_memory.py +142 -0
  70. keyshield-2.0.0/src/keyshield/repositories/sql.py +330 -0
  71. keyshield-2.0.0/src/keyshield/services/__init__.py +0 -0
  72. keyshield-2.0.0/src/keyshield/services/base.py +526 -0
  73. keyshield-2.0.0/src/keyshield/services/cached.py +133 -0
  74. keyshield-2.0.0/src/keyshield/utils.py +25 -0
  75. keyshield-2.0.0/tests/__init__.py +0 -0
  76. keyshield-2.0.0/tests/conftest.py +100 -0
  77. keyshield-2.0.0/tests/integration/__init__.py +0 -0
  78. keyshield-2.0.0/tests/settings_test_django.py +19 -0
  79. keyshield-2.0.0/tests/test_regression.py +156 -0
  80. keyshield-2.0.0/tests/unit/__init__.py +0 -0
  81. keyshield-2.0.0/tests/unit/test_api.py +861 -0
  82. keyshield-2.0.0/tests/unit/test_cached_service.py +314 -0
  83. keyshield-2.0.0/tests/unit/test_cli.py +522 -0
  84. keyshield-2.0.0/tests/unit/test_django_repo.py +355 -0
  85. keyshield-2.0.0/tests/unit/test_django_views.py +550 -0
  86. keyshield-2.0.0/tests/unit/test_entity.py +298 -0
  87. keyshield-2.0.0/tests/unit/test_hasher.py +238 -0
  88. keyshield-2.0.0/tests/unit/test_litestar_api.py +516 -0
  89. keyshield-2.0.0/tests/unit/test_quart_api.py +484 -0
  90. keyshield-2.0.0/tests/unit/test_repo.py +446 -0
  91. keyshield-2.0.0/tests/unit/test_service.py +570 -0
  92. keyshield-2.0.0/tests/unit/test_sql_repo.py +504 -0
  93. keyshield-2.0.0/uv.lock +3285 -0
@@ -0,0 +1,38 @@
1
+ ---
2
+ name: Bug report
3
+ about: Create a report to help us improve
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Describe the bug**
11
+ A clear and concise description of what the bug is.
12
+
13
+ **To Reproduce**
14
+ Steps to reproduce the behavior:
15
+ 1. Go to '...'
16
+ 2. Click on '....'
17
+ 3. Scroll down to '....'
18
+ 4. See error
19
+
20
+ **Expected behavior**
21
+ A clear and concise description of what you expected to happen.
22
+
23
+ **Screenshots**
24
+ If applicable, add screenshots to help explain your problem.
25
+
26
+ **Desktop (please complete the following information):**
27
+ - OS: [e.g. iOS]
28
+ - Browser [e.g. chrome, safari]
29
+ - Version [e.g. 22]
30
+
31
+ **Smartphone (please complete the following information):**
32
+ - Device: [e.g. iPhone6]
33
+ - OS: [e.g. iOS8.1]
34
+ - Browser [e.g. stock browser, safari]
35
+ - Version [e.g. 22]
36
+
37
+ **Additional context**
38
+ Add any other context about the problem here.
@@ -0,0 +1,20 @@
1
+ ---
2
+ name: Feature request
3
+ about: Suggest an idea for this project
4
+ title: ''
5
+ labels: ''
6
+ assignees: ''
7
+
8
+ ---
9
+
10
+ **Is your feature request related to a problem? Please describe.**
11
+ A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12
+
13
+ **Describe the solution you'd like**
14
+ A clear and concise description of what you want to happen.
15
+
16
+ **Describe alternatives you've considered**
17
+ A clear and concise description of any alternative solutions or features you've considered.
18
+
19
+ **Additional context**
20
+ Add any other context or screenshots about the feature request here.
@@ -0,0 +1,107 @@
1
+ name: Development Workflow
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [ development ]
6
+ push:
7
+ branches: [ development ]
8
+ # Allow this workflow to be called by other workflows
9
+ workflow_call:
10
+
11
+ permissions:
12
+ contents: read
13
+ checks: none
14
+ pull-requests: none
15
+
16
+ jobs:
17
+ lint:
18
+ name: Lint / Format / Type-check
19
+ runs-on: ubuntu-latest
20
+
21
+ steps:
22
+ - name: Checkout
23
+ uses: actions/checkout@v4
24
+
25
+ - name: Set up UV
26
+ uses: astral-sh/setup-uv@v6
27
+ with:
28
+ python-version: '3.14'
29
+
30
+ - name: Cache UV
31
+ uses: actions/cache@v4
32
+ with:
33
+ path: ~/.cache/uv
34
+ key: ${{ runner.os }}-uv-3.14-${{ hashFiles('uv.lock') }}
35
+ restore-keys: |
36
+ ${{ runner.os }}-uv-3.14-
37
+
38
+ - name: Sync dev dependencies
39
+ run: uv sync --locked --all-extras --dev --no-progress -q
40
+
41
+ - name: Lint / Format / Type-check
42
+ run: make lint
43
+
44
+ tests:
45
+ name: Run tests (matrix)
46
+ runs-on: ubuntu-latest
47
+ needs: lint
48
+ strategy:
49
+ matrix:
50
+ python-version: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14' ]
51
+ fail-fast: false
52
+
53
+ steps:
54
+ - name: Checkout
55
+ uses: actions/checkout@v4
56
+
57
+ - name: Set up UV
58
+ uses: astral-sh/setup-uv@v6
59
+ with:
60
+ python-version: ${{ matrix.python-version }}
61
+
62
+ - name: Cache UV
63
+ uses: actions/cache@v4
64
+ with:
65
+ path: ~/.cache/uv
66
+ key: ${{ runner.os }}-uv-${{ matrix.python-version }}-${{ hashFiles('uv.lock') }}
67
+ restore-keys: |
68
+ ${{ runner.os }}-uv-${{ matrix.python-version }}-
69
+
70
+ - name: Sync dev dependencies
71
+ run: uv sync --locked --all-extras --dev --no-progress -q
72
+
73
+ - name: Run tests
74
+ run: uv run pytest
75
+
76
+ codecov:
77
+ name: Upload saved coverage to Codecov
78
+ runs-on: ubuntu-latest
79
+ needs: tests
80
+ steps:
81
+ # Codecov needs the full history to map commits correctly
82
+ - name: Checkout
83
+ uses: actions/checkout@v4
84
+
85
+ - name: Set up UV
86
+ uses: astral-sh/setup-uv@v6
87
+ with:
88
+ python-version: ${{ matrix.python-version }}
89
+
90
+ - name: Cache UV
91
+ uses: actions/cache@v4
92
+ with:
93
+ path: ~/.cache/uv
94
+ key: ${{ runner.os }}-uv-3.14-${{ hashFiles('uv.lock') }}
95
+ restore-keys: |
96
+ ${{ runner.os }}-uv-3.14-
97
+
98
+ - name: Sync dev dependencies
99
+ run: uv sync --locked --all-extras --dev --no-progress -q
100
+
101
+ - name: Run tests
102
+ run: uv run pytest
103
+
104
+ - name: Upload coverage to Codecov
105
+ uses: codecov/codecov-action@v4
106
+ env:
107
+ CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
@@ -0,0 +1,78 @@
1
+ name: CI & Publish (PyPI + Release)
2
+
3
+ on:
4
+ pull_request:
5
+ branches: [ main ]
6
+ push:
7
+ branches: [ main ]
8
+ tags:
9
+ - '*'
10
+ permissions:
11
+ contents: write # For creating the release
12
+ id-token: write # Required for PyPI Trusted Publisher
13
+ actions: read
14
+
15
+ jobs:
16
+ dev:
17
+ name: Development Checks
18
+ uses: ./.github/workflows/develop.yml
19
+ secrets: inherit
20
+
21
+ publish:
22
+ needs: dev
23
+ name: Publish to PyPI & Create GitHub Release
24
+ runs-on: ubuntu-latest
25
+
26
+ # Runs only if on a version tag (e.g., refs/tags/1.2.3)
27
+ # Runs only when code is pushed to main (not on PRs)
28
+ if: startsWith(github.ref, 'refs/tags/')
29
+ steps:
30
+ - name: Checkout (full history for changelog/tools)
31
+ uses: actions/checkout@v4
32
+ with:
33
+ fetch-depth: 0
34
+
35
+ - name: Set up UV (publish runtime)
36
+ uses: astral-sh/setup-uv@v6
37
+ with:
38
+ python-version: "3.14"
39
+
40
+ - name: Sync dev dependencies
41
+ run: uv sync --locked --all-extras --dev --no-progress -q
42
+
43
+ - name: Build sdist & wheel with uv
44
+ run: uv build
45
+
46
+ # Publication via OIDC (Trusted Publisher) – no secret required on GitHub side
47
+ - name: Publish to PyPI
48
+ run: uv publish --verbose
49
+
50
+ # GitHub Release with auto notes + attached artifacts (sdist/wheel)
51
+ - name: Create GitHub Release
52
+ uses: softprops/action-gh-release@v2
53
+ with:
54
+ tag_name: ${{ github.ref_name }}
55
+ name: ${{ github.ref_name }}
56
+ generate_release_notes: true
57
+ files: |
58
+ dist/*.whl
59
+ dist/*.tar.gz
60
+
61
+ docs:
62
+ name: Deploy MkDocs
63
+ runs-on: ubuntu-latest
64
+ needs: publish
65
+ steps:
66
+ - name: Checkout
67
+ uses: actions/checkout@v4
68
+
69
+ - name: Set up UV
70
+ uses: astral-sh/setup-uv@v6
71
+ with:
72
+ python-version: '3.14'
73
+
74
+ - name: Install dependencies
75
+ run: uv sync --locked --all-extras --dev --no-progress -q
76
+
77
+ - name: Deploy MkDocs
78
+ run: uv run mkdocs gh-deploy --force
@@ -0,0 +1,253 @@
1
+ ### PyCharm+all template
2
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
3
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
4
+
5
+ # User-specific stuff
6
+ .idea/**/workspace.xml
7
+ .idea/**/tasks.xml
8
+ .idea/**/usage.statistics.xml
9
+ .idea/**/dictionaries
10
+ .idea/**/shelf
11
+
12
+ # AWS User-specific
13
+ .idea/**/aws.xml
14
+
15
+ # Generated files
16
+ .idea/**/contentModel.xml
17
+
18
+ # Sensitive or high-churn files
19
+ .idea/**/dataSources/
20
+ .idea/**/dataSources.ids
21
+ .idea/**/dataSources.local.xml
22
+ .idea/**/sqlDataSources.xml
23
+ .idea/**/dynamic.xml
24
+ .idea/**/uiDesigner.xml
25
+ .idea/**/dbnavigator.xml
26
+
27
+ # Gradle
28
+ .idea/**/gradle.xml
29
+ .idea/**/libraries
30
+
31
+ # Gradle and Maven with auto-import
32
+ # When using Gradle or Maven with auto-import, you should exclude module files,
33
+ # since they will be recreated, and may cause churn. Uncomment if using
34
+ # auto-import.
35
+ # .idea/artifacts
36
+ # .idea/compiler.xml
37
+ # .idea/jarRepositories.xml
38
+ # .idea/modules.xml
39
+ # .idea/*.iml
40
+ # .idea/modules
41
+ # *.iml
42
+ # *.ipr
43
+
44
+ # CMake
45
+ cmake-build-*/
46
+
47
+ # Mongo Explorer plugin
48
+ .idea/**/mongoSettings.xml
49
+
50
+ # File-based project format
51
+ *.iws
52
+
53
+ # IntelliJ
54
+ out/
55
+
56
+ # mpeltonen/sbt-idea plugin
57
+ .idea_modules/
58
+
59
+ # JIRA plugin
60
+ atlassian-ide-plugin.xml
61
+
62
+ # Cursive Clojure plugin
63
+ .idea/replstate.xml
64
+
65
+ # SonarLint plugin
66
+ .idea/sonarlint/
67
+
68
+ # Crashlytics plugin (for Android Studio and IntelliJ)
69
+ com_crashlytics_export_strings.xml
70
+ crashlytics.properties
71
+ crashlytics-build.properties
72
+ fabric.properties
73
+
74
+ # Editor-based Rest Client
75
+ .idea/httpRequests
76
+
77
+ # Android studio 3.1+ serialized cache file
78
+ .idea/caches/build_file_checksums.ser
79
+
80
+ ### Python template
81
+ # Byte-compiled / optimized / DLL files
82
+ __pycache__/
83
+ *.py[cod]
84
+ *$py.class
85
+
86
+ # C extensions
87
+ *.so
88
+
89
+ # Distribution / packaging
90
+ .Python
91
+ build/
92
+ develop-eggs/
93
+ dist/
94
+ downloads/
95
+ eggs/
96
+ .eggs/
97
+ lib/
98
+ lib64/
99
+ parts/
100
+ sdist/
101
+ var/
102
+ wheels/
103
+ share/python-wheels/
104
+ *.egg-info/
105
+ .installed.cfg
106
+ *.egg
107
+ MANIFEST
108
+
109
+ # PyInstaller
110
+ # Usually these files are written by a python script from a template
111
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
112
+ *.manifest
113
+ *.spec
114
+
115
+ # Installer logs
116
+ pip-log.txt
117
+ pip-delete-this-directory.txt
118
+
119
+ # Unit test / coverage reports
120
+ htmlcov/
121
+ .tox/
122
+ .nox/
123
+ .coverage
124
+ .coverage.*
125
+ .cache
126
+ nosetests.xml
127
+ coverage.xml
128
+ junit.xml
129
+ *.cover
130
+ *.py,cover
131
+ .hypothesis/
132
+ .pytest_cache/
133
+ cover/
134
+
135
+ # Translations
136
+ *.mo
137
+ *.pot
138
+
139
+ # Django stuff:
140
+ *.log
141
+ local_settings.py
142
+ db.sqlite3
143
+ db.sqlite3-journal
144
+
145
+ # Flask stuff:
146
+ instance/
147
+ .webassets-cache
148
+
149
+ # Scrapy stuff:
150
+ .scrapy
151
+
152
+ # Sphinx documentation
153
+ docs/_build/
154
+
155
+ # PyBuilder
156
+ .pybuilder/
157
+ target/
158
+
159
+ # Jupyter Notebook
160
+ .ipynb_checkpoints
161
+
162
+ # IPython
163
+ profile_default/
164
+ ipython_config.py
165
+
166
+ # pyenv
167
+ # For a library or package, you might want to ignore these files since the code is
168
+ # intended to run in multiple environments; otherwise, check them in:
169
+ # .python-version
170
+
171
+ # pipenv
172
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
173
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
174
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
175
+ # install all needed dependencies.
176
+ #Pipfile.lock
177
+
178
+ # poetry
179
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
180
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
181
+ # commonly ignored for libraries.
182
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
183
+ #poetry.lock
184
+
185
+ # pdm
186
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
187
+ #pdm.lock
188
+ # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
189
+ # in version control.
190
+ # https://pdm.fming.dev/latest/usage/project/#working-with-version-control
191
+ .pdm.toml
192
+ .pdm-python
193
+ .pdm-build/
194
+
195
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
196
+ __pypackages__/
197
+
198
+ # Celery stuff
199
+ celerybeat-schedule
200
+ celerybeat.pid
201
+
202
+ # SageMath parsed files
203
+ *.sage.py
204
+
205
+ # Environments
206
+ .env
207
+ .venv
208
+ env/
209
+ venv/
210
+ ENV/
211
+ env.bak/
212
+ venv.bak/
213
+
214
+ # Spyder project settings
215
+ .spyderproject
216
+ .spyproject
217
+
218
+ # Rope project settings
219
+ .ropeproject
220
+
221
+ # mkdocs documentation
222
+ /site
223
+
224
+ # mypy
225
+ .mypy_cache/
226
+ .dmypy.json
227
+ dmypy.json
228
+
229
+ # Pyre type checker
230
+ .pyre/
231
+
232
+ # pytype static type analyzer
233
+ .pytype/
234
+
235
+ # Cython debug symbols
236
+ cython_debug/
237
+
238
+ # PyCharm
239
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
240
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
241
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
242
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
243
+ .idea/
244
+
245
+ # Examples generated database files
246
+ examples/*.sqlite3
247
+
248
+ # Keep personal TODO list file
249
+ TODO.md
250
+ todo.md
251
+
252
+ # Keep AI assistant conversation history
253
+ CLAUDE.md
@@ -0,0 +1,30 @@
1
+ repos:
2
+ - repo: https://github.com/commitizen-tools/commitizen
3
+ rev: v4.8.3
4
+ hooks:
5
+ # Check the commit message when committing
6
+ - id: commitizen
7
+ stages: [commit-msg]
8
+
9
+ # Check all commits before pushing (optional but recommended)
10
+ - id: commitizen-branch
11
+ stages: [pre-push]
12
+
13
+ # Local hooks for linting and testing
14
+ - repo: local
15
+ hooks:
16
+ # Lint
17
+ - id: uv-lint
18
+ name: "Run linters and formatters"
19
+ entry: uv run lint
20
+ language: system # we leave the binary as is
21
+ pass_filenames: false # « uv run lint » don't need filenames
22
+ stages: [pre-commit]
23
+
24
+ # Tests
25
+ - id: uv-test
26
+ name: "Run tests"
27
+ entry: uv run test
28
+ language: system
29
+ pass_filenames: false
30
+ stages: [pre-commit]
@@ -0,0 +1 @@
1
+ 3.14
@@ -0,0 +1,145 @@
1
+ # AGENTS.md
2
+
3
+ This file guides automated coding agents working in this repository.
4
+ Follow existing patterns and run the same tooling as humans.
5
+
6
+ ## Project overview
7
+
8
+ - Language: Python 3.9+
9
+ - Package: `keyshield` (src layout)
10
+ - Async-first services and repositories
11
+ - Optional extras for FastAPI, SQLAlchemy, Argon2, bcrypt, aiocache
12
+
13
+ ## Tooling and environment
14
+
15
+ - Dependency manager: `uv`
16
+ - Build backend: `hatchling`
17
+ - Format/lint: Ruff
18
+ - Type checks: Ty and Pyrefly
19
+ - Security lint: Bandit
20
+ - Tests: pytest + pytest-cov + pytest-asyncio
21
+
22
+ ## Build, lint, and test commands
23
+
24
+ ### Install (dev)
25
+
26
+ - `uv sync --extra all --group dev`
27
+ - `source .venv/bin/activate` (or Windows `.venv\Scripts\Activate.ps1`)
28
+
29
+ ### Lint and format
30
+
31
+ - `make lint`
32
+ - Runs: `ruff format`, `ruff check --fix`, `ty check`, `pyrefly check`, `bandit`
33
+ - Direct commands:
34
+ - `uv run ruff format .`
35
+ - `uv run ruff check --fix .`
36
+ - `uv run ty check .`
37
+ - `uv run pyrefly check .`
38
+ - `uv run bandit -c pyproject.toml -r src examples -q`
39
+
40
+ ### Tests
41
+
42
+ - Full test suite: `uv run pytest`
43
+ - Single test file: `uv run pytest tests/unit/test_service.py`
44
+ - Single test by node id:
45
+ - `uv run pytest tests/unit/test_service.py::test_create_api_key`
46
+ - Single test by keyword:
47
+ - `uv run pytest -k "create_api_key"`
48
+ - Run a test class: `uv run pytest tests/unit/test_service.py::TestApiKeyService`
49
+
50
+ ### Coverage output
51
+
52
+ Pytest defaults include coverage reports (XML/HTML/terminal) via `pyproject.toml`.
53
+ Artifacts: `coverage.xml`, `htmlcov/`, `junit.xml`.
54
+
55
+ ## Code style and conventions
56
+
57
+ ### Formatting
58
+
59
+ - Ruff handles formatting; follow it instead of manual styling.
60
+ - Line length: 120 characters.
61
+ - Indentation: 4 spaces.
62
+ - Quotes: double quotes.
63
+
64
+ ### Imports
65
+
66
+ - Standard library imports first, then third-party, then local.
67
+ - Prefer explicit imports over wildcard.
68
+ - Keep module-level imports; avoid local imports unless required to break cycles.
69
+ - In optional dependency modules, guard imports with `try/except ModuleNotFoundError`.
70
+
71
+ ### Typing
72
+
73
+ - Use Python 3.9+ type hints everywhere.
74
+ - Prefer `Optional[T]` and `list[T]`/`dict[K, V]` style.
75
+ - Return concrete types (`ApiKey`, `list[ApiKey]`, etc.) not `Any`.
76
+ - Use `Annotated` for FastAPI params when needed.
77
+
78
+ ### Naming
79
+
80
+ - Classes: `CamelCase` (e.g., `ApiKeyService`).
81
+ - Functions/vars: `snake_case`.
82
+ - Constants: `UPPER_SNAKE_CASE`.
83
+ - Private helpers: prefix `_` (e.g., `_verify_entity`).
84
+ - API key fields follow existing names (`key_id`, `key_secret`, `key_hash`).
85
+
86
+ ### Dataclasses and models
87
+
88
+ - Use `@dataclass` for small data carriers (e.g., parsed key results).
89
+ - Use Pydantic models for API request/response schemas.
90
+
91
+ ### Error handling
92
+
93
+ - Domain exceptions live in `keyshield.domain.errors`.
94
+ - Raise domain errors in services and repositories, translate to HTTP exceptions in API layer.
95
+ - Preserve original errors with `raise ... from exc`.
96
+ - Use specific exceptions (`KeyNotFound`, `InvalidKey`, `ConfigurationError`) rather than `ValueError` unless appropriate.
97
+ - Validate inputs early; prefer guard clauses.
98
+
99
+ ### Async and I/O
100
+
101
+ - Services and repositories are async; keep APIs async throughout.
102
+ - Use `await` for repo calls and when composing service logic.
103
+ - Avoid blocking I/O in async paths.
104
+
105
+ ### Security-sensitive behavior
106
+
107
+ - Never log or return plaintext API keys after creation.
108
+ - Enforce API key parsing/validation via `ParsedApiKey` and `_get_parts` patterns.
109
+ - Use timing jitter on auth failures (`verify_key` behavior).
110
+ - Treat secret pepper as external configuration; do not hardcode.
111
+
112
+ ### API layer
113
+
114
+ - Use FastAPI dependency injection with `Depends`.
115
+ - Map domain errors to `HTTPException` with correct status codes.
116
+ - API responses use Pydantic models; keep output schemas stable.
117
+
118
+ ### Tests
119
+
120
+ - Tests live in `tests/` with unit and integration subpackages.
121
+ - Prefer clear arrange/act/assert structure.
122
+ - Use fixtures from `tests/conftest.py` where available.
123
+
124
+ ## Repository layout
125
+
126
+ - `src/keyshield/` core library
127
+ - `tests/` unit/integration tests
128
+ - `examples/` example applications (used in docs)
129
+
130
+ ## Repo-specific notes
131
+
132
+ - `make lint` is the canonical lint entrypoint.
133
+ - The project warns when the default pepper is used; tests expect that behavior.
134
+ - Examples are used in docs; keep them runnable.
135
+
136
+ ## Cursor and Copilot rules
137
+
138
+ - No `.cursor/rules/`, `.cursorrules`, or `.github/copilot-instructions.md` found in this repo.
139
+
140
+ ## When editing
141
+
142
+ - Preserve public API behavior and exception types.
143
+ - Keep docstrings consistent with existing style.
144
+ - Match current error messages when possible (tests may assert them).
145
+ - Avoid introducing extra dependencies without updating optional extras.