Giter VIP home page Giter VIP logo

adafruit_circuitpython_wsgi's Introduction

Introduction

Documentation Status Discord Build Status Code Style: Black

CircuitPython framework for creating WSGI compatible web server applications.

Dependencies

This driver depends on:

Please ensure all dependencies are available on the CircuitPython filesystem. This is easily achieved by downloading the Adafruit library and driver bundle.

Installing from PyPI

Note

This library is not available on PyPI yet. Install documentation is included as a standard element. Stay tuned for PyPI availability!

On supported GNU/Linux systems like the Raspberry Pi, you can install the driver locally from PyPI. To install for current user:

pip3 install adafruit-circuitpython-wsgi

To install system-wide (this may be required in some cases):

sudo pip3 install adafruit-circuitpython-wsgi

To install in a virtual environment in your current project:

mkdir project-name && cd project-name
python3 -m venv .venv
source .venv/bin/activate
pip3 install adafruit-circuitpython-wsgi

Documentation

API documentation for this library can be found on Read the Docs.

For information on building library documentation, please check out this guide.

Contributing

Contributions are welcome! Please read our Code of Conduct before contributing to help this project stay welcoming.

Sphinx documentation

Sphinx is used to build the documentation based on rST files and comments in the code. First, install dependencies (feel free to reuse the virtual environment from above):

python3 -m venv .venv
source .venv/bin/activate
pip install Sphinx sphinx-rtd-theme

Now, once you have the virtual environment activated:

cd docs
sphinx-build -E -W -b html . _build/html

This will output the documentation to docs/_build/html. Open the index.html in your browser to view them. It will also (due to -W) error out on any warning like Travis will. This is a good way to locally verify it will pass.

adafruit_circuitpython_wsgi's People

Contributors

dannystaple avatar dbisu avatar dhalbert avatar evaherrada avatar foamyguy avatar kattni avatar ladyada avatar mscosti avatar rhooper avatar siddacious avatar sommersoft avatar tannewt avatar tekktrik avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

adafruit_circuitpython_wsgi's Issues

Exception handling tries to use function.__name__

While testing AirLift code, I must have triggered the RuntimeError in WSGIApp.__call__. Which lead to this error:

Traceback (most recent call last):
  File "code.py", line 1, in <module>
  File "counting.py", line 23, in <module>
  File "robot_wifi.py", line 52, in start_wifi_server
  File "adafruit_esp32spi/adafruit_esp32spi_wsgiserver.py", line 105, in update_poll
  File "adafruit_wsgi/wsgi_app.py", line 74, in __call__
AttributeError: 'function' object has no attribute '__name__'

CircuitPython does not implement the function.__name__ magic. The following demonstrates this:

Adafruit CircuitPython 7.3.0-beta.2 on 2022-04-27; Raspberry Pi Pico with rp2040
>>> def a():
...     return 1
...
...
>>> a.__name__
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
AttributeError: 'function' object has no attribute '__name__'

This is different from CPython:

Python 3.7.4 (v3.7.4:e09359112e, Jul  8 2019, 14:54:52) 
[Clang 6.0 (clang-600.0.57)] on darwin
Type "help", "copyright", "credits" or "license" for more information.
>>> def a():
...   return 1
... 
>>> a.__name__
'a'

The current exception handler looks like:

                raise RuntimeError(
                    "Proper HTTP response return not given for request handler '{}'".format(
                        route["func"].__name__
                    )
                ) from err

I recommend maybe a getattr with a default?

>>> getattr('a', '__name__', '')
''

Which means where it is available it will format in the name, otherwise it's empty.

Allow CORS by handling OPTIONS requests

When developing a web application connecting to CircuitPython_WSGI, it's practical to have the web server on the host, and make query to Adafruit_CircuitPython_WSGI from the web application.

In such a scenario, non GET requests are forbidden at the browser level if we don't handle CORS and OPTIONS requests.

I've made a few modifications to handle it unconditionally (allowing all types of requests from any host). Is there any security consideration not to do this ?

I'll post a PR for this shortly.

Add in support for static asset file serving

A very common use case for a web server is to be able to respond with static asset (files). This is primarily so that if a web browser makes an http request to your webserver, you can respond with a .html file which contains markup for a user interface. This .html file may also include references to other files, namely .js or .css, for front end javascript code and styling.

A common way to achieve this in other frameworks is to specify a particular folder on the server's filesystem, and expose white listed parts or all of its contents. I.e, If a http request comes in and matches the relative path of a file in that folder, then send back an 200 with the contents of that file.

This means that you wouldn't have to manually add a particular route for every static asset you want to serve, and less boiler plate code to read and transmit the asset.


Using Flask as inspiration

Need at minimum

  • WSGIApp class accepts a param called static_folder , that is the path to a folder on the local filesystem that should be served.
  • WSGIApp class accepts a param called static_url_path, that is what the web app base path would be for serving files from static_folder. If not provided, default to serving with the same base path of static_folder
  • expose a utility method for responding with a static folder that looks something like
    def send_from_directory(directory, filepath) -> return valid WSGI response, for situations where you need to respond with a file but need to do it from your own route handler (simple example, serve this OR that file depending on some dynamic criteria)
    • Like flask documents, only send_from_directory should be used if there is ANY client provided data being directly used, so that you do not permit read access to the entire filesystem; it must a filepath within a known directory.
    • Do not expose a 'send_file' method for above reason, easy to shoot yourself in the foot.

