senzu 0.2.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.
@@ -0,0 +1,17 @@
1
+ name: CI
2
+
3
+ on:
4
+ pull_request:
5
+ push:
6
+ branches: [main]
7
+
8
+ jobs:
9
+ test:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ - uses: actions/setup-python@v5
14
+ with:
15
+ python-version: "3.12"
16
+ - run: pip install -e ".[dev]"
17
+ - run: pytest
@@ -0,0 +1,40 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ release:
5
+ types: [published]
6
+ workflow_dispatch:
7
+
8
+ permissions:
9
+ id-token: write
10
+
11
+ jobs:
12
+ build:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+
17
+ - uses: actions/setup-python@v5
18
+ with:
19
+ python-version: "3.12"
20
+
21
+ - name: Build package
22
+ run: |
23
+ pip install hatch
24
+ hatch build
25
+
26
+ - uses: actions/upload-artifact@v4
27
+ with:
28
+ name: dist
29
+ path: dist/
30
+
31
+ publish:
32
+ needs: build
33
+ runs-on: ubuntu-latest
34
+ steps:
35
+ - uses: actions/download-artifact@v4
36
+ with:
37
+ name: dist
38
+ path: dist/
39
+
40
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,17 @@
1
+ name: Release Please
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+
7
+ permissions:
8
+ contents: write
9
+ pull-requests: write
10
+
11
+ jobs:
12
+ release-please:
13
+ runs-on: ubuntu-latest
14
+ steps:
15
+ - uses: googleapis/release-please-action@v4
16
+ with:
17
+ release-type: python
senzu-0.2.0/.gitignore ADDED
@@ -0,0 +1,210 @@
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
+ lib/
18
+ lib64/
19
+ parts/
20
+ sdist/
21
+ var/
22
+ wheels/
23
+ share/python-wheels/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+ MANIFEST
28
+
29
+ # PyInstaller
30
+ # Usually these files are written by a python script from a template
31
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
32
+ *.manifest
33
+ *.spec
34
+
35
+ # Installer logs
36
+ pip-log.txt
37
+ pip-delete-this-directory.txt
38
+
39
+ # Unit test / coverage reports
40
+ htmlcov/
41
+ .tox/
42
+ .nox/
43
+ .coverage
44
+ .coverage.*
45
+ .cache
46
+ nosetests.xml
47
+ coverage.xml
48
+ *.cover
49
+ *.py.cover
50
+ .hypothesis/
51
+ .pytest_cache/
52
+ cover/
53
+
54
+ # Translations
55
+ *.mo
56
+ *.pot
57
+
58
+ # Django stuff:
59
+ *.log
60
+ local_settings.py
61
+ db.sqlite3
62
+ db.sqlite3-journal
63
+
64
+ # Flask stuff:
65
+ instance/
66
+ .webassets-cache
67
+
68
+ # Scrapy stuff:
69
+ .scrapy
70
+
71
+ # Sphinx documentation
72
+ docs/_build/
73
+
74
+ # PyBuilder
75
+ .pybuilder/
76
+ target/
77
+
78
+ # Jupyter Notebook
79
+ .ipynb_checkpoints
80
+
81
+ # IPython
82
+ profile_default/
83
+ ipython_config.py
84
+
85
+ # pyenv
86
+ # For a library or package, you might want to ignore these files since the code is
87
+ # intended to run in multiple environments; otherwise, check them in:
88
+ # .python-version
89
+
90
+ # pipenv
91
+ # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
92
+ # However, in case of collaboration, if having platform-specific dependencies or dependencies
93
+ # having no cross-platform support, pipenv may install dependencies that don't work, or not
94
+ # install all needed dependencies.
95
+ #Pipfile.lock
96
+
97
+ # UV
98
+ # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
100
+ # commonly ignored for libraries.
101
+ #uv.lock
102
+
103
+ # poetry
104
+ # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
105
+ # This is especially recommended for binary packages to ensure reproducibility, and is more
106
+ # commonly ignored for libraries.
107
+ # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
108
+ #poetry.lock
109
+ #poetry.toml
110
+
111
+ # pdm
112
+ # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
113
+ # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
114
+ # https://pdm-project.org/en/latest/usage/project/#working-with-version-control
115
+ #pdm.lock
116
+ #pdm.toml
117
+ .pdm-python
118
+ .pdm-build/
119
+
120
+ # pixi
121
+ # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
122
+ #pixi.lock
123
+ # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
124
+ # in the .venv directory. It is recommended not to include this directory in version control.
125
+ .pixi
126
+
127
+ # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
128
+ __pypackages__/
129
+
130
+ # Celery stuff
131
+ celerybeat-schedule
132
+ celerybeat.pid
133
+
134
+ # SageMath parsed files
135
+ *.sage.py
136
+
137
+ # Environments
138
+ .env
139
+ .envrc
140
+ .venv
141
+ env/
142
+ venv/
143
+ ENV/
144
+ env.bak/
145
+ venv.bak/
146
+
147
+ # Spyder project settings
148
+ .spyderproject
149
+ .spyproject
150
+
151
+ # Rope project settings
152
+ .ropeproject
153
+
154
+ # mkdocs documentation
155
+ /site
156
+
157
+ # mypy
158
+ .mypy_cache/
159
+ .dmypy.json
160
+ dmypy.json
161
+
162
+ # Pyre type checker
163
+ .pyre/
164
+
165
+ # pytype static type analyzer
166
+ .pytype/
167
+
168
+ # Cython debug symbols
169
+ cython_debug/
170
+
171
+ # PyCharm
172
+ # JetBrains specific template is maintained in a separate JetBrains.gitignore that can
173
+ # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
174
+ # and can be added to the global gitignore or merged into this file. For a more nuclear
175
+ # option (not recommended) you can uncomment the following to ignore the entire idea folder.
176
+ #.idea/
177
+
178
+ # Abstra
179
+ # Abstra is an AI-powered process automation framework.
180
+ # Ignore directories containing user credentials, local state, and settings.
181
+ # Learn more at https://abstra.io/docs
182
+ .abstra/
183
+
184
+ # Visual Studio Code
185
+ # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
186
+ # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
187
+ # and can be added to the global gitignore or merged into this file. However, if you prefer,
188
+ # you could uncomment the following to ignore the entire vscode folder
189
+ # .vscode/
190
+
191
+ # Ruff stuff:
192
+ .ruff_cache/
193
+
194
+ # PyPI configuration file
195
+ .pypirc
196
+
197
+ # Cursor
198
+ # Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
199
+ # exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
200
+ # refer to https://docs.cursor.com/context/ignore-files
201
+ .cursorignore
202
+ .cursorindexingignore
203
+
204
+ # Marimo
205
+ marimo/_static/
206
+ marimo/_lsp/
207
+ __marimo__/
208
+
209
+ # Senzu
210
+ .env.*
@@ -0,0 +1,44 @@
1
+ # Changelog
2
+
3
+ ## [0.2.0](https://github.com/philip-730/senzu/compare/v0.1.0...v0.2.0) (2026-03-26)
4
+
5
+
6
+ ### Features
7
+
8
+ * show GCP project name in pull, push, and import output ([6e1047f](https://github.com/philip-730/senzu/commit/6e1047f5eb883b0477e0c399b91780b4c1c86578))
9
+ * show project/secret in tables for diff, push, and import output ([fd08dc3](https://github.com/philip-730/senzu/commit/fd08dc3c98769406ef053d27e940961a569bb3c0))
10
+
11
+
12
+ ### Bug Fixes
13
+
14
+ * distinguish first pull from update, simplify push confirmation ([645b80a](https://github.com/philip-730/senzu/commit/645b80a6bb439cb4f0d6f4734ac844045bac1b33))
15
+ * flag untracked local-only keys in diff output ([9c964b3](https://github.com/philip-730/senzu/commit/9c964b363cb14c203706dc70915440ac5295e834))
16
+ * pull preserves local-only keys, import shows accurate diff, init supports flags ([c48da77](https://github.com/philip-730/senzu/commit/c48da7772211828b30fc78afc70e485ccddc4096))
17
+
18
+
19
+ ### Documentation
20
+
21
+ * update README for pull merge behavior, import diff preview, init flags ([0969cdc](https://github.com/philip-730/senzu/commit/0969cdcc1d24333e25b7028c74d34aefdabe4fdf))
22
+
23
+ ## 0.1.0 (2026-03-24)
24
+
25
+
26
+ ### Features
27
+
28
+ * make google-cloud-sdk optional in devShell ([c2bcf25](https://github.com/philip-730/senzu/commit/c2bcf25754217a34bea14bc4f9dcd67db2183081))
29
+
30
+
31
+ ### Bug Fixes
32
+
33
+ * deduplicate uv2nix in flake inputs ([bb64e2b](https://github.com/philip-730/senzu/commit/bb64e2bc5722c1d9dbd2a2990686188d749c22cd))
34
+ * lowercase merged keys in SecretManagerSettingsSource to fix field required errors ([cb070b0](https://github.com/philip-730/senzu/commit/cb070b0398d1d2166d43e75344fb397fa3bb58cb))
35
+ * rename secrets_settings to file_secret_settings for pydantic-settings >=2.4 ([0c6d2af](https://github.com/philip-730/senzu/commit/0c6d2afa2ae9465d13df39291bc4382f1ea90798))
36
+ * replace custom _DotEnv with DotEnvSettingsSource to fix field required errors ([74c825f](https://github.com/philip-730/senzu/commit/74c825f4074fd80babc3b5dcb171e062962ea251))
37
+ * wire editables into hatchling editable build inputs ([2dd99fa](https://github.com/philip-730/senzu/commit/2dd99fa4950d6151f1ed22815925179a5d1382de))
38
+
39
+
40
+ ### Documentation
41
+
42
+ * add Secret Manager source JSON to nested JSON example ([9fc65bc](https://github.com/philip-730/senzu/commit/9fc65bc14342f94cdb12614ca856dc7f84bdeb81))
43
+ * expand type=raw docs to cover JSON objects, fix nested JSON wording ([aff6417](https://github.com/philip-730/senzu/commit/aff64171a2b78526a0de6136665d3fa00b41aedc))
44
+ * fill in README gaps — status, all-envs behavior, cross-project secrets, nested JSON ([a259bb5](https://github.com/philip-730/senzu/commit/a259bb55bd48d9f96f17c98f10c44f685f476a6d))
senzu-0.2.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 philip-730
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
senzu-0.2.0/PKG-INFO ADDED
@@ -0,0 +1,273 @@
1
+ Metadata-Version: 2.4
2
+ Name: senzu
3
+ Version: 0.2.0
4
+ Summary: Secret env sync for GCP teams
5
+ Author: philip-730
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: dotenv,env,gcp,secret-manager,secrets
9
+ Requires-Python: >=3.10
10
+ Requires-Dist: google-cloud-secret-manager>=2
11
+ Requires-Dist: pydantic-settings>=2
12
+ Requires-Dist: python-dotenv>=1
13
+ Requires-Dist: rich>=13
14
+ Requires-Dist: toml>=0.10
15
+ Requires-Dist: typer>=0.12
16
+ Provides-Extra: dev
17
+ Requires-Dist: pytest-mock>=3; extra == 'dev'
18
+ Requires-Dist: pytest>=8; extra == 'dev'
19
+ Requires-Dist: ruff>=0.4; extra == 'dev'
20
+ Description-Content-Type: text/markdown
21
+
22
+ # senzu
23
+
24
+ Stop manually copy-pasting secrets from GCP Secret Manager into `.env` files like an animal. Senzu syncs secrets between GCP Secret Manager and local `.env` files, tracks where every key came from, and won't let you blow up production by pushing stale local changes over remote ones.
25
+
26
+ It's a CLI + Python library for teams using GCP Secret Manager who need their secrets to actually stay in sync — across multiple environments, multiple secrets, multiple people.
27
+
28
+ ---
29
+
30
+ ## Why it's actually sick
31
+
32
+ Most teams end up with a shared `.env` in a private Slack channel, a Notion doc, or some other nightmare. If you're using Secret Manager, you're at least in the right place — but the workflow is still garbage. You open the GCP console, copy values one by one, paste them into a file you hope you don't accidentally commit.
33
+
34
+ Senzu fixes this:
35
+
36
+ - **`senzu pull`** — fetches all your configured secrets into a local `.env` file in one command. Handles JSON and dotenv formats automatically. Works across multiple secrets per environment. If your local file already has keys that aren't in remote yet, they're preserved — remote wins on conflicts, but local-only keys survive. Use `--overwrite` if you want the remote to fully replace your local file.
37
+
38
+ - **`senzu push`** — pushes local changes back to Secret Manager. But here's the thing: it actually checks if someone else changed the remote since you last pulled. If they did, it blocks you.
39
+ - **`senzu diff`** — see exactly what's different between your local file and what's in Secret Manager, without touching anything. Pipe it into CI, use it in code review, whatever.
40
+
41
+ - **Lock file** — after a pull, Senzu writes `senzu.lock` which tracks which key came from which secret and which project. This is what makes push safe. It knows exactly where to send each key back, even if you're pulling from 5 different secrets into one `.env`.
42
+
43
+ - **`senzu import`** — already have a `.env` file and want to get into Secret Manager without touching the GCP console? `senzu import dev --from .env` creates the secret if it doesn't exist, pushes the keys, and writes `senzu.lock` so you're immediately ready to pull/push. If the secret already has data, it merges — your local keys win. Before confirming, it shows you exactly which keys are new, which are changed, and which are unchanged, so you know what you're actually pushing. If nothing has changed, it exits early.
44
+
45
+ - **Multiple environments** — `dev`, `staging`, `prod`, whatever you want. Each one can have its own GCP project, its own secrets, its own local file. `senzu pull dev` or `senzu pull prod`, no config flags needed.
46
+
47
+ - **`SenzuSettings`** — drop-in Pydantic BaseSettings subclass. Automatically reads the right `.env` file based on your `ENV` var, parses nested JSON objects into proper Python dicts/lists, and falls back to reading directly from Secret Manager in Cloud Run by just setting `SENZU_USE_SECRET_MANAGER=true`.
48
+
49
+ - **`senzu generate`** — auto-generates a typed Pydantic settings class from your actual secrets. You never have to manually write `api_key: str` for every field again.
50
+
51
+ ---
52
+
53
+ ## Install
54
+
55
+ Senzu isn't on PyPI yet. Install directly from GitHub:
56
+
57
+ ```bash
58
+ pip install git+https://github.com/philip-730/senzu
59
+ # or
60
+ uv add git+https://github.com/philip-730/senzu
61
+ ```
62
+
63
+ Pin to a specific commit or tag if you need stability:
64
+
65
+ ```bash
66
+ pip install git+https://github.com/philip-730/senzu@v0.1.0
67
+ ```
68
+
69
+ To add it to `requirements.txt`:
70
+
71
+ ```
72
+ senzu @ git+https://github.com/philip-730/senzu
73
+ # or pinned:
74
+ senzu @ git+https://github.com/philip-730/senzu@v0.1.0
75
+ ```
76
+
77
+ Requires Python 3.10+. You'll need GCP credentials set up — either `gcloud auth application-default login` locally or a service account in prod.
78
+
79
+ ---
80
+
81
+ ## Setup
82
+
83
+ Run the init wizard in your project root:
84
+
85
+ ```bash
86
+ senzu init
87
+
88
+ # or skip the prompts entirely with flags
89
+ senzu init --project my-gcp-project --env dev --file .env.dev --secret app-env
90
+ ```
91
+
92
+ This creates `senzu.toml` and updates `.gitignore` to exclude your `.env.*` files. You don't want those committed. The `--env` flag controls the environment name (defaults to `dev`) — useful if you want to scaffold a non-dev env first.
93
+
94
+ Or write `senzu.toml` yourself:
95
+
96
+ ```toml
97
+ [envs.dev]
98
+ project = "my-gcp-project-dev"
99
+ file = ".env.dev"
100
+ secrets = [
101
+ { secret = "app-env" },
102
+ { secret = "db-creds", format = "json" },
103
+ ]
104
+
105
+ [envs.prod]
106
+ project = "my-gcp-project-prod"
107
+ file = ".env.prod"
108
+ secrets = [
109
+ { secret = "app-env-prod" },
110
+ ]
111
+ ```
112
+
113
+ Each secret in the `secrets` array is fetched and merged into the local file. If you want the entire secret stored as a single env var rather than expanded into individual keys, use `type = "raw"`. `env_var` is required — it sets the env var name the value is written under:
114
+
115
+ ```toml
116
+ secrets = [
117
+ # Scalar value (e.g. a webhook secret string)
118
+ { secret = "stripe-webhook-secret", type = "raw", env_var = "STRIPE_WEBHOOK_SECRET" },
119
+
120
+ # JSON object stored whole — useful when you want one dict, not individual keys
121
+ { secret = "firebase-sdk", type = "raw", env_var = "FIREBASE_CREDS" },
122
+ ]
123
+ ```
124
+
125
+ For the JSON case, Senzu stores it single-quoted in the `.env` file (`FIREBASE_CREDS='{"type":"service_account",...}'`). Declare the field as `dict` in `SenzuSettings` and it's automatically deserialized.
126
+
127
+ ### Cross-project secrets
128
+
129
+ A secret can pull from a different GCP project than the environment default. This is useful for shared infrastructure secrets owned by a central project:
130
+
131
+ ```toml
132
+ [envs.prod]
133
+ project = "my-app-prod"
134
+ file = ".env.prod"
135
+ secrets = [
136
+ { secret = "app-env-prod" },
137
+ { secret = "datadog-api-key", project = "shared-infra" },
138
+ ]
139
+ ```
140
+
141
+ When multiple secrets share a key name, the last secret listed wins. Senzu will emit a warning so you know it happened.
142
+
143
+ ---
144
+
145
+ ## Usage
146
+
147
+ ```bash
148
+ # Bootstrap — import an existing .env into Secret Manager for the first time
149
+ senzu import dev --from .env
150
+ senzu import dev --from .env --secret app-env # skip interactive routing, send all keys to this secret
151
+ senzu import dev --from .env --keys DB_URL,DB_PASSWORD # specific keys only
152
+ senzu import dev --from .env --format json # write as JSON instead of dotenv
153
+
154
+ # Pull secrets to local .env files
155
+ senzu pull # all environments defined in senzu.toml
156
+ senzu pull dev # specific environment only
157
+ senzu pull dev --overwrite # fully replace local file with remote (discards local-only keys)
158
+
159
+ # See what's different between local and remote
160
+ senzu diff # all environments
161
+ senzu diff dev # specific environment only
162
+
163
+ # Push local changes back to Secret Manager
164
+ senzu push # all environments (prompts for confirmation per env)
165
+ senzu push dev # specific environment
166
+ senzu push dev --force # skip confirmation even if remote has unretrieved changes
167
+
168
+ # Show all configured environments, their secrets, GCP projects, and local file status
169
+ senzu status
170
+
171
+ # Generate a typed Pydantic settings class from your secrets
172
+ senzu generate dev --out settings.py
173
+ ```
174
+
175
+ ---
176
+
177
+ ## Using in Python
178
+
179
+ If you have a Python app and want type-safe settings without the manual config:
180
+
181
+ ```python
182
+ from senzu import SenzuSettings
183
+
184
+ class Settings(SenzuSettings):
185
+ database_url: str
186
+ api_key: str
187
+ firebase_creds: dict # see note on nested JSON below
188
+
189
+ settings = Settings()
190
+ ```
191
+
192
+ GCP secrets often store nested JSON objects (service account keys, connection configs, etc.) that can't be written as raw dotenv values. Senzu encodes these as single-quoted strings in the `.env` file:
193
+
194
+ ```json
195
+ // Secret Manager — app-env secret value
196
+ {
197
+ "database_url": "postgres://...",
198
+ "api_key": "sk-...",
199
+ "firebase_creds": { "type": "service_account", "project_id": "my-app" }
200
+ }
201
+ ```
202
+
203
+ ```bash
204
+ # .env.dev — what Senzu writes after senzu pull
205
+ DATABASE_URL=postgres://...
206
+ API_KEY=sk-...
207
+ FIREBASE_CREDS='{"type":"service_account","project_id":"my-app"}'
208
+ ```
209
+
210
+ When you declare the field as `dict` (or `list`) in `SenzuSettings`, Senzu automatically deserializes it — so `settings.firebase_creds` is already a Python dict, not a string.
211
+
212
+ Senzu reads `ENV` or `SENZU_ENV` to figure out which environment you're in, finds the right `.env` file from `senzu.toml`, and loads it. In Cloud Run or any environment where you don't have a file, set `SENZU_USE_SECRET_MANAGER=true` and it reads directly from Secret Manager.
213
+
214
+ ---
215
+
216
+ ## The lock file
217
+
218
+ After `senzu pull`, you'll have a `senzu.lock` file. This is how Senzu knows which of your 40 env vars came from which of your 5 secrets. Don't delete it — push won't work without it. Commit it — it contains no secret values, just routing metadata (which key lives in which secret), and your teammates need it to push without doing a redundant pull first.
219
+
220
+ ---
221
+
222
+ ## Auth
223
+
224
+ Senzu uses the standard GCP auth chain via `google-cloud-secret-manager`. Locally, run:
225
+
226
+ ```bash
227
+ gcloud auth application-default login
228
+ ```
229
+
230
+ In CI/CD or Cloud Run, use a service account with `Secret Manager Secret Accessor` role on the relevant secrets.
231
+
232
+ ---
233
+
234
+ ## Development
235
+
236
+ ### With Nix (recommended)
237
+
238
+ The repo uses [uv2nix](https://github.com/pyproject-nix/uv2nix) to provide a fully reproducible dev environment. Dependencies are pinned in `uv.lock`.
239
+
240
+ ```bash
241
+ nix develop
242
+ ```
243
+
244
+ This drops you into a shell with the right Python version, all deps installed, and senzu itself available as an editable install — changes to the source are reflected immediately without reinstalling. `gcloud` is also available.
245
+
246
+ To run the CLI directly without entering a shell:
247
+
248
+ ```bash
249
+ nix run
250
+ ```
251
+
252
+ ### Without Nix
253
+
254
+ ```bash
255
+ uv sync
256
+ uv run senzu --help
257
+ ```
258
+
259
+ Or with a standard virtualenv:
260
+
261
+ ```bash
262
+ python -m venv .venv
263
+ source .venv/bin/activate
264
+ pip install -e '.[dev]'
265
+ ```
266
+
267
+ ### Running tests
268
+
269
+ ```bash
270
+ pytest
271
+ # or inside nix develop:
272
+ pytest tests/
273
+ ```