pydep-service 0.1.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.
- pydep_service-0.1.0/LICENSE +21 -0
- pydep_service-0.1.0/PKG-INFO +299 -0
- pydep_service-0.1.0/README.md +274 -0
- pydep_service-0.1.0/pyproject.toml +45 -0
- pydep_service-0.1.0/setup.cfg +4 -0
- pydep_service-0.1.0/src/pydep/__init__.py +2 -0
- pydep_service-0.1.0/src/pydep/__main__.py +3 -0
- pydep_service-0.1.0/src/pydep/cli.py +124 -0
- pydep_service-0.1.0/src/pydep/core.py +308 -0
- pydep_service-0.1.0/src/pydep/windows.py +180 -0
- pydep_service-0.1.0/src/pydep_service.egg-info/PKG-INFO +299 -0
- pydep_service-0.1.0/src/pydep_service.egg-info/SOURCES.txt +14 -0
- pydep_service-0.1.0/src/pydep_service.egg-info/dependency_links.txt +1 -0
- pydep_service-0.1.0/src/pydep_service.egg-info/entry_points.txt +2 -0
- pydep_service-0.1.0/src/pydep_service.egg-info/top_level.txt +1 -0
- pydep_service-0.1.0/tests/test_pydep.py +365 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Zubair Jamil
|
|
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.
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pydep-service
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Deploy FastAPI applications as persistent NSSM Windows Services
|
|
5
|
+
Author: Zubair Jamil
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: deployment,fastapi,nssm,service,system-administration,uvicorn,windows
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Intended Audience :: System Administrators
|
|
12
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: System :: Installation/Setup
|
|
20
|
+
Classifier: Topic :: System :: Systems Administration
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# pydep
|
|
27
|
+
|
|
28
|
+
Deploy a Python/FastAPI application from its existing `.venv` as a persistent Windows Service, managed by NSSM. Standard-library-only CLI, Python 3.10 or later.
|
|
29
|
+
|
|
30
|
+
This package includes implementation and automated behavior tests. The tests use
|
|
31
|
+
a service double, so actual SCM/NSSM integration, account permissions, and
|
|
32
|
+
reboot behavior must still be verified on Windows using the procedure below.
|
|
33
|
+
Passing the automated suite is not a claim that those integration checks ran.
|
|
34
|
+
|
|
35
|
+
## 1. Install NSSM
|
|
36
|
+
|
|
37
|
+
1. Download NSSM from its [official site](https://nssm.cc/download). Use a build compatible with your Windows version, with command-line configuration and online log rotation support (2.24 or later).
|
|
38
|
+
2. Extract the appropriate architecture's `nssm.exe` into a permanent, administrator-controlled location, for example `C:\Tools\nssm\nssm.exe`.
|
|
39
|
+
3. Add `C:\Tools\nssm` to your Windows PATH through **System Properties > Environment Variables**, then open a new PowerShell window.
|
|
40
|
+
4. Verify discovery:
|
|
41
|
+
|
|
42
|
+
```powershell
|
|
43
|
+
Get-Command nssm.exe
|
|
44
|
+
nssm.exe version
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Alternatively, supply `--nssm 'C:\Tools\nssm\nssm.exe'` to deploy/start/stop/remove. `list` queries Windows directly and `logs` reads files, so neither needs NSSM on PATH.
|
|
48
|
+
|
|
49
|
+
Keep the NSSM executable at the same location for the lifetime of its services. pydep does not download, redistribute, or automatically upgrade NSSM. See the official [usage guide](https://nssm.cc/usage).
|
|
50
|
+
|
|
51
|
+
## 2. Install pydep
|
|
52
|
+
|
|
53
|
+
The package published on PyPI is named `pydep-service`, while the installed
|
|
54
|
+
command remains `pydep`. The recommended installation uses uv's isolated tool
|
|
55
|
+
environment:
|
|
56
|
+
|
|
57
|
+
```powershell
|
|
58
|
+
uv tool install pydep-service
|
|
59
|
+
pydep --version
|
|
60
|
+
pydep --help
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Run it once without installing it permanently:
|
|
64
|
+
|
|
65
|
+
```powershell
|
|
66
|
+
uvx --from pydep-service pydep --help
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Alternative installation methods are:
|
|
70
|
+
|
|
71
|
+
```powershell
|
|
72
|
+
pipx install pydep-service
|
|
73
|
+
py -m pip install pydep-service
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Install the CLI into a stable, isolated tool environment or Python
|
|
77
|
+
installation, not an application venv you might delete. A deployed application
|
|
78
|
+
continues to use its own `.venv`; pydep invokes that environment's Python
|
|
79
|
+
directly and does not install FastAPI or Uvicorn for the application. Ensure the
|
|
80
|
+
CLI installation's Scripts directory is on PATH. If `pydep` is not found after
|
|
81
|
+
a pip installation, use `py -m pydep` with the same Python that installed it,
|
|
82
|
+
or add that Python installation's Scripts directory to PATH. No runtime
|
|
83
|
+
packages are required by pydep itself.
|
|
84
|
+
|
|
85
|
+
NSSM is a separate prerequisite: install it independently and make `nssm.exe`
|
|
86
|
+
available on PATH (or pass `--nssm` where supported). Open PowerShell **as
|
|
87
|
+
Administrator** for commands that change services: `deploy`, `start`, `stop`,
|
|
88
|
+
and `remove`. The read-only `list` and `logs` commands do not require elevation.
|
|
89
|
+
|
|
90
|
+
Use the same Windows account each time. Elevating with a different account uses
|
|
91
|
+
that account's `%APPDATA%` and therefore a different deployment registry.
|
|
92
|
+
|
|
93
|
+
## 3. Deploy D:\odin
|
|
94
|
+
|
|
95
|
+
For an existing application, keep its source and existing `.venv`. Ensure its Windows interpreter can import FastAPI and Uvicorn:
|
|
96
|
+
|
|
97
|
+
```powershell
|
|
98
|
+
Set-Location D:\odin
|
|
99
|
+
.\.venv\Scripts\python.exe -m pip install fastapi uvicorn
|
|
100
|
+
pydep deploy --alias odin
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
If you are creating a fresh example, first create the venv with `py -m venv .venv`, create `app\main.py`, and put this in it:
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from fastapi import FastAPI
|
|
107
|
+
|
|
108
|
+
app = FastAPI()
|
|
109
|
+
|
|
110
|
+
@app.get("/health")
|
|
111
|
+
def health():
|
|
112
|
+
return {"status": "ok"}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Example output, assuming the first ID is available:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
[OK] Service reconciled: PyDep_A111
|
|
119
|
+
Deployment ID: A111
|
|
120
|
+
Alias: odin
|
|
121
|
+
Module: app.main:app
|
|
122
|
+
Host: 0.0.0.0
|
|
123
|
+
Port: 8000
|
|
124
|
+
Path: D:\odin
|
|
125
|
+
Status: Running (Windows Service state)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Check the actual HTTP endpoint separately:
|
|
129
|
+
|
|
130
|
+
```powershell
|
|
131
|
+
Invoke-RestMethod http://127.0.0.1:8000/health
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`Running` is the observed Windows Service state, not an HTTP health guarantee. pydep waits for three seconds of running state after a start. Application startup failures, slow lifespan hooks, or later failures still require checking application logs and your health endpoint.
|
|
135
|
+
|
|
136
|
+
## Commands
|
|
137
|
+
|
|
138
|
+
| Command | Behavior |
|
|
139
|
+
| --- | --- |
|
|
140
|
+
| `pydep deploy` | Detect and deploy the current directory, or reconcile its existing registration |
|
|
141
|
+
| `pydep deploy --alias odin` | Set or explicitly rename the alias |
|
|
142
|
+
| `pydep deploy --module app.main:app` | Explicit import target, also stored for later deploys |
|
|
143
|
+
| `pydep deploy --host 127.0.0.1 --port 8001` | Set listening address and port |
|
|
144
|
+
| `pydep deploy --restart` | Restart even if settings already match, to reload source changes |
|
|
145
|
+
| `pydep list` | Show actual SCM state and registry operation phase |
|
|
146
|
+
| `pydep start A111` or `pydep start odin` | Start the registered service |
|
|
147
|
+
| `pydep stop A111` or `pydep stop odin` | Stop the registered service |
|
|
148
|
+
| `pydep logs A111` or `pydep logs odin` | Show the last 50 lines of each log |
|
|
149
|
+
| `pydep logs odin --follow --lines 100` | Tail both streams; Ctrl+C exits |
|
|
150
|
+
| `pydep remove A111` or `pydep remove odin` | Remove service and registration, archive logs |
|
|
151
|
+
| `pydep remove odin --purge-logs` | Also delete this deployment's current and rotated logs |
|
|
152
|
+
|
|
153
|
+
Representative command output:
|
|
154
|
+
|
|
155
|
+
```text
|
|
156
|
+
> pydep list
|
|
157
|
+
ID ALIAS STATUS PORT PHASE PATH
|
|
158
|
+
A111 odin Running 8000 ready D:\odin
|
|
159
|
+
A112 rental-api Stopped 8001 ready D:\rental-api
|
|
160
|
+
|
|
161
|
+
> pydep start odin
|
|
162
|
+
[OK] Started A111 (odin)
|
|
163
|
+
|
|
164
|
+
> pydep stop A111
|
|
165
|
+
[OK] Stopped A111 (odin)
|
|
166
|
+
|
|
167
|
+
> pydep logs odin
|
|
168
|
+
--- stdout: C:\Users\you\AppData\Roaming\pydep\logs\A111.out.log ---
|
|
169
|
+
INFO: 127.0.0.1:54321 - "GET /health HTTP/1.1" 200 OK
|
|
170
|
+
--- stderr: C:\Users\you\AppData\Roaming\pydep\logs\A111.err.log ---
|
|
171
|
+
INFO: Application startup complete.
|
|
172
|
+
|
|
173
|
+
> pydep logs A111 --follow
|
|
174
|
+
--- stdout: C:\Users\you\AppData\Roaming\pydep\logs\A111.out.log ---
|
|
175
|
+
(no logs yet)
|
|
176
|
+
--- stderr: C:\Users\you\AppData\Roaming\pydep\logs\A111.err.log ---
|
|
177
|
+
INFO: Application startup complete.
|
|
178
|
+
[stdout] INFO: 127.0.0.1:54322 - "GET /health HTTP/1.1" 200 OK
|
|
179
|
+
|
|
180
|
+
> pydep remove odin
|
|
181
|
+
[OK] Removed A111 (odin)
|
|
182
|
+
|
|
183
|
+
> pydep list
|
|
184
|
+
No deployments registered.
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Outputs are illustrative. IDs depend on existing registrations and machine services. Commands exit with 0 on success, 1 on operational failure, 2 for invalid command syntax, and 130 on Ctrl+C.
|
|
188
|
+
|
|
189
|
+
## Identity and idempotency
|
|
190
|
+
|
|
191
|
+
The canonical, resolved, case-insensitive project path is the deployment key. Repeated deployment from the same directory preserves its ID. Omitted alias/module/host/port options preserve stored values. Explicit options change desired configuration. Source changes alone are not fingerprinted: use `--restart` to reload changed application code or installed dependencies.
|
|
192
|
+
|
|
193
|
+
NSSM settings are compared with desired settings on each deployment. Drift is repaired. A configuration change restarts the service; an unchanged running service is left alone. A stopped or missing service is started or recreated. An alias-only change does not restart Python.
|
|
194
|
+
|
|
195
|
+
IDs start at `A111`, continue through `A999`, then `B000` through `F999`. This makes the requested starting example explicit. The first free ID in that sequence is used, skipping machine services with occupied names. Removed IDs can be reused. Registry aliases are unique case-insensitively; ASCII letters, digits, and hyphens are allowed, up to 63 characters. Spaces and punctuation normalize to hyphens. ID-shaped aliases are rejected to prevent ambiguous resolution. Automatically generated alias collisions receive `-2`, `-3`, etc.; explicit collisions fail.
|
|
196
|
+
|
|
197
|
+
The registry is per Windows account; services are machine-wide. Aliases are unique within that account's registry. Another account's service name is skipped. Concurrent commands against one registry are serialized with a Windows file lock. Cross-account install races fail safely instead of taking over another service; they may require the recovery procedure below. Do not share a roaming registry across multiple machines or manage the same project under multiple accounts.
|
|
198
|
+
|
|
199
|
+
New deployments default to port 8000. There is no automatic port allocation. A port registered to another deployment in this account is rejected even if its service is stopped. Unregistered programs and other accounts can also occupy ports; inspect Uvicorn bind errors and choose `--port 8001` when necessary.
|
|
200
|
+
|
|
201
|
+
## FastAPI detection and execution
|
|
202
|
+
|
|
203
|
+
Discovery parses source with Python's AST; it does not import application modules. It supports `app/main.py`, `main.py`, and other `main.py` files up to four directory levels deep. It ignores `.venv`, `venv`, `.git`, `__pycache__`, hidden directories, tests, build output, node_modules, and directory symlinks.
|
|
204
|
+
|
|
205
|
+
Top-level instances created via `from fastapi import FastAPI`, renamed imports, or `import fastapi as f` are recognized. The instance can be named `app`, `api`, or another identifier. Ambiguous matches fail with the candidates rather than silently selecting the wrong app. Factories, dynamic creation, and src-layout import-path configuration are outside automatic detection. Use `--module package.module:instance` for an importable explicit target; factories are not supported.
|
|
206
|
+
|
|
207
|
+
The preflight imports FastAPI and Uvicorn using the project's Python. It does not import the selected app or verify its startup hooks. Uvicorn imports the target when the service runs.
|
|
208
|
+
|
|
209
|
+
The service uses the project's interpreter directly, with its working directory set to the project:
|
|
210
|
+
|
|
211
|
+
```text
|
|
212
|
+
D:\odin\.venv\Scripts\python.exe -u -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Venv activation is unnecessary. One Uvicorn process is used, without development reload. All subprocess calls use argument arrays and `shell=False`. NSSM's stored `AppParameters` value uses Windows argv quoting, not shell interpolation.
|
|
216
|
+
|
|
217
|
+
## Storage and logs
|
|
218
|
+
|
|
219
|
+
pydep stores its own files under `%APPDATA%\pydep`:
|
|
220
|
+
|
|
221
|
+
- `deployments.json`: versioned schema, ID, alias, absolute project and interpreter paths, service name, module, host, port, UTC timestamps, and operation phase.
|
|
222
|
+
- `deployments.lock`: lock file; its presence does not mean a command is still running.
|
|
223
|
+
- `logs\A111.out.log` and `logs\A111.err.log`: persistent stdout/stderr.
|
|
224
|
+
- `logs\archive\A111-<timestamp>\...`: retained logs after removal, avoiding confusion when an ID is reused.
|
|
225
|
+
|
|
226
|
+
No application secrets or environment values are captured in the registry. Application logs can still contain sensitive information emitted by your code. Rotation is configured at approximately 10 MiB per active stream with NSSM online rotation. Rotated files are retained; there is no retention-age or total-disk-size cap. Apply your own retention policy. `logs` reads the current files and follows truncation or file replacement; recent output is limited to the last 1 MiB per stream. It does not merge streams chronologically or read historical archives.
|
|
227
|
+
|
|
228
|
+
`remove` stops and deletes only the owned Windows Service, then archives or purges deployment log files and removes its registry record. It never recursively removes a project, source directory, or `.venv`. Empty shared metadata directories and the lock file remain. Previously archived logs are not affected by `--purge-logs` for a newly reused ID.
|
|
229
|
+
|
|
230
|
+
## Windows persistence and service identity
|
|
231
|
+
|
|
232
|
+
Windows Service Control Manager owns the NSSM service lifecycle. pydep configures automatic startup and NSSM application restart behavior, so closing PowerShell does not stop the API. Windows starts the service after reboot; NSSM supervises the Python process. See the [NSSM command reference](https://nssm.cc/commands).
|
|
233
|
+
|
|
234
|
+
New services use NSSM's default **LocalSystem** account, which is highly privileged. Deploy only trusted applications, dependencies, and virtual environments. Protect the project, Python installation, NSSM executable, and metadata/log directories against modification by untrusted users. This small tool is intended for administrator-controlled hosts, not mutually untrusted tenants.
|
|
235
|
+
|
|
236
|
+
For a hardened host, after first deployment stop the service and select an appropriate dedicated account in `services.msc` under **Log On**. Grant that account read/execute access to the project, venv, base Python installation, and NSSM, plus write access to the log directory and any application data directories. Configure credentials through Windows, not pydep. Redeploy preserves an existing service's account. If a service was deleted and recreated, its account returns to NSSM's default and must be configured again.
|
|
237
|
+
|
|
238
|
+
Services do not inherit your interactive PowerShell environment, mapped network drives, or user login state. Use a local, continuously available project and interpreter. Manage application secrets through your existing service-compatible mechanism. pydep does not add firewall rules, TLS, a reverse proxy, or readiness dependencies. The default host binds all interfaces; choose `127.0.0.1` when placing IIS or another local proxy in front.
|
|
239
|
+
|
|
240
|
+
## Failure recovery
|
|
241
|
+
|
|
242
|
+
Registry writes use a same-directory temporary file, flush/fsync, and atomic replacement. A desired-state record is saved **before** service mutation. SCM and JSON cannot be committed as one transaction, so failed or interrupted operations retain a visible phase:
|
|
243
|
+
|
|
244
|
+
- `pending`: deployment/configuration/start was not fully completed. Fix the cause, return to the project, and rerun `pydep deploy`. The ID and desired alias/settings are retained. There is no automatic rollback to the old working configuration.
|
|
245
|
+
- `removing`: removal was interrupted. Rerun `pydep remove <id>`. Do not redeploy until removal finishes.
|
|
246
|
+
- `ready`: last deployment completed; current service status is queried separately.
|
|
247
|
+
|
|
248
|
+
The service description contains a pydep ownership marker derived from the metadata directory and deployment ID. Keep it unchanged. An ownership mismatch causes management to fail safely. This is an accidental-takeover guard, not a security boundary against administrators.
|
|
249
|
+
|
|
250
|
+
If execution stops in the small window between NSSM install and setting the description, the service may exist without a marker. Do not blindly remove it. First inspect `services.msc`, the pending JSON record, and:
|
|
251
|
+
|
|
252
|
+
```powershell
|
|
253
|
+
nssm get PyDep_A111 Application
|
|
254
|
+
nssm get PyDep_A111 AppDirectory
|
|
255
|
+
nssm get PyDep_A111 Description
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Only after confirming it is the service created for this deployment, stop it if running and remove it using `nssm remove PyDep_A111 confirm`. Keep the pending JSON record and rerun deploy. Never delete an unrelated service to free an ID.
|
|
259
|
+
|
|
260
|
+
For a corrupt registry, restore a known-good backup; pydep refuses to overwrite malformed content. Back up the registry before manual edits. If Windows reports a service is marked for deletion, close Services/Event Viewer handles and retry removal. If startup fails, inspect `pydep logs <id>` and Windows Event Viewer for missing imports, occupied ports, invalid settings, or account/path permissions.
|
|
261
|
+
|
|
262
|
+
## Manual Windows acceptance test
|
|
263
|
+
|
|
264
|
+
Use a test machine and a fresh example project; replace the sample ID with the ID actually returned.
|
|
265
|
+
|
|
266
|
+
1. Install NSSM and pydep, create the example under `D:\odin`, and deploy it. Verify `/health` returns `{"status":"ok"}`.
|
|
267
|
+
2. Run `pydep deploy` three more times. Confirm one registry row, one Windows Service, and the same ID/alias. Inspect `nssm get PyDep_A111 AppParameters` and `Start`.
|
|
268
|
+
3. Run `pydep deploy --alias renamed`; confirm the ID is unchanged, `start renamed` works, and the old alias is unknown. Rename back to `odin`.
|
|
269
|
+
4. Stop by ID and confirm HTTP is unavailable; start by alias and confirm HTTP recovers. Repeat start/stop in the already requested state.
|
|
270
|
+
5. Make HTTP requests and inspect `logs odin`, then `logs A111 --follow`. Stop and start the service; confirm existing output remains. Trigger `nssm rotate PyDep_A111`, issue more requests, and confirm follow continues into the new files.
|
|
271
|
+
6. Close PowerShell. Check the endpoint from a new window. Reboot the test machine, then verify `/health` without running pydep start. Inspect the automatic startup setting and service account.
|
|
272
|
+
7. Change service AppParameters manually to port 8999. Run `pydep deploy`; confirm the stored desired port is restored and the service restarts. Edit application code and run `pydep deploy --restart` to verify the new response.
|
|
273
|
+
8. Create a second project. Confirm an explicit duplicate alias fails; deploy it with another alias and `--port 8001`. Confirm both appear once in list.
|
|
274
|
+
9. Test missing venv/Python, `--port 0`, unknown ID/alias, and ambiguous FastAPI instances. Run a mutating command from a non-elevated terminal and verify the Administrator error. Test missing NSSM with an invalid `--nssm` path.
|
|
275
|
+
10. In the disposable project, set an invalid import target with `--module missing:app`. Verify deployment fails or fails its HTTP check; inspect status/logs. Redeploy with the correct module and verify the same ID is recovered.
|
|
276
|
+
11. Run `pydep remove odin`. Confirm `Get-Service PyDep_A111 -ErrorAction SilentlyContinue` returns nothing, list loses only that record, logs are archived, and all source and venv files still exist. Remove the second test deployment too.
|
|
277
|
+
|
|
278
|
+
## Automated tests and layout
|
|
279
|
+
|
|
280
|
+
From the extracted package directory:
|
|
281
|
+
|
|
282
|
+
```powershell
|
|
283
|
+
py -m pip install .
|
|
284
|
+
py -m unittest discover -s tests -v
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
The suite tests behavior with a service double and mocks the NSSM process boundary. It does not install or delete real services. The manual procedure above covers integration.
|
|
288
|
+
|
|
289
|
+
| File | Purpose |
|
|
290
|
+
| --- | --- |
|
|
291
|
+
| `pyproject.toml` | Installable package and global console entry point |
|
|
292
|
+
| `src/pydep/cli.py` | Commands, validation at CLI boundary, log display |
|
|
293
|
+
| `src/pydep/core.py` | Discovery, identity, locked registry, reconciliation/removal |
|
|
294
|
+
| `src/pydep/windows.py` | SCM queries, NSSM configuration and lifecycle |
|
|
295
|
+
| `src/pydep/__init__.py` | Package version |
|
|
296
|
+
| `src/pydep/__main__.py` | `python -m pydep` support |
|
|
297
|
+
| `tests/test_pydep.py` | Automated behavior tests |
|
|
298
|
+
|
|
299
|
+
Settings are centralized in `NSSM.configure`; registry schema version 1 keeps future extension explicit. No database, cloud, Docker, or large CLI framework is used.
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# pydep
|
|
2
|
+
|
|
3
|
+
Deploy a Python/FastAPI application from its existing `.venv` as a persistent Windows Service, managed by NSSM. Standard-library-only CLI, Python 3.10 or later.
|
|
4
|
+
|
|
5
|
+
This package includes implementation and automated behavior tests. The tests use
|
|
6
|
+
a service double, so actual SCM/NSSM integration, account permissions, and
|
|
7
|
+
reboot behavior must still be verified on Windows using the procedure below.
|
|
8
|
+
Passing the automated suite is not a claim that those integration checks ran.
|
|
9
|
+
|
|
10
|
+
## 1. Install NSSM
|
|
11
|
+
|
|
12
|
+
1. Download NSSM from its [official site](https://nssm.cc/download). Use a build compatible with your Windows version, with command-line configuration and online log rotation support (2.24 or later).
|
|
13
|
+
2. Extract the appropriate architecture's `nssm.exe` into a permanent, administrator-controlled location, for example `C:\Tools\nssm\nssm.exe`.
|
|
14
|
+
3. Add `C:\Tools\nssm` to your Windows PATH through **System Properties > Environment Variables**, then open a new PowerShell window.
|
|
15
|
+
4. Verify discovery:
|
|
16
|
+
|
|
17
|
+
```powershell
|
|
18
|
+
Get-Command nssm.exe
|
|
19
|
+
nssm.exe version
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Alternatively, supply `--nssm 'C:\Tools\nssm\nssm.exe'` to deploy/start/stop/remove. `list` queries Windows directly and `logs` reads files, so neither needs NSSM on PATH.
|
|
23
|
+
|
|
24
|
+
Keep the NSSM executable at the same location for the lifetime of its services. pydep does not download, redistribute, or automatically upgrade NSSM. See the official [usage guide](https://nssm.cc/usage).
|
|
25
|
+
|
|
26
|
+
## 2. Install pydep
|
|
27
|
+
|
|
28
|
+
The package published on PyPI is named `pydep-service`, while the installed
|
|
29
|
+
command remains `pydep`. The recommended installation uses uv's isolated tool
|
|
30
|
+
environment:
|
|
31
|
+
|
|
32
|
+
```powershell
|
|
33
|
+
uv tool install pydep-service
|
|
34
|
+
pydep --version
|
|
35
|
+
pydep --help
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Run it once without installing it permanently:
|
|
39
|
+
|
|
40
|
+
```powershell
|
|
41
|
+
uvx --from pydep-service pydep --help
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Alternative installation methods are:
|
|
45
|
+
|
|
46
|
+
```powershell
|
|
47
|
+
pipx install pydep-service
|
|
48
|
+
py -m pip install pydep-service
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Install the CLI into a stable, isolated tool environment or Python
|
|
52
|
+
installation, not an application venv you might delete. A deployed application
|
|
53
|
+
continues to use its own `.venv`; pydep invokes that environment's Python
|
|
54
|
+
directly and does not install FastAPI or Uvicorn for the application. Ensure the
|
|
55
|
+
CLI installation's Scripts directory is on PATH. If `pydep` is not found after
|
|
56
|
+
a pip installation, use `py -m pydep` with the same Python that installed it,
|
|
57
|
+
or add that Python installation's Scripts directory to PATH. No runtime
|
|
58
|
+
packages are required by pydep itself.
|
|
59
|
+
|
|
60
|
+
NSSM is a separate prerequisite: install it independently and make `nssm.exe`
|
|
61
|
+
available on PATH (or pass `--nssm` where supported). Open PowerShell **as
|
|
62
|
+
Administrator** for commands that change services: `deploy`, `start`, `stop`,
|
|
63
|
+
and `remove`. The read-only `list` and `logs` commands do not require elevation.
|
|
64
|
+
|
|
65
|
+
Use the same Windows account each time. Elevating with a different account uses
|
|
66
|
+
that account's `%APPDATA%` and therefore a different deployment registry.
|
|
67
|
+
|
|
68
|
+
## 3. Deploy D:\odin
|
|
69
|
+
|
|
70
|
+
For an existing application, keep its source and existing `.venv`. Ensure its Windows interpreter can import FastAPI and Uvicorn:
|
|
71
|
+
|
|
72
|
+
```powershell
|
|
73
|
+
Set-Location D:\odin
|
|
74
|
+
.\.venv\Scripts\python.exe -m pip install fastapi uvicorn
|
|
75
|
+
pydep deploy --alias odin
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
If you are creating a fresh example, first create the venv with `py -m venv .venv`, create `app\main.py`, and put this in it:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
from fastapi import FastAPI
|
|
82
|
+
|
|
83
|
+
app = FastAPI()
|
|
84
|
+
|
|
85
|
+
@app.get("/health")
|
|
86
|
+
def health():
|
|
87
|
+
return {"status": "ok"}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Example output, assuming the first ID is available:
|
|
91
|
+
|
|
92
|
+
```text
|
|
93
|
+
[OK] Service reconciled: PyDep_A111
|
|
94
|
+
Deployment ID: A111
|
|
95
|
+
Alias: odin
|
|
96
|
+
Module: app.main:app
|
|
97
|
+
Host: 0.0.0.0
|
|
98
|
+
Port: 8000
|
|
99
|
+
Path: D:\odin
|
|
100
|
+
Status: Running (Windows Service state)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Check the actual HTTP endpoint separately:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
Invoke-RestMethod http://127.0.0.1:8000/health
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
`Running` is the observed Windows Service state, not an HTTP health guarantee. pydep waits for three seconds of running state after a start. Application startup failures, slow lifespan hooks, or later failures still require checking application logs and your health endpoint.
|
|
110
|
+
|
|
111
|
+
## Commands
|
|
112
|
+
|
|
113
|
+
| Command | Behavior |
|
|
114
|
+
| --- | --- |
|
|
115
|
+
| `pydep deploy` | Detect and deploy the current directory, or reconcile its existing registration |
|
|
116
|
+
| `pydep deploy --alias odin` | Set or explicitly rename the alias |
|
|
117
|
+
| `pydep deploy --module app.main:app` | Explicit import target, also stored for later deploys |
|
|
118
|
+
| `pydep deploy --host 127.0.0.1 --port 8001` | Set listening address and port |
|
|
119
|
+
| `pydep deploy --restart` | Restart even if settings already match, to reload source changes |
|
|
120
|
+
| `pydep list` | Show actual SCM state and registry operation phase |
|
|
121
|
+
| `pydep start A111` or `pydep start odin` | Start the registered service |
|
|
122
|
+
| `pydep stop A111` or `pydep stop odin` | Stop the registered service |
|
|
123
|
+
| `pydep logs A111` or `pydep logs odin` | Show the last 50 lines of each log |
|
|
124
|
+
| `pydep logs odin --follow --lines 100` | Tail both streams; Ctrl+C exits |
|
|
125
|
+
| `pydep remove A111` or `pydep remove odin` | Remove service and registration, archive logs |
|
|
126
|
+
| `pydep remove odin --purge-logs` | Also delete this deployment's current and rotated logs |
|
|
127
|
+
|
|
128
|
+
Representative command output:
|
|
129
|
+
|
|
130
|
+
```text
|
|
131
|
+
> pydep list
|
|
132
|
+
ID ALIAS STATUS PORT PHASE PATH
|
|
133
|
+
A111 odin Running 8000 ready D:\odin
|
|
134
|
+
A112 rental-api Stopped 8001 ready D:\rental-api
|
|
135
|
+
|
|
136
|
+
> pydep start odin
|
|
137
|
+
[OK] Started A111 (odin)
|
|
138
|
+
|
|
139
|
+
> pydep stop A111
|
|
140
|
+
[OK] Stopped A111 (odin)
|
|
141
|
+
|
|
142
|
+
> pydep logs odin
|
|
143
|
+
--- stdout: C:\Users\you\AppData\Roaming\pydep\logs\A111.out.log ---
|
|
144
|
+
INFO: 127.0.0.1:54321 - "GET /health HTTP/1.1" 200 OK
|
|
145
|
+
--- stderr: C:\Users\you\AppData\Roaming\pydep\logs\A111.err.log ---
|
|
146
|
+
INFO: Application startup complete.
|
|
147
|
+
|
|
148
|
+
> pydep logs A111 --follow
|
|
149
|
+
--- stdout: C:\Users\you\AppData\Roaming\pydep\logs\A111.out.log ---
|
|
150
|
+
(no logs yet)
|
|
151
|
+
--- stderr: C:\Users\you\AppData\Roaming\pydep\logs\A111.err.log ---
|
|
152
|
+
INFO: Application startup complete.
|
|
153
|
+
[stdout] INFO: 127.0.0.1:54322 - "GET /health HTTP/1.1" 200 OK
|
|
154
|
+
|
|
155
|
+
> pydep remove odin
|
|
156
|
+
[OK] Removed A111 (odin)
|
|
157
|
+
|
|
158
|
+
> pydep list
|
|
159
|
+
No deployments registered.
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Outputs are illustrative. IDs depend on existing registrations and machine services. Commands exit with 0 on success, 1 on operational failure, 2 for invalid command syntax, and 130 on Ctrl+C.
|
|
163
|
+
|
|
164
|
+
## Identity and idempotency
|
|
165
|
+
|
|
166
|
+
The canonical, resolved, case-insensitive project path is the deployment key. Repeated deployment from the same directory preserves its ID. Omitted alias/module/host/port options preserve stored values. Explicit options change desired configuration. Source changes alone are not fingerprinted: use `--restart` to reload changed application code or installed dependencies.
|
|
167
|
+
|
|
168
|
+
NSSM settings are compared with desired settings on each deployment. Drift is repaired. A configuration change restarts the service; an unchanged running service is left alone. A stopped or missing service is started or recreated. An alias-only change does not restart Python.
|
|
169
|
+
|
|
170
|
+
IDs start at `A111`, continue through `A999`, then `B000` through `F999`. This makes the requested starting example explicit. The first free ID in that sequence is used, skipping machine services with occupied names. Removed IDs can be reused. Registry aliases are unique case-insensitively; ASCII letters, digits, and hyphens are allowed, up to 63 characters. Spaces and punctuation normalize to hyphens. ID-shaped aliases are rejected to prevent ambiguous resolution. Automatically generated alias collisions receive `-2`, `-3`, etc.; explicit collisions fail.
|
|
171
|
+
|
|
172
|
+
The registry is per Windows account; services are machine-wide. Aliases are unique within that account's registry. Another account's service name is skipped. Concurrent commands against one registry are serialized with a Windows file lock. Cross-account install races fail safely instead of taking over another service; they may require the recovery procedure below. Do not share a roaming registry across multiple machines or manage the same project under multiple accounts.
|
|
173
|
+
|
|
174
|
+
New deployments default to port 8000. There is no automatic port allocation. A port registered to another deployment in this account is rejected even if its service is stopped. Unregistered programs and other accounts can also occupy ports; inspect Uvicorn bind errors and choose `--port 8001` when necessary.
|
|
175
|
+
|
|
176
|
+
## FastAPI detection and execution
|
|
177
|
+
|
|
178
|
+
Discovery parses source with Python's AST; it does not import application modules. It supports `app/main.py`, `main.py`, and other `main.py` files up to four directory levels deep. It ignores `.venv`, `venv`, `.git`, `__pycache__`, hidden directories, tests, build output, node_modules, and directory symlinks.
|
|
179
|
+
|
|
180
|
+
Top-level instances created via `from fastapi import FastAPI`, renamed imports, or `import fastapi as f` are recognized. The instance can be named `app`, `api`, or another identifier. Ambiguous matches fail with the candidates rather than silently selecting the wrong app. Factories, dynamic creation, and src-layout import-path configuration are outside automatic detection. Use `--module package.module:instance` for an importable explicit target; factories are not supported.
|
|
181
|
+
|
|
182
|
+
The preflight imports FastAPI and Uvicorn using the project's Python. It does not import the selected app or verify its startup hooks. Uvicorn imports the target when the service runs.
|
|
183
|
+
|
|
184
|
+
The service uses the project's interpreter directly, with its working directory set to the project:
|
|
185
|
+
|
|
186
|
+
```text
|
|
187
|
+
D:\odin\.venv\Scripts\python.exe -u -m uvicorn app.main:app --host 0.0.0.0 --port 8000
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Venv activation is unnecessary. One Uvicorn process is used, without development reload. All subprocess calls use argument arrays and `shell=False`. NSSM's stored `AppParameters` value uses Windows argv quoting, not shell interpolation.
|
|
191
|
+
|
|
192
|
+
## Storage and logs
|
|
193
|
+
|
|
194
|
+
pydep stores its own files under `%APPDATA%\pydep`:
|
|
195
|
+
|
|
196
|
+
- `deployments.json`: versioned schema, ID, alias, absolute project and interpreter paths, service name, module, host, port, UTC timestamps, and operation phase.
|
|
197
|
+
- `deployments.lock`: lock file; its presence does not mean a command is still running.
|
|
198
|
+
- `logs\A111.out.log` and `logs\A111.err.log`: persistent stdout/stderr.
|
|
199
|
+
- `logs\archive\A111-<timestamp>\...`: retained logs after removal, avoiding confusion when an ID is reused.
|
|
200
|
+
|
|
201
|
+
No application secrets or environment values are captured in the registry. Application logs can still contain sensitive information emitted by your code. Rotation is configured at approximately 10 MiB per active stream with NSSM online rotation. Rotated files are retained; there is no retention-age or total-disk-size cap. Apply your own retention policy. `logs` reads the current files and follows truncation or file replacement; recent output is limited to the last 1 MiB per stream. It does not merge streams chronologically or read historical archives.
|
|
202
|
+
|
|
203
|
+
`remove` stops and deletes only the owned Windows Service, then archives or purges deployment log files and removes its registry record. It never recursively removes a project, source directory, or `.venv`. Empty shared metadata directories and the lock file remain. Previously archived logs are not affected by `--purge-logs` for a newly reused ID.
|
|
204
|
+
|
|
205
|
+
## Windows persistence and service identity
|
|
206
|
+
|
|
207
|
+
Windows Service Control Manager owns the NSSM service lifecycle. pydep configures automatic startup and NSSM application restart behavior, so closing PowerShell does not stop the API. Windows starts the service after reboot; NSSM supervises the Python process. See the [NSSM command reference](https://nssm.cc/commands).
|
|
208
|
+
|
|
209
|
+
New services use NSSM's default **LocalSystem** account, which is highly privileged. Deploy only trusted applications, dependencies, and virtual environments. Protect the project, Python installation, NSSM executable, and metadata/log directories against modification by untrusted users. This small tool is intended for administrator-controlled hosts, not mutually untrusted tenants.
|
|
210
|
+
|
|
211
|
+
For a hardened host, after first deployment stop the service and select an appropriate dedicated account in `services.msc` under **Log On**. Grant that account read/execute access to the project, venv, base Python installation, and NSSM, plus write access to the log directory and any application data directories. Configure credentials through Windows, not pydep. Redeploy preserves an existing service's account. If a service was deleted and recreated, its account returns to NSSM's default and must be configured again.
|
|
212
|
+
|
|
213
|
+
Services do not inherit your interactive PowerShell environment, mapped network drives, or user login state. Use a local, continuously available project and interpreter. Manage application secrets through your existing service-compatible mechanism. pydep does not add firewall rules, TLS, a reverse proxy, or readiness dependencies. The default host binds all interfaces; choose `127.0.0.1` when placing IIS or another local proxy in front.
|
|
214
|
+
|
|
215
|
+
## Failure recovery
|
|
216
|
+
|
|
217
|
+
Registry writes use a same-directory temporary file, flush/fsync, and atomic replacement. A desired-state record is saved **before** service mutation. SCM and JSON cannot be committed as one transaction, so failed or interrupted operations retain a visible phase:
|
|
218
|
+
|
|
219
|
+
- `pending`: deployment/configuration/start was not fully completed. Fix the cause, return to the project, and rerun `pydep deploy`. The ID and desired alias/settings are retained. There is no automatic rollback to the old working configuration.
|
|
220
|
+
- `removing`: removal was interrupted. Rerun `pydep remove <id>`. Do not redeploy until removal finishes.
|
|
221
|
+
- `ready`: last deployment completed; current service status is queried separately.
|
|
222
|
+
|
|
223
|
+
The service description contains a pydep ownership marker derived from the metadata directory and deployment ID. Keep it unchanged. An ownership mismatch causes management to fail safely. This is an accidental-takeover guard, not a security boundary against administrators.
|
|
224
|
+
|
|
225
|
+
If execution stops in the small window between NSSM install and setting the description, the service may exist without a marker. Do not blindly remove it. First inspect `services.msc`, the pending JSON record, and:
|
|
226
|
+
|
|
227
|
+
```powershell
|
|
228
|
+
nssm get PyDep_A111 Application
|
|
229
|
+
nssm get PyDep_A111 AppDirectory
|
|
230
|
+
nssm get PyDep_A111 Description
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
Only after confirming it is the service created for this deployment, stop it if running and remove it using `nssm remove PyDep_A111 confirm`. Keep the pending JSON record and rerun deploy. Never delete an unrelated service to free an ID.
|
|
234
|
+
|
|
235
|
+
For a corrupt registry, restore a known-good backup; pydep refuses to overwrite malformed content. Back up the registry before manual edits. If Windows reports a service is marked for deletion, close Services/Event Viewer handles and retry removal. If startup fails, inspect `pydep logs <id>` and Windows Event Viewer for missing imports, occupied ports, invalid settings, or account/path permissions.
|
|
236
|
+
|
|
237
|
+
## Manual Windows acceptance test
|
|
238
|
+
|
|
239
|
+
Use a test machine and a fresh example project; replace the sample ID with the ID actually returned.
|
|
240
|
+
|
|
241
|
+
1. Install NSSM and pydep, create the example under `D:\odin`, and deploy it. Verify `/health` returns `{"status":"ok"}`.
|
|
242
|
+
2. Run `pydep deploy` three more times. Confirm one registry row, one Windows Service, and the same ID/alias. Inspect `nssm get PyDep_A111 AppParameters` and `Start`.
|
|
243
|
+
3. Run `pydep deploy --alias renamed`; confirm the ID is unchanged, `start renamed` works, and the old alias is unknown. Rename back to `odin`.
|
|
244
|
+
4. Stop by ID and confirm HTTP is unavailable; start by alias and confirm HTTP recovers. Repeat start/stop in the already requested state.
|
|
245
|
+
5. Make HTTP requests and inspect `logs odin`, then `logs A111 --follow`. Stop and start the service; confirm existing output remains. Trigger `nssm rotate PyDep_A111`, issue more requests, and confirm follow continues into the new files.
|
|
246
|
+
6. Close PowerShell. Check the endpoint from a new window. Reboot the test machine, then verify `/health` without running pydep start. Inspect the automatic startup setting and service account.
|
|
247
|
+
7. Change service AppParameters manually to port 8999. Run `pydep deploy`; confirm the stored desired port is restored and the service restarts. Edit application code and run `pydep deploy --restart` to verify the new response.
|
|
248
|
+
8. Create a second project. Confirm an explicit duplicate alias fails; deploy it with another alias and `--port 8001`. Confirm both appear once in list.
|
|
249
|
+
9. Test missing venv/Python, `--port 0`, unknown ID/alias, and ambiguous FastAPI instances. Run a mutating command from a non-elevated terminal and verify the Administrator error. Test missing NSSM with an invalid `--nssm` path.
|
|
250
|
+
10. In the disposable project, set an invalid import target with `--module missing:app`. Verify deployment fails or fails its HTTP check; inspect status/logs. Redeploy with the correct module and verify the same ID is recovered.
|
|
251
|
+
11. Run `pydep remove odin`. Confirm `Get-Service PyDep_A111 -ErrorAction SilentlyContinue` returns nothing, list loses only that record, logs are archived, and all source and venv files still exist. Remove the second test deployment too.
|
|
252
|
+
|
|
253
|
+
## Automated tests and layout
|
|
254
|
+
|
|
255
|
+
From the extracted package directory:
|
|
256
|
+
|
|
257
|
+
```powershell
|
|
258
|
+
py -m pip install .
|
|
259
|
+
py -m unittest discover -s tests -v
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
The suite tests behavior with a service double and mocks the NSSM process boundary. It does not install or delete real services. The manual procedure above covers integration.
|
|
263
|
+
|
|
264
|
+
| File | Purpose |
|
|
265
|
+
| --- | --- |
|
|
266
|
+
| `pyproject.toml` | Installable package and global console entry point |
|
|
267
|
+
| `src/pydep/cli.py` | Commands, validation at CLI boundary, log display |
|
|
268
|
+
| `src/pydep/core.py` | Discovery, identity, locked registry, reconciliation/removal |
|
|
269
|
+
| `src/pydep/windows.py` | SCM queries, NSSM configuration and lifecycle |
|
|
270
|
+
| `src/pydep/__init__.py` | Package version |
|
|
271
|
+
| `src/pydep/__main__.py` | `python -m pydep` support |
|
|
272
|
+
| `tests/test_pydep.py` | Automated behavior tests |
|
|
273
|
+
|
|
274
|
+
Settings are centralized in `NSSM.configure`; registry schema version 1 keeps future extension explicit. No database, cloud, Docker, or large CLI framework is used.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pydep-service"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Deploy FastAPI applications as persistent NSSM Windows Services"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Zubair Jamil" },
|
|
14
|
+
]
|
|
15
|
+
dependencies = []
|
|
16
|
+
keywords = [
|
|
17
|
+
"deployment",
|
|
18
|
+
"fastapi",
|
|
19
|
+
"nssm",
|
|
20
|
+
"service",
|
|
21
|
+
"system-administration",
|
|
22
|
+
"uvicorn",
|
|
23
|
+
"windows",
|
|
24
|
+
]
|
|
25
|
+
classifiers = [
|
|
26
|
+
"Development Status :: 4 - Beta",
|
|
27
|
+
"Environment :: Console",
|
|
28
|
+
"Intended Audience :: Developers",
|
|
29
|
+
"Intended Audience :: System Administrators",
|
|
30
|
+
"Operating System :: Microsoft :: Windows",
|
|
31
|
+
"Programming Language :: Python :: 3",
|
|
32
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
33
|
+
"Programming Language :: Python :: 3.10",
|
|
34
|
+
"Programming Language :: Python :: 3.11",
|
|
35
|
+
"Programming Language :: Python :: 3.12",
|
|
36
|
+
"Programming Language :: Python :: 3.13",
|
|
37
|
+
"Topic :: System :: Installation/Setup",
|
|
38
|
+
"Topic :: System :: Systems Administration",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
pydep = "pydep.cli:main"
|
|
43
|
+
|
|
44
|
+
[tool.setuptools.packages.find]
|
|
45
|
+
where = ["src"]
|