Skip to main content

socketwrench

A webserver based on socket.socket with no dependencies other than the standard library. Provides a lightweight quickstart to make an API which supports OpenAPI, Swagger, and more.

Quickstart

Install

pip install socketwrench

Serve a class

from socketwrench import serve, StaticFileHandler

class MyServer:
    src = StaticFileHandler(Path(__file__).parent.parent.parent)
    
    def hello(self):
        return "world"
  
if __name__ == '__main__':
    serve(MyServer)
    # OR
    # m = MyServer()
    # serve(m)
    # OR
    # serve("my_module.MyServer")
  • Go to http://localhost:8080/hello and see the response "world".
  • Now go to http://localhost:8080/swagger to see the Swagger UI.
  • OR go to http://localhost:8080/api to see a custom api playground.

Features

OpenAPI & Swagger

  • Go to http://localhost:8080/swagger (after running serve) to see the Swagger UI.
  • Go to http://localhost:8080/openapi.json (after running serve) to see the autogenerated OpenAPI spec which Swagger uses.

Autofilled Parameters

Any of the following parameter names or typehints will get autofilled with the corresponding request data:

available_types = {
    "request": Request, # full request object, contains all the other components
    "query": Query, # query string
    "body": Body, # request body bytes
    "headers": Headers, # request headers dict[str, str]
    "route": Route, # route string (without query string)
    "full_path": FullPath, # full path string (with query string)
    "method": Method, # request method str (GET, POST, etc.)
    "file": File, # file bytes (essentially the same as body)
    "client_addr": ClientAddr, # client ip address string (also contains host and port attributes)
}

Decorators

from socketwrench import route, methods, get, post, put, patch, delete, private

These decorators do not modify the functions they decorate, they simply tag the function by adding attributes to the functions. func.__dict__[key] = value. This allows the setting function-specific preferences such as which methods to allow.

@tag

simply modifies the function's __dict__ to add the specified attributes.

@tag(do_not_serve=False, methods=["GET", "POST"], error_mode="traceback")
def my_function():
    pass

The following decorators set the available_methods attribute of the function to the specified methods and tells the server to override its default behavior for the function.

  • @methods("GET", "POST", "DELETE"): equivalent to @tag(available_methods=["GET", "POST", "DELETE"])
  • @get, @post, @put, @patch, @delete, @private: self-explanatory

Route Decorator

@route("/a/{c}") tells the server to use /a/{c} as the route for the function instead of using the function's name as it normally does. This also allows for capturing path parameters.

@get
@post
@route("/a/{c}", error_mode="traceback")
def a(self, b, c=5):
    print(f"calling a with {b=}, {c=}")
    return f"captured {b=}, {c=}"

Error Modes

  • "hide" or ErrorModes.HIDE: returns b"Internal Server Error" in the response body when an error occurs.
  • type or ErrorModes.TYPE: returns the error type only in the response body when an error occurs.
  • "short" or ErrorModes.SHORT: returns the python error message but no traceback in the response body when an error occurs.
  • "traceback" or ErrorModes.TRACEBACK or ErrorModes.LONG or ErrorModes.TB: returns the full traceback in the response body when an error occurs.

favicon.ico

No need to use our favicon! pass a str | Path .ico filepath to favicon argument to use your own favicon. Alternatively, tag @route('/favicon.ico') on a function returning the path.

fallback handler

Add a custom function to handle any requests that don't match any other routes.

Planned Features

  • Implement nesting / recursion to serve deeper routes and/or multiple classes
  • Enforce OpenAPI spec with better error responses
  • Serve static folders
  • Make a better playground for testing endpoints
  • Make a client-side python proxy object to make API requests from python
  • Test on ESP32 and other microcontrollers
  • Ideas? Let me know!

Other Usage Modes

Serve a module

Using commandline, just specify a filepath or file import path to a module.

# my_module.py
def hello():
    return "world"
python -m socketwrench my_module

NOTE: this mode is experimental and less tested than the other modes.

Serve a single function on all routes

from socketwrench import serve

def print_request(request):
    s = "<h>You made the following request:</h><br/>"
    s += f"<b>Method:</b> {request.method}<br/>"
    s += f"<b>Route:</b> {request.path.route()}<br/>"
    s += f"<b>Headers:</b><br/> {str(request.headers).replace('\n', '<br>')}<br/>"
    s += f"<b>Query:</b> {request.path.query_args()}<br/>"
    s += f"<b>Body:</b> {request.body}<br/>"
    return s


if __name__ == '__main__':
    serve(print_request)

(mostly) Full Feature Sample

import logging
from pathlib import Path

from socketwrench.tags import private, post, put, patch, delete, route, methods

logging.basicConfig(level=logging.DEBUG)


