Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ basic_auth = BasicAuth()
@input(BookCreateSchema, location="form")
@output(BookSchema, status_code=201)
@expect({401: "Unauthorized", 400: "Invalid file format"})
@db.transaction
async def create_book(req, resp, *, data):
"""
Create a new Book
Expand All @@ -35,10 +34,11 @@ async def create_book(req, resp, *, data):
"""

image = data.pop("image")
await image.asave(f"uploads/{image.filename}") # image is already validated for extension and size.

await image.asave(f"uploads/{image.filename}") # image is already validated for extension, size and filename.

session = await req.db
book = await Book.create(**data, cover=image.filename)
await session.commit()

resp.obj = book

Expand Down
296 changes: 292 additions & 4 deletions docs/tour.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1189,8 +1189,10 @@ Create a record:

@app.route("/users", methods=["POST"])
async def create_user(req, resp):
await req.db
session = await req.db
user = await User.create(name="Dyne User")
await session.commit()

resp.media = {"id": user.id}

Update a record:
Expand Down Expand Up @@ -1782,7 +1784,6 @@ Use this form when you want **full control** over both the response schema and i
pass



Webhooks
--------

Expand Down Expand Up @@ -2419,7 +2420,7 @@ To provide a title and description for your API, assign a docstring or a configu

description = """
User Management API
-------------------

This API allows for comprehensive management of users and books.

**Base URL:** `https://api.example.com/v1`
Expand Down Expand Up @@ -2487,7 +2488,7 @@ This example demonstrates how the Marshmallow strategy captures a complex schema

description = """
User Management API
-------------------

This API allows for comprehensive management of users and books.

**Base URL:** `https://api.example.com/v1`
Expand Down Expand Up @@ -2748,6 +2749,293 @@ It is important to distinguish between ``req.app.state`` and ``req.state``.
you are unsure if a value exists.



Dyne CLI
--------

The Dyne CLI provides a simple interface for running Dyne applications
using Uvicorn as the ASGI server.

It supports application discovery, debug mode, automatic reload,
and environment-based configuration.

Installation
^^^^^^^^^^^^

The CLI is installed automatically when installing Dyne:

.. code-block:: bash

pip install dyne

Usage
^^^^^

Basic usage:

.. code-block:: bash

dyne run

Specify an application explicitly:

.. code-block:: bash

dyne --app myapp:app run

If ``:app`` is omitted, Dyne automatically appends it:

.. code-block:: bash

dyne --app myapp run

This resolves internally to:

.. code-block:: text

myapp:app

Command Structure
^^^^^^^^^^^^^^^^^

The CLI is built using Click and supports global options followed by commands:

.. code-block:: text

dyne [OPTIONS] COMMAND

Available Commands
^^^^^^^^^^^^^^^^^^

run
"""

Runs the Dyne application using Uvicorn.

.. code-block:: bash

dyne run

Global Options
^^^^^^^^^^^^^^

--app, -a
"""""""""

Specify the Dyne application import path.

- Format: ``module:variable``
- Example: ``myproject.main:app``
- Defaults to the ``DYNE_APP`` environment variable.
- If not provided, defaults to ``app:app``.

Example:

.. code-block:: bash

dyne --app myproject.main:app run

--debug
"""""""

Enable debug mode.

When enabled:

- Log level is set to ``debug``
- Auto-reload is enabled (unless explicitly overridden)
- ``DEBUG=true`` is added to the environment

Example:

.. code-block:: bash

dyne --debug run

--host
""""""

Interface to bind the server to.

Default:

.. code-block:: text

127.0.0.1

Example:

.. code-block:: bash

dyne --host 0.0.0.0 run

--port
""""""

Port to bind the server to.

Default:

.. code-block:: text

8000

Example:

.. code-block:: bash

dyne --port 9000 run

--reload / --no-reload
""""""""""""""""""""""

Force enable or disable auto-reload.

If not explicitly set:

- Reload defaults to the value of ``--debug``.

Examples:

.. code-block:: bash

dyne --reload run

dyne --no-reload run

--version
"""""""""

