Bottler
A Bottle subclass that bundles REST resources, JWT auth, SQLite/MySQL access and CORS into one app, plus a CLI to scaffold and run projects and an optional admin dashboard.
The REST, JWT, SQL and CORS plugins are built into the package
(bottler.plugins). No separate bottle-rest, bottle-jwt, bottle-sql or
bottle-cors install is needed. Runtime dependencies are only bottle,
PyJWT, pymysql and toml. The plugin classes (API, Resource,
JWTPlugin, sqlitePlugin, sqlPlugin, CorsPlugin) can also be imported
from bottler.plugins and installed on a plain Bottle app.
Installation
python3 -m pip install bottler
Usage
CLI
Create Project
$ bottler-create
Enter project name. <bottler-project>:
Create a SQLite database? [y/N]:
Attach an SQL database? [y/N]:
Created project bottler-project
cd bottler-project
bottler -d -r
Run
$ cd bottler-project
$ bottler -d -r
View dashboard @ http://localhost:8000/dashboard
Bottle v0.13-dev server starting up (using WSGIRefServer(dashboard=True))...
Listening on http://localhost:8000/
Hit Ctrl-C to quit.
Options
usage: bottler [-h] [--port PORT] [--host HOST] [--dir PATH] [--jwt [JWT_KEY]]
[--sqlite [PATH]] [--dbhost DBHOST] [--dbname DBNAME]
[--dbuser DBUSER] [--dbpass DBPASS] [--cors CORS] [-r] [-d]
options:
-h, --help show this help message and exit
--port PORT Port to listen on
--host HOST Host to listen on
--dir PATH Change to this directory before starting, so bottler.toml
and resources/ are resolved relative to it
--jwt [JWT_KEY] JWT key
--sqlite [PATH] Path to SQLite database (default: <package>/cli/tmp.db)
--dbhost DBHOST SQL database host
--dbname DBNAME SQL database name
--dbuser DBUSER SQL database username
--dbpass DBPASS SQL database password
--cors CORS Enable CORS
-r Automatic reloading
-d Enable dashboard
--jwt with no value generates a random key. --sqlite with no path uses a
temporary database that is deleted on exit.
Import
app.py
from bottler import Bottler
app = Bottler()
app.run(reloader=True)
Resources
Resource Folder
Bottler will attempt to automatically find Resources objects in a resource folder in the working directory and create endpoints for them. By default it will look for a folder named "resources". All ".py" files in the folder will be scanned for Resource objects.
Example Project Structure
├── src
│ ├── resources
│ │ ├── __init__.py
│ │ ├── resource_a.py
| | ├── resource_b.py
│ ├── app.py
Resource Objects
A resource is a subclass of bottler.Resource. Each method it defines
(options, get, post, put, patch, delete) becomes a route for that
HTTP method on every path the resource is added at.
Method arguments are filled from URL parameters and the JSON request body. If
a required argument (one with no default) is missing from both, the request
fails with 400 {"<name>": "This field is required"}. Query-string values are
available through self.params.
When a SQLite or SQL database is configured, an argument named db receives a
database cursor (SQLite uses ? placeholders, MySQL uses %s):
from bottler import Resource
class ProjectTasks(Resource):
def get(self, db, project_id):
sql = "SELECT * FROM tasks WHERE project_id = ?"
return {"tasks": db.execute(sql, (project_id,)).fetchall()}
Per-method route options such as roles are set as class attributes named
after the HTTP method:
class Reports(Resource):
GET = {"roles": ["admin"]}
def get(self):
return {}
resource_a.py
from bottler import Resource
class ResourceA(Resource):
def options(self):
pass
def get(self):
return {}
def post(self):
pass
def put(self):
pass
def patch(self):
pass
def delete(self):
pass
Adding Endpoints for Resources
Bottler has a Bottle REST object. Resources can be added by accessing the Bottle REST object and calling addResource().
app.py
from resources.resource_a import ResourceA
from bottler import Bottler
app = Bottler()
app.rest.addResource(ResourceA, "/resource_a")
app.run(reloader=True)
Auto-Generated Database Resources
When a SQLite or SQL database is configured (sqlite=... / sql=..., via
the constructor or bottler.toml), Bottler introspects every
table on startup and auto-generates a full CRUD Resource for each table
that doesn't already have a route (gen_db=True by default - pass
gen_db=False to disable). Only tables with a detected primary key are
eligible.
For a table widgets with primary key id, this registers:
GET/POST /widgets- list (filterable by any?column=value, LIKE-matched) / createGET/PUT/PATCH/DELETE /widgets/<id>- fetch/replace/patch/delete a single rowGET /<ref_table>/<ref_id>/widgets- fetch rows referencing another table's row via a detected foreign key
Any auto-CRUD GET also accepts ?levels=N to inline foreign-key
references N levels deep - e.g. GET /widgets/1?levels=2 replaces a
category_id column with a nested category object.
Per-table role gating and custom paths are configured under
[resources.<table>] in bottler.toml. See example/ for a full
working demo (example/bottler.toml, example/README.md).
API Prefix
Set api_prefix (constructor kwarg or top-level bottler.toml key,
e.g. api_prefix = "/api") to prepend a fixed prefix to every route
Bottler registers as part of your project's own API — hand-authored
resources, auto-generated database resources, and the JWT /token/
/users/current endpoints. This is useful when something else (a
static-site resource, a frontend build) needs to own the unprefixed
paths. Bottler's own dashboard and meta/admin routes (/dashboard,
/openapi.json, /_resources, etc.) are never affected by api_prefix.
An individual Resource subclass can opt out of the global prefix (or use
a different one) by setting its own api_prefix class attribute:
from bottler import Resource
class ResourceA(Resource):
api_prefix = "" # always unprefixed, even if the app sets api_prefix
def get(self):
return {}
A resource with an api_prefix override — e.g. a root-mounted catch-all like /<path:path> serving a
static SPA build — is always registered after every default-prefixed route (hand-authored, built-in,
and auto-generated DB resources), so it can never shadow a real API route no matter what order its
module happens to load in.
Default Roles
Hand-authored and auto-generated database resources are open unless they
set roles. Set default_roles (constructor kwarg or top-level
bottler.toml key) to protect all of them at once:
default_roles = true # any valid token
# default_roles = ["admin", "finance"] # token must hold at least one role
A list can also be given as a comma-separated string ("admin,finance").
The default applies only to methods that declare no roles of their own.
Roles set on the resource class, or under [resources.<name>.roles], win.
An explicit false opts a method out, for example a public static-site
resource:
[resources.spa.roles]
get = false
OPTIONS never gets the default, so CORS preflight still works. The
built-in meta routes (/_resources, /_roles, ...) keep their own
roles = true and ignore default_roles. The JWT /token route is
unaffected.
Mounting Packages
One app can serve several installed packages, each under its own URL prefix
and each with its own database. List them under mounts in
bottler.toml:
jwt = "..."
api_prefix = "api"
[mounts]
trax = "trax_connector" # short form: config folder = "trax"
[mounts.bri]
package = "bri_connector" # importable package with a resources/ subpackage
base_dir = "connectors/bri" # its config folder (default: the mount name)
Each mount is a child Bottler built from <package>.resources (not
./resources), served at <api_prefix>/<name>/. Its own
<base_dir>/bottler.toml sets its sql/sqlite, resources.* paths
and roles, and default_roles; DB auto-CRUD works per mount. The child always
takes jwt, cors, rest, dashboard, openapi and api_prefix from the
parent (its own values for those are ignored), so one token works everywhere
and only the parent's /token issues one. A mount without its own
default_roles inherits the parent's.
The package's __init__.py may define configure(app). It runs last, like
hooks.py, and app.base_dir is the mount's config folder. Packages never
get an auth.py or hooks.py from the working directory.
A package can also run on its own (e.g. in its own repo for development):
Bottler(package="bri_connector", base_dir="config").
Custom Setup Hooks
If a project defines a top-level hooks.py with a callable configure(app),
Bottler calls it automatically at the very end of __init__ — after
every resource, DB table, and built-in route (including /openapi.json) is
already registered. This is the place to add anything Bottler doesn't
already cover: extra routes, additional Bottle plugins, middleware, or
overriding a route Bottler itself just set up.
hooks.py
def configure(app):
@app.route("/healthz")
def healthz():
return {"status": "ok"}
Disable auto-discovery with gen_hooks=False (constructor-only, not
bottler.toml-configurable). This works the same way whether the app
is started via the bottler CLI, a hand-rolled app.py, or a WSGI
deploy — hooks.py just needs to sit at the process's working directory
when Bottler() is constructed.
Metadata
Release files for bottler 0.0.19
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| bottler-0.0.19.tar.gz | 1.9 MB | Without provenance | Details for bottler-0.0.19.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| bottler-0.0.19-py3-none-any.whl | Python 3 | none | any | Details for bottler-0.0.19-py3-none-any.whl |
Total release size: 3.8 MB
Release files / bottler-0.0.19.tar.gz
| Download URL | bottler-0.0.19.tar.gz |
|---|---|
| Size | 1.9 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9a368d44eca1c74f1ff7224f3143db959c3bbb3e2088761ac3194485963e91f8
|
|
BLAKE2b-256 checksum How to use checksums |
2ec699ed0fe5fb72e8189a245bd0a8734c56e2f840aa19c1c68c5cbf75bdef3f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Release files / bottler-0.0.19-py3-none-any.whl
| Download URL | bottler-0.0.19-py3-none-any.whl |
|---|---|
| Size | 1.9 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
df1e39f2013704dcb64535954937a4fd8d2afee8e695d12b08073d6ebf342913
|
|
BLAKE2b-256 checksum How to use checksums |
51a77ed65940a03ef4f2763e9d6608f105ab5cb7274bc041388640ad52614853
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.3
|
Security for bottler 0.0.19
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.