tidepool 0.1.1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tidepool-0.1.1/LICENSE +21 -0
- tidepool-0.1.1/PKG-INFO +231 -0
- tidepool-0.1.1/README.md +418 -0
- tidepool-0.1.1/pyproject.toml +43 -0
- tidepool-0.1.1/sdk/README.md +208 -0
- tidepool-0.1.1/sdk/__init__.py +0 -0
- tidepool-0.1.1/sdk/cli.py +656 -0
- tidepool-0.1.1/sdk/tp_backend.py +137 -0
- tidepool-0.1.1/sdk/tp_runtime.py +330 -0
- tidepool-0.1.1/sdk/tp_server.py +1206 -0
- tidepool-0.1.1/sdk/tp_templates/auth_base.html +3 -0
- tidepool-0.1.1/sdk/tp_templates/auth_forms.html +82 -0
- tidepool-0.1.1/sdk/tp_templates/email.html +11 -0
- tidepool-0.1.1/setup.cfg +4 -0
- tidepool-0.1.1/tidepool.egg-info/PKG-INFO +231 -0
- tidepool-0.1.1/tidepool.egg-info/SOURCES.txt +18 -0
- tidepool-0.1.1/tidepool.egg-info/dependency_links.txt +1 -0
- tidepool-0.1.1/tidepool.egg-info/entry_points.txt +2 -0
- tidepool-0.1.1/tidepool.egg-info/requires.txt +5 -0
- tidepool-0.1.1/tidepool.egg-info/top_level.txt +1 -0
tidepool-0.1.1/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025-2026 Tidepool
|
|
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.
|
tidepool-0.1.1/PKG-INFO
ADDED
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: tidepool
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: Deploy full-featured web apps in minutes. Auth, payments, admin, email, database — one import.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://tidepool.sh
|
|
7
|
+
Project-URL: Documentation, https://tidepool.sh/api
|
|
8
|
+
Keywords: paas,web,deploy,cli,ai
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
13
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
14
|
+
Requires-Python: >=3.10
|
|
15
|
+
Description-Content-Type: text/markdown
|
|
16
|
+
License-File: LICENSE
|
|
17
|
+
Requires-Dist: requests>=2.28
|
|
18
|
+
Requires-Dist: argon2-cffi>=21.0
|
|
19
|
+
Requires-Dist: jinja2>=3.1
|
|
20
|
+
Requires-Dist: itsdangerous>=2.0
|
|
21
|
+
Requires-Dist: markdown>=3.4
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# Tidepool
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
┌─────────────────────────────────────────────┐
|
|
28
|
+
│ build locally → deploy in 30s → live │
|
|
29
|
+
│ ↑ ↓ │
|
|
30
|
+
│ iterate auth · payments · email │
|
|
31
|
+
│ ↑ db · files · http │
|
|
32
|
+
│ └────────── <you>.tidepool.sh ──┘ │
|
|
33
|
+
└─────────────────────────────────────────────┘
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Build and deploy full-featured web apps in minutes. Auth, payments, admin, email, file storage, database, background tasks — all from a single `tp` object.
|
|
37
|
+
|
|
38
|
+
Designed for AI agents and humans with CLI coding tools like Claude Code. The API is self-documenting (`curl https://tidepool.sh/api`), and every feature is available via the command line.
|
|
39
|
+
|
|
40
|
+
## Quickstart
|
|
41
|
+
|
|
42
|
+
### Install
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install tidepool
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Init a pod
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
tidepool init my-blog
|
|
52
|
+
cd my-blog
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Creates a directory with a default `main.py`:
|
|
56
|
+
```python
|
|
57
|
+
import tp
|
|
58
|
+
tp.page('/', '<h1>Hello from Tidepool!</h1>')
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Develop locally
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
tidepool dev
|
|
65
|
+
# Pod runs at http://localhost:8000
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
The dev server replicates production pod behavior: file I/O goes to `./tp_data/files/`, `tp.db` persists to a JSON file, `tp.state` is readable at `?format=json`. Stripe, R2, and email are optional — the app runs without them.
|
|
69
|
+
|
|
70
|
+
### Deploy
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
tidepool --url https://tidepool.sh register --email you@example.com
|
|
74
|
+
# verify email, then:
|
|
75
|
+
tidepool deploy
|
|
76
|
+
# Pod is live at https://my-blog.tidepool.sh in ~30 seconds
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`deploy` auto-discovers source files and uploads `tp_data/files/` (images, media) and `tp_data/secrets.json` automatically.
|
|
80
|
+
|
|
81
|
+
### Push & Pull
|
|
82
|
+
|
|
83
|
+
Pull a live pod to develop locally — all state comes with it:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
tidepool pull abc123
|
|
87
|
+
# Creates my-blog/ with source files + tp_data/ (db, secrets, files)
|
|
88
|
+
cd my-blog
|
|
89
|
+
tidepool dev
|
|
90
|
+
# Edit code, add data, test locally...
|
|
91
|
+
tidepool push # pushes everything back (hash remembered from pull)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
`pull` downloads source files, `tp.db` → `tp_data/db.json`, `tp.secrets` → `tp_data/secrets.json`, and pod files → `tp_data/files/`. The dev server reads all of these natively — no conversion needed.
|
|
95
|
+
|
|
96
|
+
`push` auto-discovers source files (same as deploy) and uploads them along with `tp_data/db.json` (merge by default), `tp_data/secrets.json`, and all files in `tp_data/files/`. Pushing source files triggers a pod restart. Use `-y` to skip the confirmation prompt.
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
tidepool pull abc123 --dir . # pull into current directory instead of a subdirectory
|
|
100
|
+
tidepool push abc123 # explicit hash (overrides remembered hash)
|
|
101
|
+
tidepool push --file main.py # push specific files instead of auto-discover
|
|
102
|
+
tidepool push --secret STRIPE_KEY=sk_xxx # override a secret
|
|
103
|
+
tidepool push --replace-db # replace all db keys instead of merging
|
|
104
|
+
tidepool push --sync # delete remote files not present locally
|
|
105
|
+
tidepool push -y # skip confirmation prompt
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### .tpignore
|
|
109
|
+
|
|
110
|
+
Create a `.tpignore` file to exclude files from `deploy` and `push` (same syntax as `.gitignore`):
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
# Directories (trailing slash)
|
|
114
|
+
data/
|
|
115
|
+
notebooks/
|
|
116
|
+
|
|
117
|
+
# File patterns
|
|
118
|
+
*.csv
|
|
119
|
+
*.npy
|
|
120
|
+
*.pkl
|
|
121
|
+
*.parquet
|
|
122
|
+
pipeline.py
|
|
123
|
+
build_log.*
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Always ignored regardless of `.tpignore`: `tp_data/`, `__pycache__/`, `.git/`, `venv/`, `.venv/`, `node_modules/`, dotfiles, and `build_log.*`. Files over 50MB are skipped automatically with a warning.
|
|
127
|
+
|
|
128
|
+
`tidepool init` generates a starter `.tpignore`. For existing projects, create one before your first `deploy` or `push` to avoid uploading data files, models, or build artifacts.
|
|
129
|
+
|
|
130
|
+
### Eject Mode
|
|
131
|
+
|
|
132
|
+
For full control over the runtime, eject the internals into your project:
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
tidepool eject
|
|
136
|
+
# Copies tp_runtime.py, tp_server.py, tp_backend.py, tp_templates/ into your project
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
These files are now yours to modify. `tidepool dev`, `deploy`, and `push` auto-detect eject mode when `tp_server.py` exists in the project directory — no flags needed. To undo, delete the ejected files.
|
|
140
|
+
|
|
141
|
+
## Runtime Tools
|
|
142
|
+
|
|
143
|
+
Use `import tp` at the top of every `.py` file. `main.py` runs once at startup to configure the pod — set auth, payments, seed data, register routes. The server dispatches requests directly to handlers.
|
|
144
|
+
|
|
145
|
+
| Name | Description | Usage |
|
|
146
|
+
| ---- | ----------- | ----- |
|
|
147
|
+
| `tp.route` | Register a request handler with path params | `@tp.route('/post/:slug', methods=['GET'])` |
|
|
148
|
+
| `tp.page` | Register a static HTML page (no handler) | `tp.page('/about', '<h1>About</h1>')` |
|
|
149
|
+
| `tp.auth` | Full auth system. Presets: `'paywall'` (pay-first + magic link) or `'standard'` (email/password) | `tp.auth = 'paywall'` or `tp.auth = 'standard'` |
|
|
150
|
+
| `tp.payments` | Stripe subscriptions and one-time purchases (in cents) | `tp.payments = {products: [{id: 'pro', price: 500, recurring: 'month'}]}` |
|
|
151
|
+
| `tp.admin` | Auto-generated admin panel at `/_admin/` | `tp.admin = {users: ['admin@example.com']}` |
|
|
152
|
+
| `tp.create_user` | Create user with hashed password (idempotent) | `tp.create_user('sam@x.com', 'pass', subscriptions={'pro': True})` |
|
|
153
|
+
| `tp.db` | Key-value store, 1GB limit, persisted across runs | `tp.db.set('post:slug', {...})` / `tp.db.get('post:slug')` |
|
|
154
|
+
| `tp.files` | File storage (R2 in prod, 50GB), served at `/_files/` | `tp.files.write('photo.jpg', data)` / `tp.files.read('photo.jpg')` |
|
|
155
|
+
| `tp.email` | Send email with optional HTML and attachments | `tp.email('user@x.com', 'Subject', 'body', html='<p>hi</p>')` |
|
|
156
|
+
| `tp.http` | HTTP client (same API as `requests`), 200 req/60s, SSRF-protected | `tp.http.post(url, json=payload, headers={...})` |
|
|
157
|
+
| `tp.markdown` | Convert markdown to HTML (tables, code, footnotes) | `html = tp.markdown('# Hello\n\nWorld')` |
|
|
158
|
+
| `tp.secrets` | Read-only dict of deploy-time credentials | `api_key = tp.secrets['STRIPE_KEY']` |
|
|
159
|
+
| `tp.state` | Public app state dict, readable at `?format=json` | `tp.state = {'status': 'live'}` |
|
|
160
|
+
| `tp.publish` | Update public JSON state (ETag-supported polling) | `tp.publish({'messages': msgs})` |
|
|
161
|
+
| `tp.background` | Background tasks (max 5). `seconds<=0`: once, `>0`: loop | `@tp.background(seconds=3600)` |
|
|
162
|
+
|
|
163
|
+
**Handler return values:** `str` → 200 HTML, `dict`/`list` → 200 JSON, `int` → status code, `tuple(body, status)` → body + status, `None` → 303 redirect, `generator` → SSE stream.
|
|
164
|
+
|
|
165
|
+
**Request object:** Handler receives `(req, **params)`. Attributes: `req.path`, `req.method`, `req.query`, `req.user` (dict or None), `req.body` (dict), `req.files` (dict of upload metadata — file data auto-saved to `tp.files`).
|
|
166
|
+
|
|
167
|
+
### Auth details
|
|
168
|
+
|
|
169
|
+
**Auth presets:** `tp.auth = 'paywall'` for payment-first apps (no signup form, accounts auto-created at checkout, magic link for return logins — pair with `tp.payments`). `tp.auth = 'standard'` for traditional email/password signup with confirmation, reset, and magic link. Customize after setting a preset: `tp.auth['required'] = ['/dash/*']`, `tp.auth['theme'] = {'accent': '#e74c3c'}`. Or pass a full dict for manual control: `tp.auth = {required: ['/dash/*'], signup: True, reset: True, oauth: ['google']}`.
|
|
170
|
+
|
|
171
|
+
Email confirmation on by default — `signup_confirm: False` to disable. `req.user` in handlers gives the logged-in user including subscriptions, purchases, and `avatar_url` (from Google profile). Theme: `theme: {page: '<html>...{content}...</html>'}` wraps auth pages in your layout; `{content}` receives the form, `{title}` the page title. Simpler: `theme: {accent: '#color', css: '...'}`.
|
|
172
|
+
|
|
173
|
+
**Google OAuth setup:** Add `google_client_id` and `google_client_secret` to `tp_data/secrets.json`. Get credentials at [Google Cloud Console](https://console.cloud.google.com/apis/credentials) → Create OAuth 2.0 Client ID (Web application). Add authorized redirect URI: `http://localhost:8000/_auth/oauth/google/callback` for dev, `https://yourdomain.com/_auth/oauth/google/callback` for prod. That's it — the server handles the rest.
|
|
174
|
+
|
|
175
|
+
### Payments details
|
|
176
|
+
|
|
177
|
+
Set `tp.payments = {products: [{id: 'pro', name: 'Pro', price: 500, recurring: 'month'}]}`. Users pay at `/_pay/pro`, manage subscriptions at `/_pay/portal`. `recurring: 'month'`/`'year'` for subscriptions; omit for one-time. Dev mode simulates purchases instantly. Prod requires `tidepool stripe-connect` (one-time setup).
|
|
178
|
+
|
|
179
|
+
### Admin details
|
|
180
|
+
|
|
181
|
+
Set `tp.admin = {models: {post: {fields: {title: 'string', body: 'text', tier: 'choice:free,pro'}, display: ['title']}}}`. Field types: `string`, `text`, `bool`, `number`, `date`, `choice:a,b,c`. Plus read-only views of users, payments, emails, files. If not set, auto-inferred from `tp.db` key patterns. Admin access control: with `tp.auth` configured, set `users: ['admin@example.com']` to restrict to specific emails (otherwise any logged-in user can access). Without `tp.auth`, admin is open in dev and key-gated in prod (key printed in server logs at startup, access via `/_admin?key=<key>`).
|
|
182
|
+
|
|
183
|
+
### Background tasks
|
|
184
|
+
|
|
185
|
+
```python
|
|
186
|
+
@tp.background() # runs once at startup
|
|
187
|
+
def migrate(tp):
|
|
188
|
+
if not tp.db.get('_migrated_v2'):
|
|
189
|
+
for key, val in tp.db.prefix('post:'):
|
|
190
|
+
val['version'] = 2
|
|
191
|
+
tp.db.set(key, val)
|
|
192
|
+
tp.db.set('_migrated_v2', True)
|
|
193
|
+
|
|
194
|
+
@tp.background(seconds=3600) # every hour
|
|
195
|
+
def send_digest(tp):
|
|
196
|
+
for email, user in tp.users().items():
|
|
197
|
+
if user.get('subscriptions', {}).get('digest'):
|
|
198
|
+
posts = tp.db.prefix('post:', reverse=True, limit=5)
|
|
199
|
+
tp.email(email, 'Hourly Digest', '\n'.join(t for _, t in posts))
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### Server-Sent Events (SSE)
|
|
203
|
+
|
|
204
|
+
Return a generator from any route handler to stream real-time events:
|
|
205
|
+
|
|
206
|
+
```python
|
|
207
|
+
@tp.route('/feed/live')
|
|
208
|
+
def live_feed(req):
|
|
209
|
+
def stream():
|
|
210
|
+
last_count = 0
|
|
211
|
+
while True:
|
|
212
|
+
messages = tp.db.prefix('msg:', reverse=True, limit=20)
|
|
213
|
+
if len(messages) != last_count:
|
|
214
|
+
last_count = len(messages)
|
|
215
|
+
yield {'messages': [m for _, m in messages]}
|
|
216
|
+
time.sleep(2)
|
|
217
|
+
return stream()
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Client-side: `new EventSource('/feed/live')`. Max 100 concurrent SSE connections per pod.
|
|
221
|
+
|
|
222
|
+
### Static files & templating
|
|
223
|
+
|
|
224
|
+
- **Static files** — Files in `static/` alongside `main.py` are served at `/static/<path>`.
|
|
225
|
+
- **Jinja2** — Pre-installed. `from jinja2 import Environment, FileSystemLoader; env = Environment(loader=FileSystemLoader('templates'), autoescape=True)`. Render: `env.get_template('page.html').render(posts=p)`.
|
|
226
|
+
|
|
227
|
+
## Full API reference
|
|
228
|
+
|
|
229
|
+
```bash
|
|
230
|
+
curl https://tidepool.sh/api
|
|
231
|
+
```
|