snapstack 1.0.0__py3-none-any.whl
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.
- pysnap/__init__.py +1 -0
- pysnap/_shared/Dockerfile.j2 +34 -0
- pysnap/_shared/ci.yml.j2 +53 -0
- pysnap/_shared/docker-compose.yml.j2 +46 -0
- pysnap/_shared/dockerignore.j2 +13 -0
- pysnap/_shared/env_example.j2 +24 -0
- pysnap/_shared/gitignore.j2 +15 -0
- pysnap/commands/__init__.py +1 -0
- pysnap/commands/add.py +171 -0
- pysnap/commands/create.py +136 -0
- pysnap/commands/templates_cmd.py +134 -0
- pysnap/commands/update.py +133 -0
- pysnap/community.py +113 -0
- pysnap/config.py +76 -0
- pysnap/generator.py +262 -0
- pysnap/main.py +65 -0
- pysnap/manifest.py +101 -0
- pysnap/plugins.py +123 -0
- pysnap/preview.py +131 -0
- pysnap/prompts.py +217 -0
- pysnap/registry.py +123 -0
- pysnap/templates/django/.dockerignore.j2 +15 -0
- pysnap/templates/django/.github/workflows/ci.yml.j2 +34 -0
- pysnap/templates/django/.gitignore.j2 +14 -0
- pysnap/templates/django/Dockerfile.j2 +14 -0
- pysnap/templates/django/README.md.j2 +36 -0
- pysnap/templates/django/apps/__init__.py.j2 +0 -0
- pysnap/templates/django/apps/core/__init__.py.j2 +0 -0
- pysnap/templates/django/apps/core/apps.py.j2 +6 -0
- pysnap/templates/django/apps/core/urls.py.j2 +7 -0
- pysnap/templates/django/apps/core/views.py.j2 +6 -0
- pysnap/templates/django/apps/users/__init__.py.j2 +0 -0
- pysnap/templates/django/apps/users/apps.py.j2 +6 -0
- pysnap/templates/django/apps/users/models.py.j2 +14 -0
- pysnap/templates/django/apps/users/serializers.py.j2 +13 -0
- pysnap/templates/django/apps/users/urls.py.j2 +10 -0
- pysnap/templates/django/apps/users/views.py.j2 +22 -0
- pysnap/templates/django/config/__init__.py.j2 +0 -0
- pysnap/templates/django/config/asgi.py.j2 +9 -0
- pysnap/templates/django/config/settings.py.j2 +110 -0
- pysnap/templates/django/config/urls.py.j2 +12 -0
- pysnap/templates/django/config/wsgi.py.j2 +9 -0
- pysnap/templates/django/docker-compose.yml.j2 +29 -0
- pysnap/templates/django/manage.py.j2 +22 -0
- pysnap/templates/django/pyproject.toml.j2 +40 -0
- pysnap/templates/django/template.json +50 -0
- pysnap/templates/django/tests/__init__.py.j2 +1 -0
- pysnap/templates/django/tests/conftest.py.j2 +6 -0
- pysnap/templates/django/tests/test_health.py.j2 +9 -0
- pysnap/templates/fastapi/.dockerignore.j2 +8 -0
- pysnap/templates/fastapi/.github/workflows/ci.yml.j2 +46 -0
- pysnap/templates/fastapi/.gitignore.j2 +13 -0
- pysnap/templates/fastapi/Dockerfile.j2 +14 -0
- pysnap/templates/fastapi/README.md.j2 +57 -0
- pysnap/templates/fastapi/api/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/api/routes/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/api/routes/auth.py.j2 +18 -0
- pysnap/templates/fastapi/api/routes/health.py.j2 +8 -0
- pysnap/templates/fastapi/app/__init__.py.j2 +1 -0
- pysnap/templates/fastapi/core/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/core/config.py.j2 +26 -0
- pysnap/templates/fastapi/core/security.py.j2 +22 -0
- pysnap/templates/fastapi/db/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/db/base.py.j2 +5 -0
- pysnap/templates/fastapi/db/session.py.j2 +15 -0
- pysnap/templates/fastapi/docker-compose.yml.j2 +30 -0
- pysnap/templates/fastapi/main.py.j2 +27 -0
- pysnap/templates/fastapi/models/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/models/user.py.j2 +13 -0
- pysnap/templates/fastapi/pyproject.toml.j2 +48 -0
- pysnap/templates/fastapi/schemas/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/schemas/user.py.j2 +18 -0
- pysnap/templates/fastapi/template.json +53 -0
- pysnap/templates/fastapi/tests/__init__.py.j2 +0 -0
- pysnap/templates/fastapi/tests/conftest.py.j2 +9 -0
- pysnap/templates/fastapi/tests/test_health.py.j2 +9 -0
- pysnap/templates/flask/.dockerignore.j2 +14 -0
- pysnap/templates/flask/.github/workflows/ci.yml.j2 +34 -0
- pysnap/templates/flask/.gitignore.j2 +13 -0
- pysnap/templates/flask/Dockerfile.j2 +14 -0
- pysnap/templates/flask/README.md.j2 +34 -0
- pysnap/templates/flask/app/__init__.py.j2 +30 -0
- pysnap/templates/flask/app/config.py.j2 +23 -0
- pysnap/templates/flask/app/extensions.py.j2 +9 -0
- pysnap/templates/flask/app/models/__init__.py.j2 +1 -0
- pysnap/templates/flask/app/models/user.py.j2 +16 -0
- pysnap/templates/flask/app/routes/__init__.py.j2 +1 -0
- pysnap/templates/flask/app/routes/auth.py.j2 +31 -0
- pysnap/templates/flask/app/routes/health.py.j2 +11 -0
- pysnap/templates/flask/docker-compose.yml.j2 +29 -0
- pysnap/templates/flask/pyproject.toml.j2 +39 -0
- pysnap/templates/flask/template.json +44 -0
- pysnap/templates/flask/tests/__init__.py.j2 +1 -0
- pysnap/templates/flask/tests/conftest.py.j2 +16 -0
- pysnap/templates/flask/tests/test_health.py.j2 +8 -0
- pysnap/templates/flask/wsgi.py.j2 +8 -0
- pysnap/validator.py +89 -0
- snapstack-1.0.0.dist-info/METADATA +267 -0
- snapstack-1.0.0.dist-info/RECORD +102 -0
- snapstack-1.0.0.dist-info/WHEEL +4 -0
- snapstack-1.0.0.dist-info/entry_points.txt +2 -0
- snapstack-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
services:
|
|
2
|
+
app:
|
|
3
|
+
build: .
|
|
4
|
+
ports:
|
|
5
|
+
- "5000:5000"
|
|
6
|
+
env_file:
|
|
7
|
+
- .env
|
|
8
|
+
{% if database == "postgresql" %}
|
|
9
|
+
depends_on:
|
|
10
|
+
db:
|
|
11
|
+
condition: service_healthy
|
|
12
|
+
db:
|
|
13
|
+
image: postgres:16-alpine
|
|
14
|
+
environment:
|
|
15
|
+
POSTGRES_USER: ${POSTGRES_USER:-user}
|
|
16
|
+
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-password}
|
|
17
|
+
POSTGRES_DB: ${POSTGRES_DB:-{{ project_name_slug }}}
|
|
18
|
+
ports:
|
|
19
|
+
- "5432:5432"
|
|
20
|
+
volumes:
|
|
21
|
+
- pgdata:/var/lib/postgresql/data
|
|
22
|
+
healthcheck:
|
|
23
|
+
test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER:-user}"]
|
|
24
|
+
interval: 5s
|
|
25
|
+
timeout: 5s
|
|
26
|
+
retries: 5
|
|
27
|
+
volumes:
|
|
28
|
+
pgdata:
|
|
29
|
+
{% endif %}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "{{ project_name }}"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Generated by pysnap"
|
|
9
|
+
requires-python = ">=3.11"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"flask>=3.0.0",
|
|
12
|
+
{%- if database != "none" %}
|
|
13
|
+
"flask-sqlalchemy>=3.1.0",
|
|
14
|
+
{%- endif %}
|
|
15
|
+
{%- if database == "postgresql" %}
|
|
16
|
+
"psycopg[binary]>=3.1.0",
|
|
17
|
+
{%- elif database == "sqlite" %}
|
|
18
|
+
"aiosqlite>=0.20.0",
|
|
19
|
+
{%- endif %}
|
|
20
|
+
{%- if include_auth %}
|
|
21
|
+
"flask-jwt-extended>=4.6.0",
|
|
22
|
+
{%- endif %}
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
[project.optional-dependencies]
|
|
26
|
+
dev = [
|
|
27
|
+
{%- if include_tests %}
|
|
28
|
+
"pytest>=8.0.0",
|
|
29
|
+
"pytest-flask>=1.3.0",
|
|
30
|
+
{%- endif %}
|
|
31
|
+
"ruff>=0.8.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[tool.hatch.build.targets.wheel]
|
|
35
|
+
packages = ["app"]
|
|
36
|
+
|
|
37
|
+
[tool.ruff]
|
|
38
|
+
line-length = 100
|
|
39
|
+
target-version = "py311"
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flask",
|
|
3
|
+
"display_name": "Flask",
|
|
4
|
+
"description": "Minimal but production-ready Flask project with Blueprints, health check, and SQLAlchemy 2",
|
|
5
|
+
"version": "1.0.0",
|
|
6
|
+
"files": {
|
|
7
|
+
"always": {
|
|
8
|
+
"pyproject.toml.j2": "pyproject.toml",
|
|
9
|
+
".gitignore.j2": ".gitignore",
|
|
10
|
+
".env.example.j2": ".env.example",
|
|
11
|
+
"README.md.j2": "README.md",
|
|
12
|
+
"app/__init__.py.j2": "app/__init__.py",
|
|
13
|
+
"app/config.py.j2": "app/config.py",
|
|
14
|
+
"app/extensions.py.j2": "app/extensions.py",
|
|
15
|
+
"app/routes/__init__.py.j2": "app/routes/__init__.py",
|
|
16
|
+
"app/routes/health.py.j2": "app/routes/health.py",
|
|
17
|
+
"wsgi.py.j2": "wsgi.py"
|
|
18
|
+
},
|
|
19
|
+
"when_database": {
|
|
20
|
+
"app/models/__init__.py.j2": "app/models/__init__.py"
|
|
21
|
+
},
|
|
22
|
+
"when_auth": {
|
|
23
|
+
"app/routes/auth.py.j2": "app/routes/auth.py",
|
|
24
|
+
"app/models/user.py.j2": "app/models/user.py"
|
|
25
|
+
},
|
|
26
|
+
"when_docker": {
|
|
27
|
+
"Dockerfile.j2": "Dockerfile",
|
|
28
|
+
"docker-compose.yml.j2": "docker-compose.yml",
|
|
29
|
+
".dockerignore.j2": ".dockerignore"
|
|
30
|
+
},
|
|
31
|
+
"when_tests": {
|
|
32
|
+
"tests/__init__.py.j2": "tests/__init__.py",
|
|
33
|
+
"tests/conftest.py.j2": "tests/conftest.py",
|
|
34
|
+
"tests/test_health.py.j2": "tests/test_health.py"
|
|
35
|
+
},
|
|
36
|
+
"when_ci": {
|
|
37
|
+
".github/workflows/ci.yml.j2": ".github/workflows/ci.yml"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"prompts": [],
|
|
41
|
+
"hooks": {
|
|
42
|
+
"post_generate": []
|
|
43
|
+
}
|
|
44
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Flask tests package."""
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""Flask test fixtures."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from app import create_app
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.fixture()
|
|
8
|
+
def app():
|
|
9
|
+
app = create_app()
|
|
10
|
+
app.config.update({"TESTING": True})
|
|
11
|
+
yield app
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
@pytest.fixture()
|
|
15
|
+
def client(app):
|
|
16
|
+
return app.test_client()
|
pysnap/validator.py
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Post-generation validation.
|
|
3
|
+
|
|
4
|
+
Checks that all generated .py files are syntactically valid and optionally
|
|
5
|
+
runs pytest in the generated project directory.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import py_compile
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from rich.console import Console
|
|
17
|
+
from rich.panel import Panel
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def validate_syntax(project_path: Path) -> list[tuple[str, str]]:
|
|
21
|
+
"""Run py_compile on every .py file in *project_path*.
|
|
22
|
+
|
|
23
|
+
Returns
|
|
24
|
+
-------
|
|
25
|
+
list of (relative_path, error_message)
|
|
26
|
+
Empty list means all files are valid.
|
|
27
|
+
"""
|
|
28
|
+
failures: list[tuple[str, str]] = []
|
|
29
|
+
for py_file in sorted(project_path.rglob("*.py")):
|
|
30
|
+
try:
|
|
31
|
+
py_compile.compile(str(py_file), doraise=True)
|
|
32
|
+
except py_compile.PyCompileError as exc:
|
|
33
|
+
rel = str(py_file.relative_to(project_path))
|
|
34
|
+
failures.append((rel, str(exc)))
|
|
35
|
+
return failures
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def validate_tests(project_path: Path) -> tuple[bool, str]:
|
|
39
|
+
"""Run pytest in *project_path* if a tests/ directory exists.
|
|
40
|
+
|
|
41
|
+
Returns
|
|
42
|
+
-------
|
|
43
|
+
(passed, output)
|
|
44
|
+
"""
|
|
45
|
+
tests_dir = project_path / "tests"
|
|
46
|
+
if not tests_dir.exists():
|
|
47
|
+
return True, "No tests directory found -- skipping pytest."
|
|
48
|
+
|
|
49
|
+
result = subprocess.run(
|
|
50
|
+
[sys.executable, "-m", "pytest", str(tests_dir), "--tb=short", "-q"],
|
|
51
|
+
capture_output=True,
|
|
52
|
+
text=True,
|
|
53
|
+
cwd=str(project_path),
|
|
54
|
+
)
|
|
55
|
+
output = result.stdout + result.stderr
|
|
56
|
+
return result.returncode == 0, output
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def report_validation(
|
|
60
|
+
failures: list[tuple[str, str]],
|
|
61
|
+
console: Console,
|
|
62
|
+
test_result: tuple[bool, str] | None = None,
|
|
63
|
+
) -> None:
|
|
64
|
+
"""Render validation results using Rich panels."""
|
|
65
|
+
if not failures and (test_result is None or test_result[0]):
|
|
66
|
+
console.print(
|
|
67
|
+
Panel(
|
|
68
|
+
"[bold green]Validation passed[/bold green] -- all generated .py files are valid.",
|
|
69
|
+
border_style="green",
|
|
70
|
+
)
|
|
71
|
+
)
|
|
72
|
+
return
|
|
73
|
+
|
|
74
|
+
if failures:
|
|
75
|
+
details = "\n".join(f" [red]{path}[/red]: {msg}" for path, msg in failures)
|
|
76
|
+
console.print(
|
|
77
|
+
Panel(
|
|
78
|
+
f"[bold red]Syntax validation failed[/bold red]\n\n{details}",
|
|
79
|
+
border_style="red",
|
|
80
|
+
)
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
if test_result is not None and not test_result[0]:
|
|
84
|
+
console.print(
|
|
85
|
+
Panel(
|
|
86
|
+
f"[bold red]Tests failed[/bold red]\n\n{test_result[1]}",
|
|
87
|
+
border_style="red",
|
|
88
|
+
)
|
|
89
|
+
)
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: snapstack
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: The create-next-app experience for Python — zero-config FastAPI/Django/Flask scaffolding
|
|
5
|
+
Project-URL: Homepage, https://github.com/thewizard2030/pysnap
|
|
6
|
+
Project-URL: Repository, https://github.com/thewizard2030/pysnap
|
|
7
|
+
Project-URL: Issues, https://github.com/thewizard2030/pysnap/issues
|
|
8
|
+
Author-email: Your Name <you@example.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: boilerplate,cli,django,fastapi,flask,project-starter,scaffolding,snapstack
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
20
|
+
Requires-Python: >=3.11
|
|
21
|
+
Requires-Dist: jinja2>=3.1.0
|
|
22
|
+
Requires-Dist: questionary>=2.0.0
|
|
23
|
+
Requires-Dist: rich>=13.0.0
|
|
24
|
+
Requires-Dist: typer>=0.12.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
28
|
+
Requires-Dist: ruff>=0.8.0; extra == 'dev'
|
|
29
|
+
Description-Content-Type: text/markdown
|
|
30
|
+
|
|
31
|
+
<div align="center">
|
|
32
|
+
|
|
33
|
+
# snapstack
|
|
34
|
+
|
|
35
|
+
**The `create-next-app` experience for Python backend projects.**
|
|
36
|
+
|
|
37
|
+
Scaffold a fully wired FastAPI, Django, or Flask project in seconds -- not hours.
|
|
38
|
+
|
|
39
|
+
[](https://pypi.org/project/snapstack/)
|
|
40
|
+
[](https://www.python.org/downloads/)
|
|
41
|
+
[](https://github.com/thewizard2030/pysnap/actions)
|
|
42
|
+
[](LICENSE)
|
|
43
|
+
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
Every Python developer knows the drill: new project, 45 minutes of boilerplate. Folder structure, config, Docker, CI, tests, auth -- all wired up by hand, every single time.
|
|
49
|
+
|
|
50
|
+
JavaScript solved this years ago with `create-next-app`. Python didn't have an equivalent. **Until now.**
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
pip install snapstack
|
|
54
|
+
snapstack create my-api
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Answer a few prompts. Get a production-ready project. Start coding your actual features.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## What You Get
|
|
62
|
+
|
|
63
|
+
Every generated project includes:
|
|
64
|
+
|
|
65
|
+
- **Working code on first run** -- zero manual edits required
|
|
66
|
+
- **Health check endpoint** already wired and tested
|
|
67
|
+
- **pyproject.toml** with hatchling, typed config, and dev extras
|
|
68
|
+
- **pytest test suite** with fixtures and a passing test
|
|
69
|
+
- **Dockerfile + docker-compose** with multi-stage build and health checks
|
|
70
|
+
- **GitHub Actions CI** with lint + test pipeline
|
|
71
|
+
- **JWT authentication** (optional) with login, register, and refresh
|
|
72
|
+
- **SQLAlchemy 2 / Django ORM** with async support and migrations
|
|
73
|
+
- **.env.example** with documented config variables
|
|
74
|
+
- **.pysnap.json manifest** for future `snapstack update` upgrades
|
|
75
|
+
|
|
76
|
+
## Frameworks
|
|
77
|
+
|
|
78
|
+
<table>
|
|
79
|
+
<tr>
|
|
80
|
+
<td width="33%">
|
|
81
|
+
|
|
82
|
+
### FastAPI
|
|
83
|
+
|
|
84
|
+
Async API with Pydantic v2, SQLAlchemy 2, uvicorn
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
snapstack create my-api \
|
|
88
|
+
--framework fastapi \
|
|
89
|
+
--db postgresql \
|
|
90
|
+
--auth --docker --ci
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
</td>
|
|
94
|
+
<td width="33%">
|
|
95
|
+
|
|
96
|
+
### Django
|
|
97
|
+
|
|
98
|
+
Classic Django with DRF, SimpleJWT, python-decouple
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
snapstack create my-app \
|
|
102
|
+
--framework django \
|
|
103
|
+
--db postgresql \
|
|
104
|
+
--auth --docker --ci
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
</td>
|
|
108
|
+
<td width="33%">
|
|
109
|
+
|
|
110
|
+
### Flask
|
|
111
|
+
|
|
112
|
+
Minimal Flask with Blueprints, SQLAlchemy 2, factory pattern
|
|
113
|
+
|
|
114
|
+
```bash
|
|
115
|
+
snapstack create my-service \
|
|
116
|
+
--framework flask \
|
|
117
|
+
--db sqlite \
|
|
118
|
+
--docker --ci
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
</td>
|
|
122
|
+
</tr>
|
|
123
|
+
</table>
|
|
124
|
+
|
|
125
|
+
## Generated Project Structure
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
my-api/
|
|
129
|
+
├── app/
|
|
130
|
+
│ ├── api/routes/ # Route handlers
|
|
131
|
+
│ ├── core/config.py # Typed settings (.env)
|
|
132
|
+
│ ├── core/security.py # JWT auth (optional)
|
|
133
|
+
│ ├── db/session.py # Async database session
|
|
134
|
+
│ ├── models/ # ORM models
|
|
135
|
+
│ ├── schemas/ # Pydantic schemas
|
|
136
|
+
│ └── main.py # App entrypoint
|
|
137
|
+
├── tests/
|
|
138
|
+
│ ├── conftest.py
|
|
139
|
+
│ └── test_health.py
|
|
140
|
+
├── .github/workflows/ci.yml
|
|
141
|
+
├── Dockerfile
|
|
142
|
+
├── docker-compose.yml
|
|
143
|
+
├── pyproject.toml
|
|
144
|
+
├── .env.example
|
|
145
|
+
├── .pysnap.json # Manifest for `snapstack update`
|
|
146
|
+
└── README.md
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Install
|
|
150
|
+
|
|
151
|
+
```bash
|
|
152
|
+
# Recommended
|
|
153
|
+
uv tool install snapstack
|
|
154
|
+
|
|
155
|
+
# Or with pip
|
|
156
|
+
pip install snapstack
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Requires Python 3.11+.
|
|
160
|
+
|
|
161
|
+
## Quick Start
|
|
162
|
+
|
|
163
|
+
### Interactive mode (guided prompts)
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
snapstack create my-api
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
### Non-interactive mode (CI-friendly)
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
snapstack create my-api \
|
|
173
|
+
--framework fastapi \
|
|
174
|
+
--db sqlite \
|
|
175
|
+
--no-auth \
|
|
176
|
+
--docker \
|
|
177
|
+
--ci \
|
|
178
|
+
--tests \
|
|
179
|
+
--pm uv
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Preview before generating
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
snapstack create my-api
|
|
186
|
+
# Shows a file tree preview before writing anything
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### Add features to existing projects
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
cd my-existing-project
|
|
193
|
+
snapstack add docker # Add Dockerfile + docker-compose
|
|
194
|
+
snapstack add ci # Add GitHub Actions workflow
|
|
195
|
+
snapstack add auth # Add JWT authentication
|
|
196
|
+
snapstack add tests # Add pytest boilerplate
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Update infrastructure files
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
snapstack update # Interactive diff review
|
|
203
|
+
snapstack update --dry-run # See changes without applying
|
|
204
|
+
snapstack update --accept-all
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Community templates
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
# Browse available templates
|
|
211
|
+
snapstack templates list
|
|
212
|
+
snapstack templates search "graphql"
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Plugin System
|
|
216
|
+
|
|
217
|
+
Third-party packages can register custom framework templates via Python entry points:
|
|
218
|
+
|
|
219
|
+
```toml
|
|
220
|
+
# In a plugin's pyproject.toml
|
|
221
|
+
[project.entry-points."pysnap.plugins"]
|
|
222
|
+
my-template = "my_package.plugin:register"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
Plugins appear automatically in prompts after `pip install`.
|
|
226
|
+
|
|
227
|
+
## CLI Reference
|
|
228
|
+
|
|
229
|
+
| Command | Description |
|
|
230
|
+
| -------------------------------------- | -------------------------------------------------- |
|
|
231
|
+
| `snapstack create <name>` | Scaffold a new project |
|
|
232
|
+
| `snapstack add <component>` | Add docker, ci, auth, or tests to existing project |
|
|
233
|
+
| `snapstack update` | Update infrastructure files from latest templates |
|
|
234
|
+
| `snapstack templates list` | List all available templates |
|
|
235
|
+
| `snapstack templates search <keyword>` | Search community template registry |
|
|
236
|
+
| `snapstack --version` | Show version |
|
|
237
|
+
|
|
238
|
+
## Flags for `snapstack create`
|
|
239
|
+
|
|
240
|
+
| Flag | Values | Default |
|
|
241
|
+
| -------------------------- | ------------------------------ | ------------------------- |
|
|
242
|
+
| `--framework`, `-f` | `fastapi`, `django`, `flask` | `fastapi` |
|
|
243
|
+
| `--db`, `-d` | `sqlite`, `postgresql`, `none` | `sqlite` |
|
|
244
|
+
| `--auth` / `--no-auth` | | `--no-auth` |
|
|
245
|
+
| `--docker` / `--no-docker` | | `--docker` |
|
|
246
|
+
| `--ci` / `--no-ci` | | `--ci` |
|
|
247
|
+
| `--tests` / `--no-tests` | | `--tests` |
|
|
248
|
+
| `--pm` | `uv`, `pip`, `poetry` | `uv` |
|
|
249
|
+
| `--output`, `-o` | path | `.` |
|
|
250
|
+
| `--no-preview` | | show preview |
|
|
251
|
+
| `--no-validate` | | validate after generation |
|
|
252
|
+
|
|
253
|
+
## Contributing
|
|
254
|
+
|
|
255
|
+
Contributions welcome! See [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines.
|
|
256
|
+
|
|
257
|
+
```bash
|
|
258
|
+
# Development setup
|
|
259
|
+
git clone https://github.com/thewizard2030/pysnap.git
|
|
260
|
+
cd pysnap
|
|
261
|
+
pip install -e ".[dev]"
|
|
262
|
+
pytest
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## License
|
|
266
|
+
|
|
267
|
+
[MIT](LICENSE)
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
pysnap/__init__.py,sha256=ZhzQKWZ8RFrTIkj7z87B144DI95e8LMfA5w8NDWQDtg,23
|
|
2
|
+
pysnap/community.py,sha256=7wGrE1-wL2TPlGfSylHK36kdQ5r7DliH0VjHHwsUyuQ,3283
|
|
3
|
+
pysnap/config.py,sha256=2h9_usESCCgSK0pjeSpGOKHptercDjcU5JWuNr1A_YU,2097
|
|
4
|
+
pysnap/generator.py,sha256=7jteIAHuTR6jqQXk9gg7KYIpuccpSpP2yGRFUq6lu50,8830
|
|
5
|
+
pysnap/main.py,sha256=MBvzc5OcGZOvEYkMOfvuDjCGfMEN8kqKideMhLLW6g8,1925
|
|
6
|
+
pysnap/manifest.py,sha256=UxjZ0sDBjkaa_8v8fX1ukw_rOkCOfUu12f4rTyEBmtk,3280
|
|
7
|
+
pysnap/plugins.py,sha256=1yy5lmMwsZjShy_0z5YMc8bGo401Sibkk5L8c4WXB9I,3980
|
|
8
|
+
pysnap/preview.py,sha256=3M5fqHGCh_q4q6FJ2XUDsNw4v3mg8dm-V4Vx5qMAO5g,3930
|
|
9
|
+
pysnap/prompts.py,sha256=66GLnqCGc--8VjCYvCGXZIm8dMNvM5NWC0GXvNslZGE,6868
|
|
10
|
+
pysnap/registry.py,sha256=1rq2g87H_tp9z3o85N0eOxYBzY4VCmx2vaiLHfBfkDM,3606
|
|
11
|
+
pysnap/validator.py,sha256=ojawtqs5_pYN2j3TV0AEC5wNQYtZu-AZpqO42V2DRoo,2629
|
|
12
|
+
pysnap/_shared/Dockerfile.j2,sha256=sKtCbWEbzMM1-BuLWnQBH43wnhUYNibD9oe4X24akaQ,932
|
|
13
|
+
pysnap/_shared/ci.yml.j2,sha256=PAGzolo0Mn0p60vhJLWgatu6wcN_iHOkMsAiIpIUfQQ,1040
|
|
14
|
+
pysnap/_shared/docker-compose.yml.j2,sha256=UNOKihi3gVTYS8O0nfdFeCB7oQ-VEohZK7-XGQu8UT0,1003
|
|
15
|
+
pysnap/_shared/dockerignore.j2,sha256=eEo-_jNPHjcjGIHbD6jdtm8zPuwduKy-RnVlNxmRLf0,132
|
|
16
|
+
pysnap/_shared/env_example.j2,sha256=LkPP81xa4xE_eXwPTFesAWB3SLBKiFCw7Is1pHnvxN0,714
|
|
17
|
+
pysnap/_shared/gitignore.j2,sha256=N_ryYqw5x0smCa_rimRHRSyG0bnye541OOxC60IJpAk,155
|
|
18
|
+
pysnap/commands/__init__.py,sha256=zZrm4lb-8r2hGgGKgxmBGc5Wk6dHlAZigwjeEWvzdxI,39
|
|
19
|
+
pysnap/commands/add.py,sha256=c5KWU-iou0VFnCfr4BLpb8g0nYzWrB343Zt3-J060M8,5695
|
|
20
|
+
pysnap/commands/create.py,sha256=G8X2R_wbWOa_9mmKOJTGi9EPc6PRAYCsyGzA_TTNcyU,4691
|
|
21
|
+
pysnap/commands/templates_cmd.py,sha256=IfaEOllmE-jBeSD94A4rARDXgD7KhUDZGifyis0ommw,4322
|
|
22
|
+
pysnap/commands/update.py,sha256=5i5O30pcFyKWTCqN9dyMX05qNgiWPbMbSrCws8FFVwM,4400
|
|
23
|
+
pysnap/templates/django/.dockerignore.j2,sha256=SN3WRpLlVXyivIYHWixXMDKAaxjxRflKplV4sl4cbAg,153
|
|
24
|
+
pysnap/templates/django/.gitignore.j2,sha256=L-Pj26QsHaTM9vpgEQfkDi2wi5VTPXYvDgMUx_YLuyI,145
|
|
25
|
+
pysnap/templates/django/Dockerfile.j2,sha256=BBQVohS_9csyuMOj31AkL-P3xP4FGoNQT5WudpYFYuA,427
|
|
26
|
+
pysnap/templates/django/README.md.j2,sha256=BQDRu6-LaVkX6Gp4pdbEfn5mxynjV7XSfd6y9JjPvZQ,583
|
|
27
|
+
pysnap/templates/django/docker-compose.yml.j2,sha256=dgdLiOv_z0ANdYPXr3wE9qxUuVp0htD_TgMWDoHRgB4,677
|
|
28
|
+
pysnap/templates/django/manage.py.j2,sha256=04KqIph4ereNnI8CsvOcXK2WQD82CP1bK1ZllRWKw-c,684
|
|
29
|
+
pysnap/templates/django/pyproject.toml.j2,sha256=kd7UfX2ZmilZ9mdWJtan7ODCFOYoZia4fTnzLQflO68,852
|
|
30
|
+
pysnap/templates/django/template.json,sha256=TzFwZ2Ud2QBk7O5dPcNrFEqgkLsl3uQixB6mB0wGxcQ,2087
|
|
31
|
+
pysnap/templates/django/.github/workflows/ci.yml.j2,sha256=6WQGAO1lvboBwCgjznOa_Qus_Wi6eNFryfw-EVXl7Cs,742
|
|
32
|
+
pysnap/templates/django/apps/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
33
|
+
pysnap/templates/django/apps/core/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
34
|
+
pysnap/templates/django/apps/core/apps.py.j2,sha256=kbGjkM2ifkNolj3XhS4i3LHTW-0tsRcrsYyxMvkOZ0w,151
|
|
35
|
+
pysnap/templates/django/apps/core/urls.py.j2,sha256=TpcAaVlb0ftP6grdc7qo59040cEH_URtd2EZXagd_fo,146
|
|
36
|
+
pysnap/templates/django/apps/core/views.py.j2,sha256=X8tKRoQJn0aFz08FWICD7Lp_QZgd8tM4H8lHYLRYAYE,180
|
|
37
|
+
pysnap/templates/django/apps/users/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
38
|
+
pysnap/templates/django/apps/users/apps.py.j2,sha256=UuphC1T-F3xyHI-9rEE8wGc_obrJFY6QPBZTei1YJhs,153
|
|
39
|
+
pysnap/templates/django/apps/users/models.py.j2,sha256=W40WChvVIj_jX-9X9aYVNAGks5e_XYq0YWwSR_4llsE,314
|
|
40
|
+
pysnap/templates/django/apps/users/serializers.py.j2,sha256=fRBOgDFkyI2VKqN19YM8NlcLNJUWqfgXMjS1dMv5iRU,398
|
|
41
|
+
pysnap/templates/django/apps/users/urls.py.j2,sha256=v9F3uhPCXSQ-ljE9MdnItiUirmrJE4PkVJpWZFRXeEE,396
|
|
42
|
+
pysnap/templates/django/apps/users/views.py.j2,sha256=iJ3Dm9D9yLeGjmWyC3O12yMgNQs02JNEdYGrC7nHuYA,829
|
|
43
|
+
pysnap/templates/django/config/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
44
|
+
pysnap/templates/django/config/asgi.py.j2,sha256=tBBu3hlkv7dzVtA7glDhRCE9M8nYFIVlGwz6ljYN-aE,227
|
|
45
|
+
pysnap/templates/django/config/settings.py.j2,sha256=FS80Ykaj_-vuqoSv99-g83xPG9T8tYc5L_dwZbySABU,3251
|
|
46
|
+
pysnap/templates/django/config/urls.py.j2,sha256=cDqWSukGEY4W5pwrACDksQP0Hra2Dm6Pf7z-3tTNPsg,302
|
|
47
|
+
pysnap/templates/django/config/wsgi.py.j2,sha256=7hy8snNltrHZlf1miF6UA_QDR-8yiG97LfBCe5_BOq0,227
|
|
48
|
+
pysnap/templates/django/tests/__init__.py.j2,sha256=YJKI6UvBOFtijXsytiWFSlZ-CHYeIjFxh22o_8Pkw3A,29
|
|
49
|
+
pysnap/templates/django/tests/conftest.py.j2,sha256=dgnvjgyNrcm0ej2YdUzM7RDMCqz74uKbvqrSR3bKcbs,87
|
|
50
|
+
pysnap/templates/django/tests/test_health.py.j2,sha256=0gOeH8x3hq0cRoIwUiA4qd_HW_1WX3tYTNoGQHhGsQQ,221
|
|
51
|
+
pysnap/templates/fastapi/.dockerignore.j2,sha256=rAHLK4ZCEWegzGX9PDMqzgPCYD8NnOduK2NCqY6pvKI,63
|
|
52
|
+
pysnap/templates/fastapi/.gitignore.j2,sha256=QfSwIg-J6r5hcSFBCwfJ9GPeHU9T8M70BpTLK9lS7VU,118
|
|
53
|
+
pysnap/templates/fastapi/Dockerfile.j2,sha256=xim8vC18P_ptivFI8lA856daDSzW9mDvalzyLlMXsTk,458
|
|
54
|
+
pysnap/templates/fastapi/README.md.j2,sha256=t5sSnqadpc1CZY50FViLSWx_hGgxH8Sw-zrOjK7wW_c,1015
|
|
55
|
+
pysnap/templates/fastapi/docker-compose.yml.j2,sha256=A6KW1V0rEeFM93Ja4NloJaT3bkAozBS1Q_Auk39V4jc,610
|
|
56
|
+
pysnap/templates/fastapi/main.py.j2,sha256=9Hh1Ja2U9t1pmNtXguPkmRy48FIbU0EkLvRESoYpHBQ,744
|
|
57
|
+
pysnap/templates/fastapi/pyproject.toml.j2,sha256=yRXcrRBS57DyVb9UusPP3OpaWEP8em_cjHJylMKsPFk,992
|
|
58
|
+
pysnap/templates/fastapi/template.json,sha256=WpZdg56cVzhHGo40tqRs-IoJi3gWbcc5PaSXnS7J6VQ,1819
|
|
59
|
+
pysnap/templates/fastapi/.github/workflows/ci.yml.j2,sha256=LiDX3xpcH7-RnLHXPQoAYnVH2T_0WZEf6uzECRTnF30,830
|
|
60
|
+
pysnap/templates/fastapi/api/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
61
|
+
pysnap/templates/fastapi/api/routes/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
62
|
+
pysnap/templates/fastapi/api/routes/auth.py.j2,sha256=Ti_fmU99sQgvCj0WKNUOl16Un3fDLjkAMAWrC05heI4,691
|
|
63
|
+
pysnap/templates/fastapi/api/routes/health.py.j2,sha256=28bLbPtg7XCZKATIzPpV2DZnZJxHBcdIc6NcQbLJxgY,175
|
|
64
|
+
pysnap/templates/fastapi/app/__init__.py.j2,sha256=frcCV1k9oG9oKj3dpUqdJg1PxRT2RSN_XKdLCPjaYaY,2
|
|
65
|
+
pysnap/templates/fastapi/core/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
66
|
+
pysnap/templates/fastapi/core/config.py.j2,sha256=FDdgGkiuWw4En2l-A7w79_fnUVzQZrym_H2yPyZURCo,869
|
|
67
|
+
pysnap/templates/fastapi/core/security.py.j2,sha256=pzsiIab_1JvzyCHwKg0MtQnwr-yJRFjIDEfmA-vqYuw,779
|
|
68
|
+
pysnap/templates/fastapi/db/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
69
|
+
pysnap/templates/fastapi/db/base.py.j2,sha256=mH7f2d_jiyxJSSx9Gk53QBXRa3LiKBsBjkFgvmtH1WA,83
|
|
70
|
+
pysnap/templates/fastapi/db/session.py.j2,sha256=jirWyfC2ATtr5Upqehs7Km5sg9P3MWas_IA_xH9qJ8Q,532
|
|
71
|
+
pysnap/templates/fastapi/models/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
72
|
+
pysnap/templates/fastapi/models/user.py.j2,sha256=GrAxbs6ahDhc-AiauLO0-Y8ieHLXfgwepk37U3qF2Zc,497
|
|
73
|
+
pysnap/templates/fastapi/schemas/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
74
|
+
pysnap/templates/fastapi/schemas/user.py.j2,sha256=FeJTY2d37DDfA8HDgB0PbRipXlw6BjAM2lwfqV5CwOU,314
|
|
75
|
+
pysnap/templates/fastapi/tests/__init__.py.j2,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
76
|
+
pysnap/templates/fastapi/tests/conftest.py.j2,sha256=1cXf2CpKPxanMoAKNYVct_C8ZLdpL-l6dg6JuMMhXcc,231
|
|
77
|
+
pysnap/templates/fastapi/tests/test_health.py.j2,sha256=aUywAGaNJsvgZBamfX1X2sYg4pkOYmnogOBLhPxeqOk,251
|
|
78
|
+
pysnap/templates/flask/.dockerignore.j2,sha256=L7sgez5NnZWwWz5MDLhZCuD9pQGXN6wE2S02ahoU8lU,139
|
|
79
|
+
pysnap/templates/flask/.gitignore.j2,sha256=2DuD1LsnZlC5OuIzYl4f9DYdnv92fn25ibi0HVbnSd4,131
|
|
80
|
+
pysnap/templates/flask/Dockerfile.j2,sha256=B_Bhj9NLkIU5Zi4og3eQ90diE0J8lkWxjeFxwgc5mCo,425
|
|
81
|
+
pysnap/templates/flask/README.md.j2,sha256=Ake0fMrD99GzhXTnSKciGnIGHJs-lofKRbmUvspI_2Y,527
|
|
82
|
+
pysnap/templates/flask/docker-compose.yml.j2,sha256=hUOxgx_0FFabbaJaWhh2bCS2L8vnWMlRYoCoHElZLo0,677
|
|
83
|
+
pysnap/templates/flask/pyproject.toml.j2,sha256=frZ98JRIIKPV3EHBzeI3IQ3MLP_0VN7lrojTBP_D7X4,806
|
|
84
|
+
pysnap/templates/flask/template.json,sha256=KEkCSs2RxhAnItQD-06HbC5XedL1pqsXsUyxIR_90sU,1591
|
|
85
|
+
pysnap/templates/flask/wsgi.py.j2,sha256=r_MHiSxJbxn1kNZZBTo3NPwfjOKq2TDcBkwBuzPztoY,145
|
|
86
|
+
pysnap/templates/flask/.github/workflows/ci.yml.j2,sha256=6WQGAO1lvboBwCgjznOa_Qus_Wi6eNFryfw-EVXl7Cs,742
|
|
87
|
+
pysnap/templates/flask/app/__init__.py.j2,sha256=_rpXot_ND2FxmkaV1oUVej-1Eihjlfe2VINCxwQdoVw,693
|
|
88
|
+
pysnap/templates/flask/app/config.py.j2,sha256=EWLuOAX_FC3KKiITuRUfDG6ycDSU_LzAqI1C1rm6XjI,778
|
|
89
|
+
pysnap/templates/flask/app/extensions.py.j2,sha256=jx3Re1oXxFmyeSZmji1UJ1l0Bis35l8V0Hd_b4eysSQ,230
|
|
90
|
+
pysnap/templates/flask/app/models/__init__.py.j2,sha256=V1Vexk1zHoXd-1K272wxkFDBEjjkWp-JmZPn8k0F_tU,23
|
|
91
|
+
pysnap/templates/flask/app/models/user.py.j2,sha256=pRBSoTGhYMK_EHTurDwqwot8q0fidzsERPFPpmbm8mU,510
|
|
92
|
+
pysnap/templates/flask/app/routes/__init__.py.j2,sha256=jQzbC58bIMb-1Dpoelj5Zb6_AB4StIrAhruNCCqtjzM,23
|
|
93
|
+
pysnap/templates/flask/app/routes/auth.py.j2,sha256=BX8gKOd3XJ4d8pOTJ0a9ajq_pBMbCdIEvwPPbgVEEBk,1084
|
|
94
|
+
pysnap/templates/flask/app/routes/health.py.j2,sha256=Bg08AhOns5qOwu2najdBH-0fnaCYmM4vdPYfV0cP1ZY,286
|
|
95
|
+
pysnap/templates/flask/tests/__init__.py.j2,sha256=oyxQLhjBfuEYuXJ8qLn5uBeVL4f8hL4EEXMKm6RsYX8,28
|
|
96
|
+
pysnap/templates/flask/tests/conftest.py.j2,sha256=eHR2G-kunP64ODjL7Tb9TVTlgnKjZM8b8tFmQGy49D8,260
|
|
97
|
+
pysnap/templates/flask/tests/test_health.py.j2,sha256=EppZQChFe-ao_03SwtgFGoAtVyw-W9PXRSbeBa2L_Ak,206
|
|
98
|
+
snapstack-1.0.0.dist-info/METADATA,sha256=Ousf1jYAaK9RrQZ1jcLXf-_ECEVJFquxTaMFHdS3jtU,7983
|
|
99
|
+
snapstack-1.0.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
100
|
+
snapstack-1.0.0.dist-info/entry_points.txt,sha256=qLRFQX1qWtEjnkLhNLh8-NHV5GeVILndKx29yyxSYRg,46
|
|
101
|
+
snapstack-1.0.0.dist-info/licenses/LICENSE,sha256=JHv2qniMHo36leewRJ8ry7P01OJQpN3RpBxalvfEFtA,1097
|
|
102
|
+
snapstack-1.0.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pysnap contributors
|
|
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.
|