Questions

  • Do we default to automatically serving a folder named static at path /static/ ?
    • Most frameworks do this, but its worth posing the question
  • Do we need some blacklisting regex that we apply be default?
    • ex, disallow for serving .py or specifically secrets.py
    • How do you opt out?
  • Do we allow for configurable white listing or default white listing?
    • ex. Only serve files in /static that are .html, .css, .js, photo types, etc ?
    • Might not be necessary, as /static/ is meant for purposefully adding files you want to serve, and would not usually have a file in there you do not want public.
    • Would need appropriate warning documentation that this is the case.

Design goals and milestones

This is a rough summary of the discussion about Adafruit_CircuitPython_WSGI that I had with @brentru and @mscosti. Any input from the community is welcome and appreciated.

Design Goals:
A flask or sinatra like syntax for defining routes
Pico web is an example to follow though our lib should be more hardware independent than picoweb. It's also worth looking at Werkzeug which is the WSGI engine that flask wraps.

https://palletsprojects.com/p/flask/
https://palletsprojects.com/p/werkzeug/
https://github.com/pfalcon/picoweb

Template support with jinja or similar. Simple one pass substitution to start. Possibly look at cookiecutter for ideas
https://palletsprojects.com/p/jinja/

Security:
Limit the access to the hardware that the webserver has, if possible. It would be great if a user couldn't brick the device with a well-crafted POST

Requests lib from esp32spi is a good example of porting a native module to circuitpython
It is based on python’s requests module

Designs spaces it should ideally supportWYSIWYG tool for creating and exporting UI elements, using the AIO dashboard editor as an example.

Sudo OTA related things
= changing code via drop from web

Questions:
Do we want the user to be responsible for calling the poller?

  • perhaps do both; an automated poller and a manual one

This may be useful down the line:
https://palletsprojects.com/p/markupsafe/

Missing Type Annotations

There are missing type annotations for some functions in this library.

The typing module does not exist on CircuitPython devices so the import needs to be wrapped in try/except to catch the error for missing import. There is an example of how that is done here:

try:
    from typing import List, Tuple
except ImportError:
    pass

Once imported the typing annotations for the argument type(s), and return type(s) can be added to the function signature. Here is an example of a function that has had this done already:

def wrap_text_to_pixels(
    string: str, max_width: int, font=None, indent0: str = "", indent1: str = ""
) -> List[str]:

If you are new to Git or Github we have a guide about contributing to our projects here: https://learn.adafruit.com/contribute-to-circuitpython-with-git-and-github

There is also a guide that covers our CI utilities and how to run them locally to ensure they will pass in Github Actions here: https://learn.adafruit.com/creating-and-sharing-a-circuitpython-library/check-your-code In particular the pages: Sharing docs on ReadTheDocs and Check your code with pre-commit contain the tools to install and commands to run locally to run the checks.

If you are attempting to resolve this issue and need help, you can post a comment on this issue and tag both @FoamyGuy and @kattni or reach out to us on Discord: https://adafru.it/discord in the #circuitpython-dev channel.

The following locations are reported by mypy to be missing type annotations:

  • adafruit_wsgi/wsgi_app.py:46
  • adafruit_wsgi/wsgi_app.py:69
  • adafruit_wsgi/wsgi_app.py:98
  • adafruit_wsgi/wsgi_app.py:107
  • adafruit_wsgi/request.py:21
  • adafruit_wsgi/request.py:74
  • adafruit_wsgi/request.py:84

Does this library support Arduino Nano RP2040?

Does this library support Arduino Nano RP2040? I see from the source and document saying that this library is for ESP32. Or does it have the Arduino Nano RP2040/Raspberry Pi Pico version?

NoneType is not iterable (finish_response)

Can anyone tell me what could cause this error?
Traceback (most recent call last): File "code.py", line 107, in <module> File "adafruit_esp32spi/adafruit_esp32spi_wsgiserver.py", line 106, in update_poll File "adafruit_esp32spi/adafruit_esp32spi_wsgiserver.py", line 122, in finish_response TypeError: 'NoneType' object is not iterable

Improve handling for POST parameters

I saw this example code for handling POST requests in another issue:

@web_app.route("/led_on", ["POST"])
def led_on(request):
    print("led on!")
    r = request.query_params["r"]
    g = request.query_params["g"]
    b = request.query_params["b"]
    status_light.fill((int(r), int(g), int(b)))
    return ("200 OK", [], [])

It looks pretty straightforward, but when I tried it, it doesn't work. query_params only has the parameters from the query string; the POST parameters are in the request body as one might expect.

My working code looks like this:

@web_app.route("/led_on", ["POST"])
def led_on(request):
    print("led on!")
    if request.method == "POST":
        post_params = request.__parse_query_params(request.body.getvalue())
        request.body.close()
        r = post_params.get('r')
        g = post_params.get('g')
        b = post_params.get('b')
        status_light.fill((int(r), int(g), int(b)))
    return ("200 OK", [], [])

I can reuse the __parse_query_params() logic, but it's a little less intuitive than the first example would suggest. Perhaps this could be improved? Should query_params contain request body parameters for POST requests? Maybe it should be a different collection, or only parse the request body on demand.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.