Skip to content

Commit

Permalink
CLI: Load from file or module. Add software tests and documentation.
Browse files Browse the repository at this point in the history
  • Loading branch information
amotl committed Oct 26, 2024
1 parent c6f3f50 commit e61876a
Show file tree
Hide file tree
Showing 14 changed files with 652 additions and 23 deletions.
3 changes: 3 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ jobs:
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- uses: actions/setup-node@v4
with:
node-version: 22
- uses: yezz123/setup-uv@v4

- name: Install package and run software tests (Python 3.6)
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
.pytest_cache
.DS_Store
coverage.xml
.coverage*

__pycache__
tests/__pycache__
Expand Down
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,11 +44,14 @@ Install the most recent stable release:

pip install --upgrade 'responder'

Install package including CLI interface and GraphQL extension:
Install package with CLI and GraphQL support:

pip install --upgrade 'responder[cli,graphql]'

Or, install directly from the repository:
The CLI provides commands for running and building applications, while GraphQL
adds support for GraphQL query endpoints.

Alternatively, install directly from the repository:

pip install 'responder @ git+https://github.com/kennethreitz/responder.git'

Expand Down
90 changes: 90 additions & 0 deletions docs/source/cli.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
Responder CLI
=============

Responder installs a command line program ``responder``. Use it to launch
a Responder application from a file or module.

Launch application from file
----------------------------

Acquire minimal example application, `helloworld.py`_,
implementing a basic echo handler, and launch the HTTP service.

.. code-block:: shell
wget https://github.com/kennethreitz/responder/raw/refs/heads/main/examples/helloworld.py
responder run helloworld.py
In another terminal, invoke a HTTP request, for example using `HTTPie`_.

.. code-block:: shell
http http://127.0.0.1:5042/hello
The response is no surprise.

::

HTTP/1.1 200 OK
content-length: 13
content-type: text/plain
date: Sat, 26 Oct 2024 13:16:55 GMT
encoding: utf-8
server: uvicorn

hello, world!


Launch application from module
------------------------------

If your Responder application has been implemented as a Python module,
launch it like this:

.. code-block:: shell
responder run acme.app
That assumes a Python package ``acme`` including an ``app`` module
``acme/app.py`` that includes an attribute ``api`` that refers
to a ``responder.API`` instance, reflecting the typical layout of
a standard Responder application.

.. rubric:: Non-standard instance name

When your attribute that references the ``responder.API`` instance
is called differently than ``api``, append it to the launch target
address like this:

.. code-block:: shell
responder run acme.app:service
Within your ``app.py``, the instance would have been defined like this:

.. code-block:: python
service = responder.API()
Build JavaScript application
----------------------------

The ``build`` subcommand invokes ``npm run build``, optionally accepting
a target directory. By default, it uses the current working directory,
where it expects a regular NPM ``package.json`` file.

.. code-block:: shell
responder build
When specifying a target directory, responder will change to that
directory beforehand.

.. code-block:: shell
responder build /path/to/project
.. _helloworld.py: https://github.com/kennethreitz/responder/blob/main/examples/helloworld.py
.. _HTTPie: https://httpie.io/docs/cli
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ User Guides
deployment
testing
api
cli


Installing Responder
Expand Down
2 changes: 2 additions & 0 deletions examples/helloworld.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# Example HTTP service definition, using Responder.
# https://pypi.org/project/responder/
import responder

api = responder.API()
Expand Down
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,21 @@ markers = [
]
xfail_strict = true

[tool.coverage.run]
branch = false
omit = [
"*.html",
"tests/*",
]

[tool.coverage.report]
fail_under = 0
show_missing = true
exclude_lines = [
"# pragma: no cover",
"raise NotImplemented",
]

[tool.poe.tasks]

check = [
Expand Down
7 changes: 7 additions & 0 deletions responder/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
"""
Responder - a familiar HTTP Service Framework.
This module exports the core functionality of the Responder framework,
including the API, Request, Response classes and CLI interface.
"""

from . import ext
from .core import API, Request, Response

Expand Down
70 changes: 51 additions & 19 deletions responder/cli.py
Original file line number Diff line number Diff line change
@@ -1,46 +1,78 @@
"""Responder.
"""
Responder CLI.
A web framework for Python.
Commands:
run Start the application server
build Build frontend assets using npm
Usage:
responder
responder run [--build] <module>
responder build
responder run [--debug] [--limit-max-requests=] <target>
responder build [<target>]
responder --version
Options:
-h --help Show this screen.
-v --version Show version.
--debug Enable debug mode.
--limit-max-requests=<n> Maximum number of requests to handle before shutting down.
Arguments:
<target> For run: Python module path (e.g., "app:api" loads api from app.py)
For build: Directory containing package.json (default: current directory)
Examples:
responder run app:api # Run the API from app.py
responder build # Build frontend assets
"""

import logging
import os
import subprocess
import typing as t

import docopt

from .__version__ import __version__
from .util.python import load_target

logger = logging.getLogger(__name__)

def cli():

def cli() -> None:
"""
CLI interface handler of the Responder package.
Main entry point for the Responder CLI.
Parses command line arguments and executes the appropriate command.
Supports running the application, building assets, and displaying version info.
"""
args = docopt.docopt(__doc__, argv=None, version=__version__, options_first=False)

module = args["<module>"]
build = args["build"] or args["--build"]
run = args["run"]
target: t.Optional[str] = args["<target>"]
build: bool = args["build"]
debug: bool = args["--debug"]
run: bool = args["run"]

if build:
# S603, S607 are suppressed as we're using fixed arguments, not user input
subprocess.check_call(["npm", "run", "build"]) # noqa: S603, S607
# FIXME: Windows can't find `npm`, at least not on GHA.
# Using `npm.exe` also doesn't work?
subprocess.check_call(["npm", "run", "build"], cwd=target, env=dict(os.environ)) # noqa: S603, S607

if run:
split_module = module.split(":")

if len(split_module) > 1:
module = split_module[0]
prop = split_module[1]
else:
prop = "api"
# Maximum request limit. Terminating afterward. Suitable for software testing.
limit_max_requests = args["--limit-max-requests"]
if limit_max_requests is not None:
try:
limit_max_requests = int(limit_max_requests)
if limit_max_requests <= 0:
logger.error("limit-max-requests must be a positive integer")
exit(1)
except ValueError:
logger.error("limit-max-requests must be a valid integer")
exit(1)

app = __import__(module)
getattr(app, prop).run()
# Launch Responder API server (uvicorn).
api = load_target(target=target)
api.run(debug=debug, limit_max_requests=limit_max_requests)
Empty file added responder/util/__init__.py
Empty file.
Loading

0 comments on commit e61876a

Please sign in to comment.