Skip to content

Commit ff068e4

Browse files
Allow2CEOruvnet
andcommitted
Add comprehensive README for Python SDK
Covers installation (pip/keyring), quick start with asyncio, module reference, code examples for all features, and the Allow2 Device Operational Lifecycle architecture. Co-Authored-By: claude-flow <ruv@ruv.net>
1 parent d8d3c81 commit ff068e4

1 file changed

Lines changed: 186 additions & 0 deletions

File tree

README.md

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# Allow2 SDK for Python
2+
3+
[![PyPI version](https://img.shields.io/pypi/v/allow2.svg?style=flat-square)](https://pypi.org/project/allow2/)
4+
[![Python versions](https://img.shields.io/pypi/pyversions/allow2.svg?style=flat-square)](https://pypi.org/project/allow2/)
5+
[![CI](https://img.shields.io/github/actions/workflow/status/Allow2/allow2python/ci.yml?style=flat-square)](https://github.com/Allow2/allow2python/actions)
6+
7+
Official Allow2 Parental Freedom SDK for Python.
8+
9+
| | |
10+
|---|---|
11+
| **Package** | `allow2` |
12+
| **Targets** | Python 3.10+ |
13+
| **Dependencies** | `httpx` |
14+
| **Optional** | `aiohttp` (pairing server), `keyring` (secure credential storage) |
15+
| **Language** | Python (asyncio) |
16+
17+
## Installation
18+
19+
```bash
20+
pip install allow2
21+
```
22+
23+
With optional secure credential storage:
24+
25+
```bash
26+
pip install allow2[keyring]
27+
```
28+
29+
## Quick Start
30+
31+
```python
32+
import asyncio
33+
from allow2 import DeviceDaemon, PlaintextBackend, Activity
34+
35+
async def main():
36+
backend = PlaintextBackend()
37+
38+
daemon = DeviceDaemon(
39+
device_name='Living Room PC',
40+
activities=[Activity(id=1), Activity(id=8)], # Internet + Screen Time
41+
credential_backend=backend,
42+
child_resolver=lambda children: None, # interactive selection
43+
)
44+
45+
daemon.on('pairing-required', lambda info: print(f"Enter PIN: {info['pin']}"))
46+
daemon.on('child-select-required', lambda data: print('Select child:', [c['name'] for c in data['children']]))
47+
daemon.on('warning', lambda w: print(f"Warning: {w['level']}, {w['remaining']}s left"))
48+
daemon.on('soft-lock', lambda _: print('Time is up!'))
49+
50+
await daemon.start()
51+
await daemon.open_app() # triggers pairing if unpaired
52+
53+
asyncio.run(main())
54+
```
55+
56+
## Modules
57+
58+
| Module | File | Purpose |
59+
|--------|------|---------|
60+
| **DeviceDaemon** | `daemon.py` | Main orchestrator managing the full device lifecycle |
61+
| **Allow2Api** | `api.py` | httpx-based async REST client for all Allow2 endpoints |
62+
| **PairingWizard** | `pairing.py` | aiohttp-based pairing wizard (QR code + PIN display) |
63+
| **ChildShield** | `child_shield.py` | PIN hashing (SHA-256 + salt), rate limiting, session timeout |
64+
| **Checker** | `checker.py` | Permission check loop with per-activity enforcement and stacking |
65+
| **Warnings** | `warnings.py` | Configurable progressive warning scheduler |
66+
| **OfflineHandler** | `offline.py` | Response cache, grace period, deny-by-default fallback |
67+
| **RequestManager** | `request.py` | "Request More Time" flow with polling |
68+
| **UpdatePoller** | `updates.py` | Poll for children, quota, ban, and day type changes |
69+
| **Credentials** | `credentials/` | `PlaintextBackend` default + optional `KeyringBackend` |
70+
71+
## Permission Checks
72+
73+
```python
74+
# The check loop runs automatically once a child is selected.
75+
# Listen for results:
76+
@daemon.on('check-result')
77+
def on_check(result):
78+
for activity_id, activity in result['activities'].items():
79+
print(f"{activity_id}: allowed={activity['allowed']}, remaining={activity['remaining']}s")
80+
print(f"Today: {result['day_type_today']}, Tomorrow: {result['day_type_tomorrow']}")
81+
```
82+
83+
## Request More Time
84+
85+
```python
86+
# Child requests 30 more minutes of gaming
87+
result = await daemon.request_more_time(
88+
activity=3, # Gaming
89+
duration=30, # minutes
90+
message="Can I please have more time? Almost done with this level.",
91+
)
92+
93+
# Poll until parent responds
94+
status = await daemon.poll_request_status(result['request_id'], result['status_secret'])
95+
96+
if status['status'] == 'approved':
97+
print(f"Approved! {status['duration']} extra minutes.")
98+
elif status['status'] == 'denied':
99+
print("Request denied.")
100+
```
101+
102+
## Feedback
103+
104+
```python
105+
# Submit feedback
106+
result = await daemon.submit_feedback(
107+
category='not_working',
108+
message='The block screen appears even when time is remaining.',
109+
)
110+
111+
# Load feedback threads
112+
feedback = await daemon.load_device_feedback()
113+
for thread in feedback['discussions']:
114+
print(f"[{thread['category']}] {thread['status']} - {thread['message_count']} messages")
115+
116+
# Reply to a thread
117+
await daemon.reply_to_feedback(result['discussion_id'], 'This happens every Tuesday.')
118+
```
119+
120+
## Warnings
121+
122+
The SDK fires progressive warnings as time runs out:
123+
124+
```
125+
15 min -> 5 min -> 1 min -> 30 sec -> 10 sec -> BLOCKED
126+
```
127+
128+
```python
129+
@daemon.on('warning')
130+
def on_warning(data):
131+
show_warning_banner(f"{data['remaining']} seconds remaining")
132+
133+
@daemon.on('soft-lock')
134+
def on_lock(_):
135+
show_block_screen()
136+
```
137+
138+
## Credential Storage
139+
140+
The SDK uses a pluggable credential backend. The default `PlaintextBackend` writes to `~/.allow2/credentials.json` with `0600` permissions.
141+
142+
For production, use the `KeyringBackend` (backed by the system keychain) or implement your own:
143+
144+
```python
145+
class MyCredentialBackend:
146+
async def load(self) -> dict | None:
147+
"""Return {'user_id': ..., 'pair_id': ..., 'pair_token': ..., 'children': [...]} or None."""
148+
...
149+
150+
async def store(self, credentials: dict) -> None:
151+
"""Persist credentials."""
152+
...
153+
154+
async def clear(self) -> None:
155+
"""Remove stored credentials."""
156+
...
157+
```
158+
159+
## Target Platforms
160+
161+
| Platform | Notes |
162+
|----------|-------|
163+
| **Linux** | Desktop apps, daemons, Raspberry Pi |
164+
| **macOS** | Desktop apps, system services |
165+
| **Windows** | Desktop apps, services |
166+
| **Embedded** | Any device with Python 3.10+ and httpx |
167+
| **Server** | Django, FastAPI, Flask service integrations |
168+
169+
## Architecture
170+
171+
The SDK follows the Allow2 Device Operational Lifecycle:
172+
173+
1. **Pairing** (one-time) -- QR code or 6-digit PIN, parent never enters credentials on device
174+
2. **Child Identification** (every session) -- OS account mapping or child selector with PIN
175+
3. **Permission Checks** (continuous) -- POST to service URL every 30-60s with `log: true`
176+
4. **Warnings & Countdowns** -- progressive alerts before blocking
177+
5. **Request More Time** -- child requests, parent approves/denies from their phone
178+
6. **Feedback** -- in-app bug reports and feature requests
179+
180+
All API communication uses `httpx` with async support. The check endpoint POSTs to the **service URL** (`service.allow2.com`), while all other endpoints use the **API URL** (`api.allow2.com`).
181+
182+
Environment overrides via `ALLOW2_API_URL`, `ALLOW2_VID`, and `ALLOW2_TOKEN` environment variables.
183+
184+
## License
185+
186+
See [LICENSE](LICENSE) for details.

0 commit comments

Comments
 (0)