hookly 0.1.0__tar.gz → 0.1.5__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.
- hookly-0.1.5/PKG-INFO +305 -0
- hookly-0.1.5/README.md +278 -0
- hookly-0.1.5/hookly/__init__.py +12 -0
- {hookly-0.1.0 → hookly-0.1.5}/hookly/api/core.py +20 -15
- {hookly-0.1.0 → hookly-0.1.5}/hookly/crm/core.py +1 -6
- {hookly-0.1.0 → hookly-0.1.5}/hookly/db/sqlite.py +10 -8
- {hookly-0.1.0 → hookly-0.1.5}/pyproject.toml +10 -7
- hookly-0.1.0/PKG-INFO +0 -135
- hookly-0.1.0/README.md +0 -110
- hookly-0.1.0/hookly/__init__.py +0 -25
- {hookly-0.1.0 → hookly-0.1.5}/.gitignore +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/.idea/.gitignore +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/.idea/hookly.iml +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/.idea/inspectionProfiles/profiles_settings.xml +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/.idea/misc.xml +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/.idea/workspace.xml +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/hookly/api/__init__.py +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/hookly/crm/__init__.py +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/hookly/db/__init__.py +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/main.py +0 -0
- {hookly-0.1.0 → hookly-0.1.5}/tests/test_basic.py +0 -0
hookly-0.1.5/PKG-INFO
ADDED
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: hookly
|
|
3
|
+
Version: 0.1.5
|
|
4
|
+
Summary: Batteries-included backend toolkit for Python — API, DB and CRM, ready to call.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Xar1ee/hookly
|
|
6
|
+
Project-URL: Repository, https://github.com/Xar1ee/hookly
|
|
7
|
+
Project-URL: Issues, https://github.com/Xar1ee/hookly/issues
|
|
8
|
+
Author-email: Safaraliyev Xushnud <xushnudbackend@gmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
Keywords: api,backend,crm,framework,sqlite,toolkit,webhook
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
20
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Requires-Dist: colorama>=0.4
|
|
23
|
+
Requires-Dist: python-dotenv>=1.0
|
|
24
|
+
Provides-Extra: dev
|
|
25
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# ⚡ Hookly
|
|
29
|
+
|
|
30
|
+
**Batteries-included backend toolkit for Python.**
|
|
31
|
+
Stop wiring up an API, a database and a CRM by hand for every new project — import Hookly and call it.
|
|
32
|
+
|
|
33
|
+

|
|
34
|
+

|
|
35
|
+

|
|
36
|
+

