-
-
Notifications
You must be signed in to change notification settings - Fork 219
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
CLI: Load from file or module. Add software tests and documentation.
Also, refactor to `responder.ext.cli`.
- Loading branch information
Showing
16 changed files
with
924 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,6 +7,7 @@ | |
.pytest_cache | ||
.DS_Store | ||
coverage.xml | ||
.coverage* | ||
|
||
__pycache__ | ||
tests/__pycache__ | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -100,6 +100,7 @@ User Guides | |
deployment | ||
testing | ||
api | ||
cli | ||
|
||
|
||
Installing Responder | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
""" | ||
Responder CLI. | ||
A web framework for Python. | ||
Commands: | ||
run Start the application server | ||
build Build frontend assets using npm | ||
Usage: | ||
responder | ||
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 with verbose logging. | ||
--limit-max-requests=<n> Maximum number of requests to handle before shutting down. | ||
Arguments: | ||
<target> For run: Python module specifier (e.g., "app:api" loads api from app.py) | ||
Format: "module.submodule:variable_name" where variable_name is your API instance | ||
For build: Directory containing package.json (default: current directory) | ||
Examples: | ||
responder run app:api # Run the 'api' instance from app.py | ||
responder run myapp/core.py:application # Run the 'application' instance from myapp/core.py | ||
responder build # Build frontend assets | ||
""" # noqa: E501 | ||
|
||
import logging | ||
import platform | ||
import subprocess | ||
import sys | ||
import typing as t | ||
from pathlib import Path | ||
|
||
import docopt | ||
|
||
from responder.__version__ import __version__ | ||
from responder.util.python import InvalidTarget, load_target | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
def cli() -> None: | ||
""" | ||
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) | ||
setup_logging(args["--debug"]) | ||
|
||
target: t.Optional[str] = args["<target>"] | ||
build: bool = args["build"] | ||
debug: bool = args["--debug"] | ||
run: bool = args["run"] | ||
|
||
if build: | ||
target_path = Path(target).resolve() if target else Path.cwd() | ||
if not target_path.is_dir() or not (target_path / "package.json").exists(): | ||
logger.error( | ||
f"Invalid target directory or missing package.json: {target_path}" | ||
) | ||
sys.exit(1) | ||
npm_cmd = "npm.cmd" if platform.system() == "Windows" else "npm" | ||
try: | ||
# # S603, S607 are addressed by validating the target directory. | ||
subprocess.check_call( # noqa: S603, S607 | ||
[npm_cmd, "run", "build"], | ||
cwd=target_path, | ||
timeout=300, | ||
) | ||
except FileNotFoundError: | ||
logger.error("npm not found. Please install Node.js and npm.") | ||
sys.exit(1) | ||
except subprocess.CalledProcessError as e: | ||
logger.error(f"Build failed with exit code {e.returncode}") | ||
sys.exit(1) | ||
|
||
if run: | ||
if not target: | ||
logger.error("Target argument is required for run command") | ||
sys.exit(1) | ||
|
||
# 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") | ||
sys.exit(1) | ||
except ValueError: | ||
logger.error("limit-max-requests must be a valid integer") | ||
sys.exit(1) | ||
|
||
# Load application from target. | ||
try: | ||
api = load_target(target=target) | ||
except InvalidTarget as ex: | ||
raise ValueError( | ||
f"{ex}. " | ||
"Use either a Python module entrypoint specification, " | ||
"a filesystem path, or a remote URL. " | ||
"See also https://responder.kennethreitz.org/cli.html." | ||
) from ex | ||
|
||
# Launch Responder API server (uvicorn). | ||
api.run(debug=debug, limit_max_requests=limit_max_requests) | ||
|
||
|
||
def setup_logging(debug: bool) -> None: | ||
""" | ||
Configure logging based on debug mode. | ||
Args: | ||
debug: When True, sets logging level to DEBUG; otherwise, sets to INFO | ||
""" | ||
log_level = logging.DEBUG if debug else logging.INFO | ||
logging.basicConfig( | ||
level=log_level, format="%(asctime)s - %(name)s - %(levelname)s - %(message)s" | ||
) |
Empty file.
Oops, something went wrong.