Display the installed Dyne version.

.. code-block:: bash

dyne --version

Environment Variables
^^^^^^^^^^^^^^^^^^^^^

DYNE_APP
""""""""

Defines the default application import path.

Example:

.. code-block:: bash

export DYNE_APP=myproject.main:app
dyne run

DEBUG
"""""

Automatically set to ``true`` when ``--debug`` is enabled.

Implementation Notes
^^^^^^^^^^^^^^^^^^^^

- The current working directory is automatically added to ``sys.path``
to ensure local imports resolve correctly.
- Uvicorn is used as the ASGI server.
- Log level automatically switches between ``info`` and ``debug``.

Examples
^^^^^^^^

Run default app:

.. code-block:: bash

dyne run

Run with debug and reload:

.. code-block:: bash

dyne --debug run

Run custom app on all interfaces:

.. code-block:: bash

dyne --app main:app --host 0.0.0.0 --port 8080 run


Extending the Dyne CLI
----------------------

Dyne's CLI is fully extensible. Third-party packages, plugins, or
internal tools can register new commands by importing the ``cli``
group and attaching commands to it.

This allows developers to "hook" into Dyne’s CLI without modifying
Dyne’s core source code.

Basic Example
^^^^^^^^^^^^^

A plugin can register a new command like this:

.. code-block:: python

from dyne.cli import cli

@cli.command()
def custom_task():
"""A task added by a plugin."""
print("Doing something cool!")

Once this module is imported, the new command becomes available:

.. code-block:: bash

dyne custom-task


Plugin Design Pattern
^^^^^^^^^^^^^^^^^^^^^

A common structure for CLI plugins:

.. code-block:: text

myplugin/
__init__.py
cli.py

Inside ``cli.py``:

.. code-block:: python

from dyne.cli import cli

@cli.command()
def seed_data():
"""Seed the database."""
...

Then ensure the plugin module is imported during application startup.

Automatic CLI Plugin Loading
^^^^^^^^^^^^^^^^^^^^^^^^^^^^

For larger ecosystems, plugins can be automatically discovered
using Python entry points. This enables zero-configuration CLI
extensions.

Example ``pyproject.toml`` entry point:

.. code-block:: toml

[project.entry-points."dyne.cli"]
myplugin = "myplugin.cli"

Dyne can then iterate through registered entry points and import them
at startup.



Using Requests Test Client
--------------------------

Expand Down
6 changes: 4 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ authors = [{ name = "Tabot Kevin", email = "tabot.kevin@gmail.com" }]

dependencies = [
# Core ASGI / server
"starlette>=0.37.2",
"starlette>=0.37.2,<=0.52.1",
"uvicorn[standard]>=0.29.0",
# Async file handling
"aiofiles>=23.2.1",
Expand All @@ -26,8 +26,8 @@ dependencies = [
"whitenoise>=6.6.0",
"itsdangerous>=2.1.2",
"chardet>=5.2.0",
"docopt>=0.6.2",
"httpx>=0.28.1",
"click>=8.3.1",
]

classifiers = [
Expand Down Expand Up @@ -106,6 +106,8 @@ allow-direct-references = true
[tool.hatch.build.targets.wheel]
packages = ["src/dyne"]

[project.scripts]
dyne = "dyne.cli:cli"

# Rye configuration
[tool.rye]
Expand Down
5 changes: 2 additions & 3 deletions requirements-dev.lock
Original file line number Diff line number Diff line change
Expand Up @@ -36,12 +36,11 @@ chardet==5.2.0
# via dyne
charset-normalizer==3.4.4
# via requests
click==8.1.7
click==8.3.1
# via dyne
# via uvicorn
coverage==7.4.4
# via pytest-cov
docopt==0.6.2
# via dyne
exceptiongroup==1.3.1
# via anyio
# via pytest
Expand Down
Loading