|
|
37
|
+
|
|
38
|
+
Created by **Safaraliyev Xushnud**
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## Contents
|
|
43
|
+
|
|
44
|
+
- [Why Hookly exists](#why-hookly-exists)
|
|
45
|
+
- [Install](#install)
|
|
46
|
+
- [60-second quickstart](#60-second-quickstart)
|
|
47
|
+
- [hookly.db — database without the ceremony](#hooklydb--database-without-the-ceremony)
|
|
48
|
+
- [hookly.crm — contacts and leads, ready to go](#hooklycrm--contacts-and-leads-ready-to-go)
|
|
49
|
+
- [hookly.api — a tiny JSON router](#hooklyapi--a-tiny-json-router)
|
|
50
|
+
- [Configuration via .env](#configuration-via-env)
|
|
51
|
+
- [Full example: a working mini-CRM API](#full-example-a-working-mini-crm-api)
|
|
52
|
+
- [FAQ](#faq)
|
|
53
|
+
- [Roadmap](#roadmap)
|
|
54
|
+
- [Source & links](#source--links)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Why Hookly exists
|
|
59
|
+
|
|
60
|
+
Every backend project — a website, a Telegram bot's admin panel, an internal tool — ends up needing the same three things:
|
|
61
|
+
|
|
62
|
+
1. **Somewhere to store data** (usually a quick SQLite file, at least at first)
|
|
63
|
+
2. **Something to track contacts, leads or requests** (a mini-CRM)
|
|
64
|
+
3. **A way to expose it all as an API** (so a frontend, a bot, or Postman can talk to it)
|
|
65
|
+
|
|
66
|
+
Normally you rebuild all three from scratch, every single time. **Hookly ships all three as ready-made modules that already work together.** You don't write a database layer — you call `Database`. You don't write a contacts table — you call `CRM`. You don't wire up routes by hand — you decorate a function and call `API`.
|
|
67
|
+
|
|
68
|
+
> Hookly isn't trying to replace Django or FastAPI. It's for the moment *before* that — when you just need something working **right now**.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Install
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install hookly
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
That's it — `pip` also pulls in the two small libraries Hookly relies on:
|
|
79
|
+
|
|
80
|
+
| Package | What it's used for |
|
|
81
|
+
|---|---|
|
|
82
|
+
| [`python-dotenv`](https://pypi.org/project/python-dotenv/) | Lets you configure Hookly with a `.env` file instead of hardcoding values |
|
|
83
|
+
| [`colorama`](https://pypi.org/project/colorama/) | Makes Hookly's console output colored — including on Windows |
|
|
84
|
+
|
|
85
|
+
Nothing else. No Django, no ORM, no config files you have to write before your first line of code runs.
|
|
86
|
+
|
|
87
|
+
For local development (running the test suite):
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
pip install -e ".[dev]"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## 60-second quickstart
|
|
96
|
+
|
|
97
|
+
Create a file called `main.py`:
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
from hookly import Database, CRM, API
|
|
101
|
+
|
|
102
|
+
db = Database("app.db") # SQLite file — created automatically
|
|
103
|
+
crm = CRM(db) # contacts table — created automatically
|
|
104
|
+
api = API("my-service") # your API, named however you like
|
|
105
|
+
|
|
106
|
+
@api.get("/contacts")
|
|
107
|
+
def list_contacts():
|
|
108
|
+
return crm.list_contacts()
|
|
109
|
+
|
|
110
|
+
@api.post("/contacts")
|
|
111
|
+
def new_contact():
|
|
112
|
+
contact = crm.add_contact("Ali", phone="+998901234567")
|
|
113
|
+
return {"id": contact.id, "name": contact.name}
|
|
114
|
+
|
|
115
|
+
api.run(port=8000)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
Run it:
|
|
119
|
+
|
|
120
|
+
```bash
|
|
121
|
+
python main.py
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Open `http://127.0.0.1:8000/contacts` in your browser — you'll see an empty list `[]`. Send a `POST` request to the same address (Postman, curl, or your browser's console) and refresh — your contact is there, saved in SQLite, served over HTTP. **That's the whole stack, in fifteen lines.**
|
|
125
|
+
|
|
126
|
+
---
|
|
127
|
+
|
|
128
|
+
## `hookly.db` — database without the ceremony
|
|
129
|
+
|
|
130
|
+
No models to declare, no migration files, no `Base.metadata.create_all()` — describe a table once as a plain dictionary, then insert and fetch with normal Python calls.
|
|
131
|
+
|
|
132
|
+
```python
|
|
133
|
+
from hookly import Database
|
|
134
|
+
|
|
135
|
+
db = Database("app.db")
|
|
136
|
+
|
|
137
|
+
db.create_table("users", {
|
|
138
|
+
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
139
|
+
"name": "TEXT NOT NULL",
|
|
140
|
+
"email": "TEXT",
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
user_id = db.insert("users", name="Ali", email="ali@example.com")
|
|
144
|
+
|
|
145
|
+
all_users = db.fetchall("SELECT * FROM users")
|
|
146
|
+
one_user = db.fetchone("SELECT * FROM users WHERE id = ?", (user_id,))
|
|
147
|
+
|
|
148
|
+
db.update("users", "id = ?", (user_id,), name="Ali Karimov")
|
|
149
|
+
db.delete("users", "id = ?", (user_id,))
|
|
150
|
+
|
|
151
|
+
db.close()
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Or use it as a context manager so it closes itself:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
with Database("app.db") as db:
|
|
158
|
+
db.create_table("logs", {"id": "INTEGER PRIMARY KEY", "message": "TEXT"})
|
|
159
|
+
db.insert("logs", message="app started")
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
## `hookly.crm` — contacts and leads, ready to go
|
|
165
|
+
|
|
166
|
+
Every project eventually needs *some* place to track "people who reached out." Instead of building that table again, use `CRM` — it's a `Database` with a contacts schema and status tracking built in.
|
|
167
|
+
|
|
168
|
+
```python
|
|
169
|
+
from hookly import CRM
|
|
170
|
+
|
|
171
|
+
crm = CRM() # uses its own SQLite file if you don't pass a Database
|
|
172
|
+
|
|
173
|
+
contact = crm.add_contact("Ali", phone="+998901234567", email="ali@example.com")
|
|
174
|
+
# Contact(id=1, name='Ali', phone='+998901234567', email='ali@example.com', status='new', created_at='...')
|
|
175
|
+
|
|
176
|
+
crm.update_status(contact.id, "in_progress")
|
|
177
|
+
|
|
178
|
+
leads_in_progress = crm.list_contacts(status="in_progress")
|
|
179
|
+
|
|
180
|
+
crm.delete_contact(contact.id)
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Statuses move through `new → in_progress → closed` — enough structure to build a real lead pipeline, simple enough to not fight you.
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## `hookly.api` — a tiny JSON router
|
|
188
|
+
|
|
189
|
+
A decorator-based HTTP router, built entirely on Python's standard library — **zero extra dependencies for the API layer itself.** Perfect for prototypes, internal tools, and services that don't need the full weight of a framework yet.
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from hookly import API
|
|
193
|
+
|
|
194
|
+
api = API("my-service")
|
|
195
|
+
|
|
196
|
+
@api.get("/ping")
|
|
197
|
+
def ping():
|
|
198
|
+
return {"status": "ok"}
|
|
199
|
+
|
|
200
|
+
@api.get("/users/count")
|
|
201
|
+
def user_count():
|
|
202
|
+
return {"count": 42}
|
|
203
|
+
|
|
204
|
+
api.run(host="0.0.0.0", port=8000)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Every handler just returns a Python `dict` or `list` — Hookly serializes it to JSON automatically. If a handler raises an exception, Hookly catches it and returns a clean `500` JSON error instead of crashing your whole server.
|
|
208
|
+
|
|
209
|
+
> Outgrowing it? That's expected — swap `hookly.api` for FastAPI or Django REST Framework later. Hookly's job was to get you moving on day one, not to be the framework you ship to millions of users.
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Configuration via `.env`
|
|
214
|
+
|
|
215
|
+
Instead of hardcoding paths and ports, drop a `.env` file next to your script:
|
|
216
|
+
|
|
217
|
+
```
|
|
218
|
+
HOOKLY_DB_PATH=production.db
|
|
219
|
+
HOOKLY_HOST=0.0.0.0
|
|
220
|
+
HOOKLY_PORT=9000
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from hookly import Database, API
|
|
225
|
+
|
|
226
|
+
db = Database() # reads HOOKLY_DB_PATH automatically
|
|
227
|
+
api = API("service")
|
|
228
|
+
api.run() # reads HOOKLY_HOST / HOOKLY_PORT automatically
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
No `.env` file? Hookly falls back to sensible defaults (`hookly.db`, `127.0.0.1:8000`) — configuration is optional, not required.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
## Full example: a working mini-CRM API
|
|
236
|
+
|
|
237
|
+
```python
|
|
238
|
+
from hookly import Database, CRM, API
|
|
239
|
+
|
|
240
|
+
db = Database("crm.db")
|
|
241
|
+
crm = CRM(db)
|
|
242
|
+
api = API("mini-crm")
|
|
243
|
+
|
|
244
|
+
@api.get("/contacts")
|
|
245
|
+
def all_contacts():
|
|
246
|
+
return crm.list_contacts()
|
|
247
|
+
|
|
248
|
+
@api.get("/contacts/new")
|
|
249
|
+
def new_leads():
|
|
250
|
+
return crm.list_contacts(status="new")
|
|
251
|
+
|
|
252
|
+
@api.post("/contacts")
|
|
253
|
+
def add_lead():
|
|
254
|
+
contact = crm.add_contact("New Lead", phone="+998900000000")
|
|
255
|
+
return {"id": contact.id, "status": contact.status}
|
|
256
|
+
|
|
257
|
+
if __name__ == "__main__":
|
|
258
|
+
api.run(port=8000)
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
Five imports, three routes, one file — a running CRM backend with persistent storage.
|
|
262
|
+
|
|
263
|
+
---
|
|
264
|
+
|
|
265
|
+
## FAQ
|
|
266
|
+
|
|
267
|
+
**Does Hookly support PostgreSQL / MySQL?**
|
|
268
|
+
Not yet — `hookly.db` is SQLite-only for now. See [Roadmap](#roadmap).
|
|
269
|
+
|
|
270
|
+
**Can I use Hookly in production?**
|
|
271
|
+
For internal tools and small services, sure. For anything public-facing at scale, use Hookly to prototype fast, then migrate the API layer to FastAPI/Django once you know what you're building — the CRM and DB modules can usually stay as-is.
|
|
272
|
+
|
|
273
|
+
**Why does the API return a `dict`, not a `Response` object?**
|
|
274
|
+
Because 90% of the time that's all you need. Simplicity is the point — if you need response headers, status code control, or streaming, it's time to graduate to a full framework.
|
|
275
|
+
|
|
276
|
+
**Is `.env` required?**
|
|
277
|
+
No — it's entirely optional. Hookly works with zero configuration out of the box.
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Roadmap
|
|
282
|
+
|
|
283
|
+
- [ ] `hookly.auth` — token-based authentication, ready to drop into `API`
|
|
284
|
+
- [ ] `hookly.webhook` — send and receive webhooks natively (it's in the name!)
|
|
285
|
+
- [ ] Async support (`AsyncDatabase`, `AsyncAPI`)
|
|
286
|
+
- [ ] PostgreSQL backend for `hookly.db`
|
|
287
|
+
- [ ] Pagination helpers for large result sets
|
|
288
|
+
- [ ] CLI: `hookly new my-project` to scaffold a starter project
|
|
289
|
+
- [ ] `hookly.telegram` — a thin bridge for wiring a Telegram bot to the same CRM
|
|
290
|
+
|
|
291
|
+
Have an idea? Open an issue on GitHub — see below.
|
|
292
|
+
|
|
293
|
+
---
|
|
294
|
+
|
|
295
|
+
## Source & links
|
|
296
|
+
|
|
297
|
+
- **PyPI:** [pypi.org/project/hookly](https://pypi.org/project/hookly/)
|
|
298
|
+
- **GitHub:** [github.com/Xar1ee/hookly](https://github.com/Xar1ee/hookly)
|
|
299
|
+
- **Issues / feature requests:** [github.com/Xar1ee/hookly/issues](https://github.com/Xar1ee/hookly/issues)
|
|
300
|
+
|
|
301
|
+
Hookly is free and open for everyone. Fork it, break it, improve it — and if something's confusing, open an issue rather than suffering in silence.
|
|
302
|
+
|
|
303
|
+
## License
|
|
304
|
+
|
|
305
|
+
MIT — do whatever you want with it, just keep the license notice.
|
hookly-0.1.5/README.md
ADDED
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
# ⚡ Hookly
|
|
2
|
+
|
|
3
|
+
**Batteries-included backend toolkit for Python.**
|
|
4
|
+
Stop wiring up an API, a database and a CRM by hand for every new project — import Hookly and call it.
|
|
5
|
+
|
|
6
|
+

|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
Created by **Safaraliyev Xushnud**
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Contents
|
|
16
|
+
|
|
17
|
+
- [Why Hookly exists](#why-hookly-exists)
|
|
18
|
+
- [Install](#install)
|
|
19
|
+
- [60-second quickstart](#60-second-quickstart)
|
|
20
|
+
- [hookly.db — database without the ceremony](#hooklydb--database-without-the-ceremony)
|
|
21
|
+
- [hookly.crm — contacts and leads, ready to go](#hooklycrm--contacts-and-leads-ready-to-go)
|
|
22
|
+
- [hookly.api — a tiny JSON router](#hooklyapi--a-tiny-json-router)
|
|
23
|
+
- [Configuration via .env](#configuration-via-env)
|
|
24
|
+
- [Full example: a working mini-CRM API](#full-example-a-working-mini-crm-api)
|
|
25
|
+
- [FAQ](#faq)
|
|
26
|
+
- [Roadmap](#roadmap)
|
|
27
|
+
- [Source & links](#source--links)
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Why Hookly exists
|
|
32
|
+
|
|
33
|
+
Every backend project — a website, a Telegram bot's admin panel, an internal tool — ends up needing the same three things:
|
|
34
|
+
|
|
35
|
+
1. **Somewhere to store data** (usually a quick SQLite file, at least at first)
|
|
36
|
+
2. **Something to track contacts, leads or requests** (a mini-CRM)
|
|
37
|
+
3. **A way to expose it all as an API** (so a frontend, a bot, or Postman can talk to it)
|
|
38
|
+
|
|
39
|
+
Normally you rebuild all three from scratch, every single time. **Hookly ships all three as ready-made modules that already work together.** You don't write a database layer — you call `Database`. You don't write a contacts table — you call `CRM`. You don't wire up routes by hand — you decorate a function and call `API`.
|
|
40
|
+
|
|
41
|
+
> Hookly isn't trying to replace Django or FastAPI. It's for the moment *before* that — when you just need something working **right now**.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Install
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install hookly
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
That's it — `pip` also pulls in the two small libraries Hookly relies on:
|
|
52
|
+
|
|
53
|
+
| Package | What it's used for |
|
|
54
|
+
|---|---|
|
|
55
|
+
| [`python-dotenv`](https://pypi.org/project/python-dotenv/) | Lets you configure Hookly with a `.env` file instead of hardcoding values |
|
|
56
|
+
| [`colorama`](https://pypi.org/project/colorama/) | Makes Hookly's console output colored — including on Windows |
|
|
57
|
+
|
|
58
|
+
Nothing else. No Django, no ORM, no config files you have to write before your first line of code runs.
|
|
59
|
+
|
|
60
|
+
For local development (running the test suite):
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
pip install -e ".[dev]"
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## 60-second quickstart
|
|
69
|
+
|
|
70
|
+
Create a file called `main.py`:
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
from hookly import Database, CRM, API
|
|
74
|
+
|
|
75
|
+
db = Database("app.db") # SQLite file — created automatically
|
|
76
|
+
crm = CRM(db) # contacts table — created automatically
|
|
77
|
+
api = API("my-service") # your API, named however you like
|
|
78
|
+
|
|
79
|
+
@api.get("/contacts")
|
|
80
|
+
def list_contacts():
|
|
81
|
+
return crm.list_contacts()
|
|
82
|
+
|
|
83
|
+
@api.post("/contacts")
|
|
84
|
+
def new_contact():
|
|
85
|
+
contact = crm.add_contact("Ali", phone="+998901234567")
|
|
86
|
+
return {"id": contact.id, "name": contact.name}
|
|
87
|
+
|
|
88
|
+
api.run(port=8000)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Run it:
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
python main.py
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
Open `http://127.0.0.1:8000/contacts` in your browser — you'll see an empty list `[]`. Send a `POST` request to the same address (Postman, curl, or your browser's console) and refresh — your contact is there, saved in SQLite, served over HTTP. **That's the whole stack, in fifteen lines.**
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## `hookly.db` — database without the ceremony
|
|
102
|
+
|
|
103
|
+
No models to declare, no migration files, no `Base.metadata.create_all()` — describe a table once as a plain dictionary, then insert and fetch with normal Python calls.
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from hookly import Database
|
|
107
|
+
|
|
108
|
+
db = Database("app.db")
|
|
109
|
+
|
|
110
|
+
db.create_table("users", {
|
|
111
|
+
"id": "INTEGER PRIMARY KEY AUTOINCREMENT",
|
|
112
|
+
"name": "TEXT NOT NULL",
|
|
113
|
+
"email": "TEXT",
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
user_id = db.insert("users", name="Ali", email="ali@example.com")
|
|
117
|
+
|
|
118
|
+
all_users = db.fetchall("SELECT * FROM users")
|
|
119
|
+
one_user = db.fetchone("SELECT * FROM users WHERE id = ?", (user_id,))
|
|
120
|
+
|
|
121
|
+
db.update("users", "id = ?", (user_id,), name="Ali Karimov")
|
|
122
|
+
db.delete("users", "id = ?", (user_id,))
|
|
123
|
+
|
|
124
|
+
db.close()
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Or use it as a context manager so it closes itself:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
with Database("app.db") as db:
|
|
131
|
+
db.create_table("logs", {"id": "INTEGER PRIMARY KEY", "message": "TEXT"})
|
|
132
|
+
db.insert("logs", message="app started")
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## `hookly.crm` — contacts and leads, ready to go
|
|
138
|
+
|
|
139
|
+
Every project eventually needs *some* place to track "people who reached out." Instead of building that table again, use `CRM` — it's a `Database` with a contacts schema and status tracking built in.
|
|
140
|
+
|
|
141
|
+
```python
|
|
142
|
+
from hookly import CRM
|
|
143
|
+
|
|
144
|
+
crm = CRM() # uses its own SQLite file if you don't pass a Database
|
|
145
|
+
|
|
146
|
+
contact = crm.add_contact("Ali", phone="+998901234567", email="ali@example.com")
|
|
147
|
+
# Contact(id=1, name='Ali', phone='+998901234567', email='ali@example.com', status='new', created_at='...')
|
|
148
|
+
|
|
149
|
+
crm.update_status(contact.id, "in_progress")
|
|
150
|
+
|
|
151
|
+
leads_in_progress = crm.list_contacts(status="in_progress")
|
|
152
|
+
|
|
153
|
+
crm.delete_contact(contact.id)
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Statuses move through `new → in_progress → closed` — enough structure to build a real lead pipeline, simple enough to not fight you.
|
|
157
|
+
|
|
158
|
+
---
|
|
159
|
+
|
|
160
|
+
## `hookly.api` — a tiny JSON router
|
|
161
|
+
|
|
162
|
+
A decorator-based HTTP router, built entirely on Python's standard library — **zero extra dependencies for the API layer itself.** Perfect for prototypes, internal tools, and services that don't need the full weight of a framework yet.
|
|
163
|
+
|
|
164
|
+
```python
|
|
165
|
+
from hookly import API
|
|
166
|
+
|
|
167
|
+
api = API("my-service")
|
|
168
|
+
|
|
169
|
+
@api.get("/ping")
|
|
170
|
+
def ping():
|
|
171
|
+
return {"status": "ok"}
|
|
172
|
+
|
|
173
|
+
@api.get("/users/count")
|
|
174
|
+
def user_count():
|
|
175
|
+
return {"count": 42}
|
|
176
|
+
|
|
177
|
+
api.run(host="0.0.0.0", port=8000)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Every handler just returns a Python `dict` or `list` — Hookly serializes it to JSON automatically. If a handler raises an exception, Hookly catches it and returns a clean `500` JSON error instead of crashing your whole server.
|
|
181
|
+
|
|
182
|
+
> Outgrowing it? That's expected — swap `hookly.api` for FastAPI or Django REST Framework later. Hookly's job was to get you moving on day one, not to be the framework you ship to millions of users.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Configuration via `.env`
|
|
187
|
+
|
|
188
|
+
Instead of hardcoding paths and ports, drop a `.env` file next to your script:
|
|
189
|
+
|
|
190
|
+
```
|
|
191
|
+
HOOKLY_DB_PATH=production.db
|
|
192
|
+
HOOKLY_HOST=0.0.0.0
|
|
193
|
+
HOOKLY_PORT=9000
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from hookly import Database, API
|
|
198
|
+
|
|
199
|
+
db = Database() # reads HOOKLY_DB_PATH automatically
|
|
200
|
+
api = API("service")
|
|
201
|
+
api.run() # reads HOOKLY_HOST / HOOKLY_PORT automatically
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
No `.env` file? Hookly falls back to sensible defaults (`hookly.db`, `127.0.0.1:8000`) — configuration is optional, not required.
|
|
205
|
+
|
|
206
|
+
---
|
|
207
|
+
|
|
208
|
+
## Full example: a working mini-CRM API
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
from hookly import Database, CRM, API
|
|
212
|
+
|
|
213
|
+
db = Database("crm.db")
|
|
214
|
+
crm = CRM(db)
|
|
215
|
+
api = API("mini-crm")
|
|
216
|
+
|
|
217
|
+
@api.get("/contacts")
|
|
218
|
+
def all_contacts():
|
|
219
|
+
return crm.list_contacts()
|
|
220
|
+
|
|
221
|
+
@api.get("/contacts/new")
|
|
222
|
+
def new_leads():
|
|
223
|
+
return crm.list_contacts(status="new")
|
|
224
|
+
|
|
225
|
+
@api.post("/contacts")
|
|
226
|
+
def add_lead():
|
|
227
|
+
contact = crm.add_contact("New Lead", phone="+998900000000")
|
|
228
|
+
return {"id": contact.id, "status": contact.status}
|
|
229
|
+
|
|
230
|
+
if __name__ == "__main__":
|
|
231
|
+
api.run(port=8000)
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Five imports, three routes, one file — a running CRM backend with persistent storage.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## FAQ
|
|
239
|
+
|
|
240
|
+
**Does Hookly support PostgreSQL / MySQL?**
|
|
241
|
+
Not yet — `hookly.db` is SQLite-only for now. See [Roadmap](#roadmap).
|
|
242
|
+
|
|
243
|
+
**Can I use Hookly in production?**
|
|
244
|
+
For internal tools and small services, sure. For anything public-facing at scale, use Hookly to prototype fast, then migrate the API layer to FastAPI/Django once you know what you're building — the CRM and DB modules can usually stay as-is.
|
|
245
|
+
|
|
246
|
+
**Why does the API return a `dict`, not a `Response` object?**
|
|
247
|
+
Because 90% of the time that's all you need. Simplicity is the point — if you need response headers, status code control, or streaming, it's time to graduate to a full framework.
|
|
248
|
+
|
|
249
|
+
**Is `.env` required?**
|
|
250
|
+
No — it's entirely optional. Hookly works with zero configuration out of the box.
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
## Roadmap
|
|
255
|
+
|
|
256
|
+
- [ ] `hookly.auth` — token-based authentication, ready to drop into `API`
|
|
257
|
+
- [ ] `hookly.webhook` — send and receive webhooks natively (it's in the name!)
|
|
258
|
+
- [ ] Async support (`AsyncDatabase`, `AsyncAPI`)
|
|
259
|
+
- [ ] PostgreSQL backend for `hookly.db`
|
|
260
|
+
- [ ] Pagination helpers for large result sets
|
|
261
|
+
- [ ] CLI: `hookly new my-project` to scaffold a starter project
|
|
262
|
+
- [ ] `hookly.telegram` — a thin bridge for wiring a Telegram bot to the same CRM
|
|
263
|
+
|
|
264
|
+
Have an idea? Open an issue on GitHub — see below.
|
|
265
|
+
|
|
266
|
+
---
|
|
267
|
+
|
|
268
|
+
## Source & links
|
|
269
|
+
|
|
270
|
+
- **PyPI:** [pypi.org/project/hookly](https://pypi.org/project/hookly/)
|
|
271
|
+
- **GitHub:** [github.com/Xar1ee/hookly](https://github.com/Xar1ee/hookly)
|
|
272
|
+
- **Issues / feature requests:** [github.com/Xar1ee/hookly/issues](https://github.com/Xar1ee/hookly/issues)
|
|
273
|
+
|
|
274
|
+
Hookly is free and open for everyone. Fork it, break it, improve it — and if something's confusing, open an issue rather than suffering in silence.
|
|
275
|
+
|
|
276
|
+
## License
|
|
277
|
+
|
|
278
|
+
MIT — do whatever you want with it, just keep the license notice.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Hookly — batteries-included backend toolkit for Python.
|
|
3
|
+
|
|
4
|
+
from hookly import Database, CRM, API
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from .db import Database
|
|
8
|
+
from .crm import CRM
|
|
9
|
+
from .api import API
|
|
10
|
+
|
|
11
|
+
__version__ = "0.1.1"
|
|
12
|
+
__all__ = ["Database", "CRM", "API", "__version__"]
|
|
@@ -1,21 +1,24 @@
|
|
|
1
1
|
"""
|
|
2
|
-
hookly.api — a tiny decorator-based JSON API router
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
Good enough for internal tools and prototypes; swap in ASGI later for
|
|
6
|
-
production if you outgrow it.
|
|
2
|
+
hookly.api — a tiny decorator-based JSON API router, pure stdlib for the
|
|
3
|
+
server itself, with a colorama-powered console banner (works on Windows
|
|
4
|
+
too, unlike raw ANSI codes) and .env-based defaults via python-dotenv.
|
|
7
5
|
"""
|
|
8
6
|
|
|
9
7
|
from __future__ import annotations
|
|
10
8
|
|
|
11
9
|
import json
|
|
10
|
+
import os
|
|
12
11
|
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
13
12
|
from typing import Callable
|
|
14
13
|
|
|
14
|
+
from colorama import Fore, Style, init as colorama_init
|
|
15
|
+
from dotenv import load_dotenv
|
|
16
|
+
|
|
17
|
+
load_dotenv()
|
|
18
|
+
colorama_init(autoreset=True)
|
|
15
19
|
|
|
16
|
-
class API:
|
|
17
|
-
"""Call it, don't build it: a minimal HTTP router."""
|
|
18
20
|
|
|
21
|
+
class API:
|
|
19
22
|
def __init__(self, name: str = "hookly-api") -> None:
|
|
20
23
|
self.name = name
|
|
21
24
|
self._routes: dict[tuple[str, str], Callable] = {}
|
|
@@ -24,7 +27,6 @@ class API:
|
|
|
24
27
|
def decorator(func: Callable) -> Callable:
|
|
25
28
|
self._routes[(method.upper(), path)] = func
|
|
26
29
|
return func
|
|
27
|
-
|
|
28
30
|
return decorator
|
|
29
31
|
|
|
30
32
|
def get(self, path: str) -> Callable:
|
|
@@ -33,7 +35,9 @@ class API:
|
|
|
33
35
|
def post(self, path: str) -> Callable:
|
|
34
36
|
return self.route(path, "POST")
|
|
35
37
|
|
|
36
|
-
def run(self, host: str =
|
|
38
|
+
def run(self, host: str | None = None, port: int | None = None) -> None:
|
|
39
|
+
host = host or os.getenv("HOOKLY_HOST", "127.0.0.1")
|
|
40
|
+
port = port or int(os.getenv("HOOKLY_PORT", "8000"))
|
|
37
41
|
routes = self._routes
|
|
38
42
|
|
|
39
43
|
class Handler(BaseHTTPRequestHandler):
|
|
@@ -49,7 +53,7 @@ class API:
|
|
|
49
53
|
result = handler()
|
|
50
54
|
body = json.dumps(result).encode()
|
|
51
55
|
status = 200
|
|
52
|
-
except Exception as exc:
|
|
56
|
+
except Exception as exc:
|
|
53
57
|
body = json.dumps({"error": str(exc)}).encode()
|
|
54
58
|
status = 500
|
|
55
59
|
self.send_response(status)
|
|
@@ -57,14 +61,15 @@ class API:
|
|
|
57
61
|
self.end_headers()
|
|
58
62
|
self.wfile.write(body)
|
|
59
63
|
|
|
60
|
-
def do_GET(self) -> None:
|
|
64
|
+
def do_GET(self) -> None:
|
|
61
65
|
self._handle("GET")
|
|
62
66
|
|
|
63
|
-
def do_POST(self) -> None:
|
|
67
|
+
def do_POST(self) -> None:
|
|
64
68
|
self._handle("POST")
|
|
65
69
|
|
|
66
|
-
def log_message(self, format: str, *args) -> None:
|
|
67
|
-
pass
|
|
70
|
+
def log_message(self, format: str, *args) -> None:
|
|
71
|
+
pass
|
|
68
72
|
|
|
69
|
-
print(f"⚡ {self.name}
|
|
73
|
+
print(f"{Fore.CYAN}⚡ {Style.BRIGHT}{self.name}{Style.RESET_ALL}{Fore.CYAN} "
|
|
74
|
+
f"running at {Fore.YELLOW}http://{host}:{port}{Style.RESET_ALL}")
|
|
70
75
|
HTTPServer((host, port), Handler).serve_forever()
|
|
@@ -1,8 +1,5 @@
|
|
|
1
1
|
"""
|
|
2
2
|
hookly.crm — a ready-made contacts/leads module.
|
|
3
|
-
|
|
4
|
-
Drop it on top of any Database instance and you get a contacts table,
|
|
5
|
-
lead statuses and simple queries for free.
|
|
6
3
|
"""
|
|
7
4
|
|
|
8
5
|
from __future__ import annotations
|
|
@@ -20,13 +17,11 @@ class Contact:
|
|
|
20
17
|
name: str
|
|
21
18
|
phone: str = ""
|
|
22
19
|
email: str = ""
|
|
23
|
-
status: str = "new"
|
|
20
|
+
status: str = "new"
|
|
24
21
|
created_at: str = field(default_factory=lambda: datetime.now(timezone.utc).isoformat())
|
|
25
22
|
|
|
26
23
|
|
|
27
24
|
class CRM:
|
|
28
|
-
"""A small CRM: contacts in, statuses tracked, no setup required."""
|
|
29
|
-
|
|
30
25
|
STATUSES = ("new", "in_progress", "closed")
|
|
31
26
|
|
|
32
27
|
def __init__(self, db: Optional[Database] = None) -> None:
|
|
@@ -3,24 +3,30 @@ hookly.db — a SQLite layer with no ceremony.
|
|
|
3
3
|
|
|
4
4
|
No models to declare, no migrations to write for simple cases: describe a
|
|
5
5
|
table once, then insert/fetch with plain Python calls.
|
|
6
|
+
|
|
7
|
+
The default database path can be set via a HOOKLY_DB_PATH variable in a
|
|
8
|
+
.env file, thanks to python-dotenv — handy for keeping paths out of code.
|
|
6
9
|
"""
|
|
7
10
|
|
|
8
11
|
from __future__ import annotations
|
|
9
12
|
|
|
13
|
+
import os
|
|
10
14
|
import sqlite3
|
|
11
15
|
from typing import Any, Iterable, Optional
|
|
12
16
|
|
|
17
|
+
from dotenv import load_dotenv
|
|
18
|
+
|
|
19
|
+
load_dotenv()
|
|
20
|
+
|
|
13
21
|
|
|
14
22
|
class Database:
|
|
15
23
|
"""Thin, friendly wrapper around sqlite3."""
|
|
16
24
|
|
|
17
|
-
def __init__(self, path: str =
|
|
18
|
-
self.path = path
|
|
25
|
+
def __init__(self, path: str | None = None) -> None:
|
|
26
|
+
self.path = path or os.getenv("HOOKLY_DB_PATH", "hookly.db")
|
|
19
27
|
self._conn = sqlite3.connect(self.path)
|
|
20
28
|
self._conn.row_factory = sqlite3.Row
|
|
21
29
|
|
|
22
|
-
# -- low level -----------------------------------------------------
|
|
23
|
-
|
|
24
30
|
def execute(self, query: str, params: Iterable[Any] = ()) -> sqlite3.Cursor:
|
|
25
31
|
cur = self._conn.execute(query, tuple(params))
|
|
26
32
|
self._conn.commit()
|
|
@@ -35,15 +41,11 @@ class Database:
|
|
|
35
41
|
row = cur.fetchone()
|
|
36
42
|
return dict(row) if row else None
|
|
37
43
|
|
|
38
|
-
# -- convenience -----------------------------------------------------
|
|
39
|
-
|
|
40
44
|
def create_table(self, name: str, columns: dict[str, str]) -> None:
|
|
41
|
-
"""create_table('users', {'id': 'INTEGER PRIMARY KEY', 'name': 'TEXT'})"""
|
|
42
45
|
cols_sql = ", ".join(f"{col} {ctype}" for col, ctype in columns.items())
|
|
43
46
|
self.execute(f"CREATE TABLE IF NOT EXISTS {name} ({cols_sql})")
|
|
44
47
|
|
|
45
48
|
def insert(self, table: str, **fields: Any) -> int:
|
|
46
|
-
"""insert('users', name='Ali', email='ali@example.com') -> new row id"""
|
|
47
49
|
keys = ", ".join(fields.keys())
|
|
48
50
|
placeholders = ", ".join("?" for _ in fields)
|
|
49
51
|
cur = self.execute(
|
|
@@ -4,13 +4,13 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "hookly"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.5"
|
|
8
8
|
description = "Batteries-included backend toolkit for Python — API, DB and CRM, ready to call."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
11
11
|
license = { text = "MIT" }
|
|
12
|
-
authors = [{ name = "
|
|
13
|
-
keywords = ["backend", "api", "crm", "sqlite", "toolkit", "framework"]
|
|
12
|
+
authors = [{ name = "Safaraliyev Xushnud", email = "xushnudbackend@gmail.com" }]
|
|
13
|
+
keywords = ["backend", "api", "crm", "sqlite", "toolkit", "framework", "webhook"]
|
|
14
14
|
classifiers = [
|
|
15
15
|
"Development Status :: 3 - Alpha",
|
|
16
16
|
"Intended Audience :: Developers",
|
|
@@ -23,15 +23,18 @@ classifiers = [
|
|
|
23
23
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
24
24
|
"Topic :: Internet :: WWW/HTTP :: WSGI :: Application",
|
|
25
25
|
]
|
|
26
|
-
dependencies = [
|
|
26
|
+
dependencies = [
|
|
27
|
+
"python-dotenv>=1.0",
|
|
28
|
+
"colorama>=0.4",
|
|
29
|
+
]
|
|
27
30
|
|
|
28
31
|
[project.optional-dependencies]
|
|
29
32
|
dev = ["pytest>=7.0"]
|
|
30
33
|
|
|
31
34
|
[project.urls]
|
|
32
|
-
Homepage = "https://github.com/
|
|
33
|
-
Repository = "https://github.com/
|
|
34
|
-
Issues = "https://github.com/
|
|
35
|
+
Homepage = "https://github.com/Xar1ee/hookly"
|
|
36
|
+
Repository = "https://github.com/Xar1ee/hookly"
|
|
37
|
+
Issues = "https://github.com/Xar1ee/hookly/issues"
|
|
35
38
|
|
|
36
39
|
[tool.hatch.build.targets.wheel]
|
|
37
40
|
packages = ["hookly"]
|
hookly-0.1.0/PKG-INFO
DELETED
|
@@ -1,135 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.5
|
|
2
|
-
Name: hookly
|
|
3
|
-
Version: 0.1.0
|
|
4
|
-
Summary: Batteries-included backend toolkit for Python — API, DB and CRM, ready to call.
|
|
5
|
-
Project-URL: Homepage, https://github.com/yourusername/hookly
|
|
6
|
-
Project-URL: Repository, https://github.com/yourusername/hookly
|
|
7
|
-
Project-URL: Issues, https://github.com/yourusername/hookly/issues
|
|
8
|
-
Author-email: Your Name <you@example.com>
|
|
9
|
-
License: MIT
|
|
10
|
-
Keywords: api,backend,crm,framework,sqlite,toolkit
|
|
11
|
-
Classifier: Development Status :: 3 - Alpha
|
|
12
|
-
Classifier: Intended Audience :: Developers
|
|
13
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
-
Classifier: Operating System :: OS Independent
|
|
15
|
-
Classifier: Programming Language :: Python :: 3
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
-
Classifier: Topic :: Internet :: WWW/HTTP :: WSGI :: Application
|
|
20
|
-
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
21
|
-
Requires-Python: >=3.10
|
|
22
|
-
Provides-Extra: dev
|
|
23
|
-
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
24
|
-
Description-Content-Type: text/markdown
|
|
25
|
-
|
|
26
|
-
# ⚡ Hookly
|
|
27
|
-
|
|
28
|
-
**Batteries-included backend toolkit for Python.**
|
|
29
|
-
Stop wiring up an API, a database layer and a CRM by hand — import Hookly and call it.
|
|
30
|
-
|
|
31
|
-

|
|
32
|
-

|
|
33
|
-

|
|
34
|
-
|
|
35
|
-
---
|
|
36
|
-
|
|
37
|
-
## Why Hookly
|
|
38
|
-
|
|
39
|
-
Every backend needs the same handful of things: an API layer, a place to store
|
|
40
|
-
data, and something to track contacts, leads or tickets. Hookly ships all
|
|
41
|
-
three as ready-made modules that work together out of the box — you call
|
|
42
|
-
them, you don't rebuild them.
|
|
43
|
-
|
|
44
|
-
```python
|
|
45
|
-
from hookly import Database, CRM, API
|
|
46
|
-
|
|
47
|
-
db = Database("app.db")
|
|
48
|
-
crm = CRM(db)
|
|
49
|
-
api = API("my-service")
|
|
50
|
-
|
|
51
|
-
@api.get("/contacts")
|
|
52
|
-
def list_contacts():
|
|
53
|
-
return crm.list_contacts()
|
|
54
|
-
|
|
55
|
-
@api.post("/contacts")
|
|
56
|
-
def new_contact():
|
|
57
|
-
return crm.add_contact("Ali", phone="+998901234567").__dict__
|
|
58
|
-
|
|
59
|
-
api.run(port=8000)
|
|
60
|
-
```
|
|
61
|
-
|
|
62
|
-
That's a working API + database + CRM. No boilerplate.
|
|
63
|
-
|
|
64
|
-
---
|
|
65
|
-
|
|
66
|
-
## Install
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
pip install hookly
|
|
70
|
-
```
|
|
71
|
-
|
|
72
|
-
(local dev: `pip install -e ".[dev]"` from the project root)
|
|
73
|
-
|
|
74
|
-
---
|
|
75
|
-
|
|
76
|
-
## Modules
|
|
77
|
-
|
|
78
|
-
### `hookly.db` — SQLite without the ceremony
|
|
79
|
-
|
|
80
|
-
```python
|
|
81
|
-
from hookly import Database
|
|
82
|
-
|
|
83
|
-
db = Database("app.db")
|
|
84
|
-
db.create_table("users", {"id": "INTEGER PRIMARY KEY", "name": "TEXT"})
|
|
85
|
-
db.insert("users", name="Ali")
|
|
86
|
-
db.fetchall("SELECT * FROM users")
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
### `hookly.crm` — contacts and leads, ready to go
|
|
90
|
-
|
|
91
|
-
```python
|
|
92
|
-
from hookly import CRM
|
|
93
|
-
|
|
94
|
-
crm = CRM()
|
|
95
|
-
contact = crm.add_contact("Ali", email="ali@example.com")
|
|
96
|
-
crm.update_status(contact.id, "in_progress")
|
|
97
|
-
crm.list_contacts(status="in_progress")
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### `hookly.api` — a tiny decorator-based router
|
|
101
|
-
|
|
102
|
-
```python
|
|
103
|
-
from hookly import API
|
|
104
|
-
|
|
105
|
-
api = API("my-service")
|
|
106
|
-
|
|
107
|
-
@api.get("/ping")
|
|
108
|
-
def ping():
|
|
109
|
-
return {"status": "ok"}
|
|
110
|
-
|
|
111
|
-
api.run(port=8000)
|
|
112
|
-
```
|
|
113
|
-
|
|
114
|
-
---
|
|
115
|
-
|
|
116
|
-
## Roadmap
|
|
117
|
-
|
|
118
|
-
- [ ] Async support (`AsyncDatabase`, `AsyncAPI`)
|
|
119
|
-
- [ ] Auth module (`hookly.auth`)
|
|
120
|
-
- [ ] PostgreSQL backend for `hookly.db`
|
|
121
|
-
- [ ] CLI: `hookly new my-project`
|
|
122
|
-
|
|
123
|
-
---
|
|
124
|
-
|
|
125
|
-
## Philosophy
|
|
126
|
-
|
|
127
|
-
Hookly isn't trying to replace FastAPI or Django — it's for the moment
|
|
128
|
-
before that, when you just need something working *now*: a table, an
|
|
129
|
-
endpoint, a contact list. Grow out of it, swap in the big tools later.
|
|
130
|
-
|
|
131
|
-
---
|
|
132
|
-
|
|
133
|
-
## License
|
|
134
|
-
|
|
135
|
-
MIT
|
hookly-0.1.0/README.md
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# ⚡ Hookly
|
|
2
|
-
|
|
3
|
-
**Batteries-included backend toolkit for Python.**
|
|
4
|
-
Stop wiring up an API, a database layer and a CRM by hand — import Hookly and call it.
|
|
5
|
-
|
|
6
|
-

|
|
7
|
-

|
|
8
|
-

|
|
9
|
-
|
|
10
|
-
---
|
|
11
|
-
|
|
12
|
-
## Why Hookly
|
|
13
|
-
|
|
14
|
-
Every backend needs the same handful of things: an API layer, a place to store
|
|
15
|
-
data, and something to track contacts, leads or tickets. Hookly ships all
|
|
16
|
-
three as ready-made modules that work together out of the box — you call
|
|
17
|
-
them, you don't rebuild them.
|
|
18
|
-
|
|
19
|
-
```python
|
|
20
|
-
from hookly import Database, CRM, API
|
|
21
|
-
|
|
22
|
-
db = Database("app.db")
|
|
23
|
-
crm = CRM(db)
|
|
24
|
-
api = API("my-service")
|
|
25
|
-
|
|
26
|
-
@api.get("/contacts")
|
|
27
|
-
def list_contacts():
|
|
28
|
-
return crm.list_contacts()
|
|
29
|
-
|
|
30
|
-
@api.post("/contacts")
|
|
31
|
-
def new_contact():
|
|
32
|
-
return crm.add_contact("Ali", phone="+998901234567").__dict__
|
|
33
|
-
|
|
34
|
-
api.run(port=8000)
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
That's a working API + database + CRM. No boilerplate.
|
|
38
|
-
|
|
39
|
-
---
|
|
40
|
-
|
|
41
|
-
## Install
|
|
42
|
-
|
|
43
|
-
```bash
|
|
44
|
-
pip install hookly
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
(local dev: `pip install -e ".[dev]"` from the project root)
|
|
48
|
-
|
|
49
|
-
---
|
|
50
|
-
|
|
51
|
-
## Modules
|
|
52
|
-
|
|
53
|
-
### `hookly.db` — SQLite without the ceremony
|
|
54
|
-
|
|
55
|
-
```python
|
|
56
|
-
from hookly import Database
|
|
57
|
-
|
|
58
|
-
db = Database("app.db")
|
|
59
|
-
db.create_table("users", {"id": "INTEGER PRIMARY KEY", "name": "TEXT"})
|
|
60
|
-
db.insert("users", name="Ali")
|
|
61
|
-
db.fetchall("SELECT * FROM users")
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
### `hookly.crm` — contacts and leads, ready to go
|
|
65
|
-
|
|
66
|
-
```python
|
|
67
|
-
from hookly import CRM
|
|
68
|
-
|
|
69
|
-
crm = CRM()
|
|
70
|
-
contact = crm.add_contact("Ali", email="ali@example.com")
|
|
71
|
-
crm.update_status(contact.id, "in_progress")
|
|
72
|
-
crm.list_contacts(status="in_progress")
|
|
73
|
-
```
|
|
74
|
-
|
|
75
|
-
### `hookly.api` — a tiny decorator-based router
|
|
76
|
-
|
|
77
|
-
```python
|
|
78
|
-
from hookly import API
|
|
79
|
-
|
|
80
|
-
api = API("my-service")
|
|
81
|
-
|
|
82
|
-
@api.get("/ping")
|
|
83
|
-
def ping():
|
|
84
|
-
return {"status": "ok"}
|
|
85
|
-
|
|
86
|
-
api.run(port=8000)
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
---
|
|
90
|
-
|
|
91
|
-
## Roadmap
|
|
92
|
-
|
|
93
|
-
- [ ] Async support (`AsyncDatabase`, `AsyncAPI`)
|
|
94
|
-
- [ ] Auth module (`hookly.auth`)
|
|
95
|
-
- [ ] PostgreSQL backend for `hookly.db`
|
|
96
|
-
- [ ] CLI: `hookly new my-project`
|
|
97
|
-
|
|
98
|
-
---
|
|
99
|
-
|
|
100
|
-
## Philosophy
|
|
101
|
-
|
|
102
|
-
Hookly isn't trying to replace FastAPI or Django — it's for the moment
|
|
103
|
-
before that, when you just need something working *now*: a table, an
|
|
104
|
-
endpoint, a contact list. Grow out of it, swap in the big tools later.
|
|
105
|
-
|
|
106
|
-
---
|
|
107
|
-
|
|
108
|
-
## License
|
|
109
|
-
|
|
110
|
-
MIT
|
hookly-0.1.0/hookly/__init__.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
Hookly — batteries-included backend toolkit for Python.
|
|
3
|
-
|
|
4
|
-
Instead of wiring up a DB layer, a CRM module and an API router by hand,
|
|
5
|
-
you import Hookly and call it.
|
|
6
|
-
|
|
7
|
-
from hookly import Database, CRM, API
|
|
8
|
-
|
|
9
|
-
db = Database("app.db")
|
|
10
|
-
crm = CRM(db)
|
|
11
|
-
api = API("my-service")
|
|
12
|
-
|
|
13
|
-
@api.get("/contacts")
|
|
14
|
-
def list_contacts():
|
|
15
|
-
return crm.list_contacts()
|
|
16
|
-
|
|
17
|
-
api.run(port=8000)
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
from .db import Database
|
|
21
|
-
from .crm import CRM
|
|
22
|
-
from .api import API
|
|
23
|
-
|
|
24
|
-
__version__ = "0.1.0"
|
|
25
|
-
__all__ = ["Database", "CRM", "API", "__version__"]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|