class Sample:
    def hello(self):
        """A simple hello world function."""
        return "world"

    @methods("GET", "POST")  # do to the label, this will be accessible by both GET and POST requests
    def hello2(self, method):
        """A simple hello world function."""
        return "world"

    def _unserved(self):
        """This function will not be served."""
        return "this will not be served"

    @private
    def unserved(self):
        """This function will not be served."""
        return "this will not be served"

    @post
    def post(self, name):
        """This function will only be served by POST requests."""
        return f"hello {name}"

    @put
    def put(self, name):
        """This function will only be served by PUT requests."""
        return f"hello {name}"

    @patch
    def patch(self, name):
        """This function will only be served by PATCH requests."""
        return f"hello {name}"

    @delete
    def delete(self, name):
        """This function will only be served by DELETE requests."""
        return f"hello {name}"

    def echo(self, *args, **kwargs):
        """Echos back any query or body parameters."""
        if not args and not kwargs:
            return
        if args:
            if len(args) == 1:
                return args[0]
            return args
        elif kwargs:
            return kwargs
        return args, kwargs

    def string(self) -> str:
        """Returns a string response."""
        return "this is a string"

    def html(self) -> str:
        """Returns an HTML response."""
        return "<h1>hello world</h1><br><p>this is a paragraph</p>"

    def json(self) -> dict:
        """Returns a JSON response."""
        return {"x": 6, "y": 7}

    def file(self) -> Path:
        """Returns sample.py as a file response."""
        return Path(__file__)

    def add(self, x: int, y: int):
        """Adds two numbers together."""
        return x + y

    def client_addr(self, client_addr):
        """Returns the client address."""
        return client_addr

    def headers(self, headers) -> dict:
        """Returns the request headers."""
        return headers

    def query(self, query, *args, **kwargs) -> str:
        """Returns the query string."""
        return query

    def body(self, body) -> bytes:
        """Returns the request body."""
        return body

    def method(self, method) -> str:
        """Returns the method."""
        return method

    def get_route(self, route) -> str:
        """Returns the route."""
        return route

    def request(self, request) -> dict:
        """Returns the request object."""
        return request

    def everything(self, request, client_addr, headers, query, body, method, route, full_path):
        d = {
            "request": request,
            "client_addr": client_addr,
            "headers": headers,
            "query": query,
            "body": body,
            "method": method,
            "route": route,
            "full_path": full_path,
        }
        for k, v in d.items():
            print(k, v)
        return d

    @route("/a/{c}", error_mode="traceback")
    def a(self, b, c=5):
        print(f"calling a with {b=}, {c=}")
        return f"captured {b=}, {c=}"


if __name__ == '__main__':
    from socketwrench import serve
    s = Sample()
    serve(s)
    # OR
    # serve(Sample)
    # OR
    # serve("socketwrench.samples.sample.Sample")

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

socketwrench-1.7.0.tar.gz (25.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

socketwrench-1.7.0-py3-none-any.whl (25.5 kB view details)

Uploaded Python 3

File details

Details for the file socketwrench-1.7.0.tar.gz.

File metadata

  • Download URL: socketwrench-1.7.0.tar.gz
  • Upload date:
  • Size: 25.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.9.19

File hashes

Hashes for socketwrench-1.7.0.tar.gz
Algorithm Hash digest
SHA256 2ac6d5bbe703240f2c3bc0966bc1ba44645b88a4df510d2f3624b156a071a755
MD5 7b4b83a340280bb8a52bddaf43d11cc9
BLAKE2b-256 2bd15da489a110ab548e8353214649e2ef2dba323da700a79617b5267130a66f

See more details on using hashes here.

File details

Details for the file socketwrench-1.7.0-py3-none-any.whl.

File metadata

  • Download URL: socketwrench-1.7.0-py3-none-any.whl
  • Upload date:
  • Size: 25.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/5.0.0 CPython/3.9.19

File hashes

Hashes for socketwrench-1.7.0-py3-none-any.whl
Algorithm Hash digest
SHA256 edde15da225ec9fb3b659179af200ec99e8329e90d5f396d4eac1db5baa20a4f
MD5 ba9da8ebd92d53772c08226cf49164f8
BLAKE2b-256 9bb1232f03bf1d44f3f05105c793b93fe0f655b0c9a972c97be3b98058bc6ca6

See more details on using hashes here.

Release history Release notifications | RSS feed

2.3.1

2 files

2.3.0

2 files

2.2.10

2 files

2.2.9

2 files

2.2.8

2 files

2.2.7

2 files

2.2.6

2 files

2.2.5

2 files

2.2.4

2 files

2.2.3

2 files

2.2.2

2 files

2.2.1

2 files

2.2.0

2 files

2.1.2

2 files

2.1.1

2 files

2.1.0

2 files

2.0.4

2 files

2.0.3

2 files

2.0.1

2 files

2.0.0

2 files

1.9.3

2 files

1.9.2

2 files

1.9.1

2 files

1.9.0

2 files

1.8.9

2 files

1.8.8

2 files

1.8.7

2 files

1.8.6

2 files

1.8.5

2 files

1.8.4

2 files

1.8.3

2 files

1.8.2

2 files

1.8.1

2 files

1.8.0

2 files

This release

1.7.0 This release

2 files

1.6.0

2 files

1.4.0

2 files

1.3.0

2 files

1.2.0

2 files

1.1.1

2 files

0.2.1

2 files

0.1.0

2 files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page