Full Code of pfalcon/picoweb for AI

master b74428ebdde9 cached
27 files
44.0 KB
11.7k tokens
65 symbols
1 requests
Download .txt
Repository: pfalcon/picoweb
Branch: master
Commit: b74428ebdde9
Files: 27
Total size: 44.0 KB

Directory structure:
gitextract_h09r58tg/

├── LICENSE
├── Makefile
├── README.rst
├── example_webapp.py
├── example_webapp2.py
├── examples/
│   ├── README.md
│   ├── app1.py
│   ├── app2.py
│   ├── example_app_router.py
│   ├── example_basic_auth.py
│   ├── example_basic_auth_deco.py
│   ├── example_eventsource.py
│   ├── example_eventsource_push.py
│   ├── example_extra_headers.py
│   ├── example_form.py
│   ├── example_global_exc.py
│   ├── example_header_modes.py
│   ├── example_img.py
│   ├── example_unicode.py
│   ├── static/
│   │   └── style.css
│   └── templates/
│       └── unicode.tpl
├── picoweb/
│   ├── __init__.py
│   └── utils.py
├── requirements-cpython.txt
├── sdist_upip.py
├── setup.py
└── templates/
    └── squares.tpl

================================================
FILE CONTENTS
================================================

================================================
FILE: LICENSE
================================================
The MIT License (MIT)

Copyright (c) 2014-2018 Paul Sokolovsky and contributors

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.


================================================
FILE: Makefile
================================================
PREFIX = ~/.micropython/lib

all:

install:
	cp -a picoweb $(PREFIX)


================================================
FILE: README.rst
================================================
picoweb
=======

picoweb is a "micro" web micro-framework (thus, "pico-framework") for
radically unbloated web applications using radically unbloated Python
implementation, Pycopy, https://github.com/pfalcon/pycopy .

Features:

* Asynchronous from the start, using unbloated asyncio-like library
  for Pycopy (`uasyncio <https://github.com/pfalcon/pycopy-lib/tree/master/uasyncio>`_).
  This means that ``picoweb`` can process multiple concurrent requests
  at the same time (using I/O and/or CPU multiplexing).
* Small memory usage. Initial version required about 64K of heap for
  a trivial web app, and since then, it was optimized to allow run
  more or less realistic web app in ~36K of heap. More optimizations
  on all the levels (Pycopy and up) are planned (but may lead to
  API changes).
* Has API affinity with some well-known Python web micro-framework(s),
  thus it should be an easy start if you have experience with them, and
  existing applications can be potentially ported, instead of requiring
  complete rewrite.


Requirements and optional modules
---------------------------------

``picoweb`` depends on ``uasyncio`` for asynchronous networking
(https://github.com/pfalcon/pycopy-lib/tree/master/uasyncio).
``uasyncio`` itself requires `Pycopy <https://github.com/pfalcon/pycopy>`,
a minimalist, lightweight, and resource-efficient Python language
implementation.

It is also indended to be used with ``utemplate``
(https://github.com/pfalcon/utemplate) for templating, but this is
a "soft" dependency - picoweb offers convenience functions to use
``utemplate`` templates, but if you don't use them or will handle
templating in your app (e.g. with a different library), it won't be
imported.

For database access, there are following options (``picoweb`` does
not depend on any of them, up to your application to choose):

* `btree <https://pycopy.readthedocs.io/en/latest/library/btree.html>`_
  builtin Pycopy module. This is a recommended way to do a database
  storage for `picoweb`, as it allows portability across all Pycopy
  targets, starting with very memory- and storage-limited baremetal systems.
* ``btreedb`` wrapper on top of ``btree`` builtin module. This may add some
  overhead, but may allow to make an application portable between different
  database backends (`filedb` and `uorm` below).
  https://github.com/pfalcon/pycopy-btreedb
* ``filedb``, for a simple database using files in a filesystem
  https://github.com/pfalcon/filedb
* ``uorm``, for Sqlite3 database access (works only with Pycopy
  Unix port) https://github.com/pfalcon/uorm

Last but not least, ``picoweb`` uses a standard ``logging``-compatible
logger for diagnostic output (like a connection opened, errors and debug
information). However this output is optional, and otherwise you can use
a custom logging class instead of the standard ``logging``/``ulogging``
module. Due to this, and to not put additional dependencies burden on
the small webapps for small systems, ``logging`` module is not included
in ``picoweb``'s installation dependencies. Instead, a particular app
using ``picoweb`` should depend on ``pycopy-ulogging`` or
``pycopy-logging`` package. Note that to disable use of logging,
an application should start up using ``WebApp.run(debug=-1)``. The
default value for ``debug`` parameter is 0 however, in which case
picoweb will use ``ulogging`` module (on which your application needs
to depend, again).


Details
-------

picoweb API is roughly based on APIs of other well-known Python web
frameworks. The strongest affinity is Flask, http://flask.pocoo.org, as
arguably the most popular micro-framework. Some features are also based on
Bottle and Django. Note that this does not mean particular "compatibility"
with Flask, Bottle, or Django: most existing web frameworks are synchronous
(and threaded), while picoweb is async framework, so its architecture is
quite different. However, there is an aim to save porting efforts from
repetitive search & replace trials: for example, when methods do similar
things, they are likely named the same (but they may take slightly different
parameters, return different values, and behave slightly differently).

The biggest difference is async, non-threaded nature of picoweb. That means
that the same code may handle multiple requests at the same time, but unlike
threaded environment, there's no external context (like thread and thread
local storage) to associate with each request. Thus, there're no "global"
(or thread-local "global") request and response objects, like Flask,
Bottle, Django have. Instead, all picoweb functions explicitly pass the
current request and response objects around.

Also, picoweb, being unbloated framework, tries to avoid avoidable
abstractions. For example, HTTP at the lowest level has just read and write
endpoints of a socket. To dispatch request, picoweb needs to pre-parse
some request data from input stream, and it saves that partially (sic!)
parsed data as a "request" object, and that's what passed to application
handlers. However, there's no unavoidable need to have a "response"
abstraction - the most efficient/lightweight application may want to
just write raw HTTP status line, headers, and body to the socket. Thus,
raw write stream is passed to application handlers as the "response" object.
(But high-level convenience functions to construct an HTTP response are
provided).


API reference
-------------

The best API reference currently are examples (see below) and the ``picoweb``
source code itself. It's under 10K, so enjoy:
https://github.com/pfalcon/picoweb/blob/master/picoweb/__init__.py

Note that API is experimental and may undergo changes.


Examples
--------

* `example_webapp.py <https://github.com/pfalcon/picoweb/blob/master/example_webapp.py>`_ -
  A simple webapp showing you how to generate a complete HTTP response
  yourself, use ``picoweb`` convenience functions for HTTP headers generation,
  and use of templates. Mapping from URLs to webapp view functions ("web
  routes" or just "routes") is done Django-style, using a centralized route
  list.
* `example_webapp2.py <https://github.com/pfalcon/picoweb/blob/master/example_webapp2.py>`_ -
  Like above, but uses ``app.route()`` decorator for route specification,
  Flask-style.
* `examples/ <https://github.com/pfalcon/picoweb/tree/master/examples>`_ -
  Additional examples for various features of picoweb. See comments in each
  file for additional info. To run examples in this directory, you normally
  would need to have picoweb installed (i.e. available in your ``MICROPYPATH``,
  which defaults to ``~/.micropython/lib/``).
* `notes-pico <https://github.com/pfalcon/notes-pico>`_ - A more realistic
  example webapp, ported from the Flask original.


Running under CPython (regressed)
---------------------------------

Initial versions of picoweb could run under CPython, but later it was
further optimized for Pycopy, and ability to run under CPython
regressed. It's still on TODO to fix it, instructions below tell how
it used to work.

At least CPython 3.4.2 is required (for asyncio loop.create_task() support).
To run under CPython, uasyncio compatibility module for CPython is required
(pycopy-cpython-uasyncio). This and other dependencies can be installed
using requirements-cpython.txt::

    pip install -r requirements-cpython.txt

Reporting Issues
----------------

Here are a few guidelines to make feedback more productive:

1. Please be considerate of the overall best practices and common pitfalls in
   reporting issues, this document gives a good overview:
   `How to Report Bugs Effectively <https://www.chiark.greenend.org.uk/~sgtatham/bugs.html>`_.
2. The reference platform for ``picoweb`` is the Unix port of Pycopy. All issues
   reported must be validated against this version, to differentiate issues of
   ``picoweb``/``uasyncio`` from the issues of your underlying platform.
3. All reports must include version information of all components involved:
   Pycopy, picoweb, uasyncio, uasyncio.core, any additional modules. Generally,
   only the latest versions of the above are supported (this is what you get when
   you install/reinstall components using the ``upip`` package manager). The
   version information are thus first of all important for yourself, the issue
   reporter, it allows you to double-check if you're using an outdated or
   unsupported component.
4. Feature requests: ``picoweb`` is by definition a pico-framework, and bound
   to stay so. Feature requests are welcome, but please be considerate that
   they may be outside the scope of core project. There's an easy way out
   though: instead of putting more stuff *into* ``picoweb``, build new things
   *on top* of it: via plugins, subclassing, additional modules etc. That's
   how it was intended to be from the beginning!
5. We would like to establish a dedicated QA team to support users of this
   project better. If you would like to sponsor this effort, please let us
   know.


================================================
FILE: example_webapp.py
================================================
#
# This is a picoweb example showing a centralized web page route
# specification (classical Django style).
#
import ure as re
import picoweb


def index(req, resp):
    # You can construct an HTTP response completely yourself, having
    # a full control of headers sent...
    yield from resp.awrite("HTTP/1.0 200 OK\r\n")
    yield from resp.awrite("Content-Type: text/html\r\n")
    yield from resp.awrite("\r\n")
    yield from resp.awrite("I can show you a table of <a href='squares'>squares</a>.<br/>")
    yield from resp.awrite("Or my <a href='file'>source</a>.")


def squares(req, resp):
    # Or can use a convenience function start_response() (see its source for
    # extra params it takes).
    yield from picoweb.start_response(resp)
    yield from app.render_template(resp, "squares.tpl", (req,))


def hello(req, resp):
    yield from picoweb.start_response(resp)
    # Here's how you extract matched groups from a regex URI match
    yield from resp.awrite("Hello " + req.url_match.group(1))


ROUTES = [
    # You can specify exact URI string matches...
    ("/", index),
    ("/squares", squares),
    ("/file", lambda req, resp: (yield from app.sendfile(resp, "example_webapp.py"))),
    # ... or match using a regex, the match result available as req.url_match
    # for match group extraction in your view.
    (re.compile("^/iam/(.+)"), hello),
]


import ulogging as logging
logging.basicConfig(level=logging.INFO)
#logging.basicConfig(level=logging.DEBUG)

app = picoweb.WebApp(__name__, ROUTES)
# debug values:
# -1 disable all logging
# 0 (False) normal logging: requests and errors
# 1 (True) debug logging
# 2 extra debug logging
app.run(debug=1)


================================================
FILE: example_webapp2.py
================================================
#
# This is a picoweb example showing a web page route
# specification using view decorators (Flask style).
#
import picoweb


app = picoweb.WebApp(__name__)

@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("I can show you a table of <a href='squares'>squares</a>.")

@app.route("/squares")
def squares(req, resp):
    yield from picoweb.start_response(resp)
    yield from app.render_template(resp, "squares.tpl", (req,))


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/README.md
================================================
More picoweb examples
=====================

This directory contains additional examples (beyond a couple available
in the top-level directory) of picoweb usage. These examples are intended
to serve as learn-by-example material, showing various features and
usage patterns of picoweb. Each example starts with a comment header
describing what the example does.

To run these examples, you normally need picoweb already installed (i.e.
available in your MICROPYPATH). If you want a quick start, you need to
try a couple of examples in the top-level dir mentioned above - those
can be run directly from the git checkout. (And as a final hint, you can
also copy any example to the top-level dir and run it from there too).


================================================
FILE: examples/app1.py
================================================
#
# This is an example of a (sub)application, which can be made a part of
# bigger site using "app mount" feature, see example_app_router.py.
#
import picoweb


app = picoweb.WebApp(__name__)

@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("This is webapp #1")


if __name__ == "__main__":
    app.run(debug=True)


================================================
FILE: examples/app2.py
================================================
#
# This is an example of a (sub)application, which can be made a part of
# bigger site using "app mount" feature, see example_app_router.py.
#
import picoweb


app = picoweb.WebApp(__name__)

@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("This is webapp #2")


if __name__ == "__main__":
    app.run(debug=True)


================================================
FILE: examples/example_app_router.py
================================================
#
# This is an example of running several sub-applications in one bigger
# application, by "mounting" them under specific URLs.
#
import picoweb
import app1, app2


site = picoweb.WebApp(__name__)
site.mount("/app1", app1.app)
site.mount("/app2", app2.app)


@site.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("<a href='app1'>app1<a> or <a href='app2'>app2</a>")


site.run(debug=True)


================================================
FILE: examples/example_basic_auth.py
================================================
#
# This is a picoweb example showing handling of HTTP Basic authentication.
#
import ubinascii

import picoweb


app = picoweb.WebApp(__name__)

@app.route("/")
def index(req, resp):
    if b"Authorization" not in req.headers:
        yield from resp.awrite(
            'HTTP/1.0 401 NA\r\n'
            'WWW-Authenticate: Basic realm="Picoweb Realm"\r\n'
            '\r\n'
        )
        return

    auth = req.headers[b"Authorization"].split(None, 1)[1]
    auth = ubinascii.a2b_base64(auth).decode()
    username, passwd = auth.split(":", 1)
    yield from picoweb.start_response(resp)
    yield from resp.awrite("You logged in with username: %s, password: %s" % (username, passwd))


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/example_basic_auth_deco.py
================================================
#
# This is a picoweb example showing handling of HTTP Basic authentication
# using a decorator. Note: using decorator is cute, bit isn't the most
# memory-efficient way. Prefer calling functions directly if you develop
# for memory-constrained device.
#
import ubinascii

import picoweb


app = picoweb.WebApp(__name__)


def require_auth(func):

    def auth(req, resp):
        auth = req.headers.get(b"Authorization")
        if not auth:
            yield from resp.awrite(
                'HTTP/1.0 401 NA\r\n'
                'WWW-Authenticate: Basic realm="Picoweb Realm"\r\n'
                '\r\n'
            )
            return

        auth = auth.split(None, 1)[1]
        auth = ubinascii.a2b_base64(auth).decode()
        req.username, req.passwd = auth.split(":", 1)
        yield from func(req, resp)

    return auth


@app.route("/")
@require_auth
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("You logged in with username: %s, password: %s" % (req.username, req.passwd))


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/example_eventsource.py
================================================
#
# This is a picoweb example showing a Server Side Events (SSE) aka
# EventSource handling. Each connecting client gets its own events,
# independent from other connected clients.
#
import uasyncio
import picoweb


def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("""\
<!DOCTYPE html>
<html>
<head>
<script>
var source = new EventSource("events");
source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br>";
}
source.onerror = function(error) {
    console.log(error);
    document.getElementById("result").innerHTML += "EventSource error:" + error + "<br>";
}
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>
""")

def events(req, resp):
    print("Event source connected")
    yield from resp.awrite("HTTP/1.0 200 OK\r\n")
    yield from resp.awrite("Content-Type: text/event-stream\r\n")
    yield from resp.awrite("\r\n")
    i = 0
    try:
        while True:
            yield from resp.awrite("data: %d\n\n" % i)
            yield from uasyncio.sleep(1)
            i += 1
    except OSError:
        print("Event source connection closed")
        yield from resp.aclose()


ROUTES = [
    ("/", index),
    ("/events", events),
]


import ulogging as logging
logging.basicConfig(level=logging.INFO)
#logging.basicConfig(level=logging.DEBUG)

app = picoweb.WebApp(__name__, ROUTES)
app.run(debug=True)


================================================
FILE: examples/example_eventsource_push.py
================================================
#
# This is a picoweb example showing a Server Side Events (SSE) aka
# EventSource handling. All connecting clients get the same events.
# This is achieved by running a "background service" (a coroutine)
# and "pushing" the same event to each connected client.
#
import uasyncio
import picoweb

event_sinks = set()

#
# Webapp part
#

def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("""\
<!DOCTYPE html>
<html>
<head>
<script>
var source = new EventSource("events");
source.onmessage = function(event) {
    document.getElementById("result").innerHTML += event.data + "<br>";
}
source.onerror = function(error) {
    console.log(error);
    document.getElementById("result").innerHTML += "EventSource error:" + error + "<br>";
}
</script>
</head>
<body>
<div id="result"></div>
</body>
</html>
""")


def events(req, resp):
    global event_sinks
    print("Event source %r connected" % resp)
    yield from resp.awrite("HTTP/1.0 200 OK\r\n")
    yield from resp.awrite("Content-Type: text/event-stream\r\n")
    yield from resp.awrite("\r\n")
    event_sinks.add(resp)
    return False


ROUTES = [
    ("/", index),
    ("/events", events),
]

#
# Background service part
#

def push_event(ev):
    global event_sinks
    to_del = set()

    for resp in event_sinks:
        try:
            await resp.awrite("data: %s\n\n" % ev)
        except OSError as e:
            print("Event source %r disconnected (%r)" % (resp, e))
            await resp.aclose()
            # Can't remove item from set while iterating, have to have
            # second pass for that (not very efficient).
            to_del.add(resp)

    for resp in to_del:
        event_sinks.remove(resp)


def push_count():
    i = 0
    while 1:
        await push_event("%s" % i)
        i += 1
        await uasyncio.sleep(1)


import ulogging as logging
logging.basicConfig(level=logging.INFO)
#logging.basicConfig(level=logging.DEBUG)

loop = uasyncio.get_event_loop()
loop.create_task(push_count())

app = picoweb.WebApp(__name__, ROUTES)
app.run(debug=True)


================================================
FILE: examples/example_extra_headers.py
================================================
#
# This is a picoweb example showing the usage of
# extra headers in responses.
#
import picoweb
import ure as re

app = picoweb.WebApp(__name__)

# Shows sending extra headers specified as a dictionary.
@app.route("/")
def index(req, resp):

    headers = {"X-MyHeader1": "foo", "X-MyHeader2": "bar"}

    # Passing headers as a positional param is more efficient,
    # but we pass by keyword here ;-)
    yield from picoweb.start_response(resp, headers=headers)
    yield from resp.awrite(b"""\
<!DOCTYPE html>
<html>
<head>
<link href="style.css" rel="stylesheet">
</head>
<body>
<p>The style.css should be cached and might be encoded.</p>
<p class="green">Check out your webdev tool!</p>
</body>
</html>""")


# Send gzipped content if supported by client.
# Shows specifying headers as a flat binary string -
# more efficient if such headers are static.
@app.route(re.compile('^\/(.+\.css)$'))
def styles(req, resp):
    file_path = req.url_match.group(1)
    headers = b"Cache-Control: max-age=86400\r\n"

    if b"gzip" in req.headers.get(b"Accept-Encoding", b""):
        file_path += ".gz"
        headers += b"Content-Encoding: gzip\r\n"

    print("sending " + file_path)
    yield from app.sendfile(resp, "static/" + file_path, "text/css", headers)


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/example_form.py
================================================
#
# This is a picoweb example showing how to handle form data.
#
import picoweb


app = picoweb.WebApp(__name__)


@app.route("/form_url")
def index(req, resp):
    if req.method == "POST":
        yield from req.read_form_data()
    else:  # GET, apparently
        # Note: parse_qs() is not a coroutine, but a normal function.
        # But you can call it using yield from too.
        req.parse_qs()

    # Whether form data comes from GET or POST request, once parsed,
    # it's available as req.form dictionary

    yield from picoweb.start_response(resp)
    yield from resp.awrite("Hello %s!" % req.form["name"])


@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("POST form:<br>")
    yield from resp.awrite("<form action='form_url' method='POST'>"
        "What is your name? <input name='name' /> "
        "<input type='submit'></form>")

    yield from resp.awrite("GET form:<br>")
    # GET is the default
    yield from resp.awrite("<form action='form_url'>"
        "What is your name? <input name='name' /> "
        "<input type='submit'></form>")


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/example_global_exc.py
================================================
#
# This is a picoweb example showing a how to "globally" handle exceptions
# during request processing. Note that you can always handle
# exceptions in a particular request using normal try/except/finally.
# That's actually the recommended way. Of course, if you have a
# webapp with many request handlers, that becomes less practical
# and global exception handler may make sense. A common action of
# global handler would be to send a "500" page, but mind the cuprit
# shown below.
#
import picoweb


class ExcWebApp(picoweb.WebApp):

    async def handle_exc(self, req, resp, exc):
        try:
            # Do you already see a problem - what happens if your action
            # already started output before exception happened? Resolving
            # that issue is wholy up to your webapp, picoweb doesn't limit
            # you to any particular method, use whatever suits you better!
            await picoweb.start_response(resp, status="500")
            await resp.awrite("We've got 500, cap!")
        except Exception as e:
            # Per API contract, handle_exc() must not raise exceptions
            # (unless we want the whole webapp to terminate).
            print(repr(e))


app = ExcWebApp(__name__)


@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite(
        "<a href='case1'>good exception case</a> "
        "<a href='case2'>less good exception case</a>"
    )


@app.route("/case1")
def case1(req, resp):
    1/0


@app.route("/case2")
def case2(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite(
        "Here, I started to write something to response, and suddenly..."
    )
    1/0


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/example_header_modes.py
================================================
#
# This is a picoweb example showing various header parsing modes.
#
import ure as re
import picoweb


def index(req, resp):
    yield from resp.awrite("HTTP/1.0 200 OK\r\n")
    yield from resp.awrite("Content-Type: text/html\r\n")
    yield from resp.awrite("\r\n")
    yield from resp.awrite('<li><a href="mode_parse">header_mode="parse"</a>')
    yield from resp.awrite('<li><a href="mode_skip">header_mode="skip"</a>')
    yield from resp.awrite('<li><a href="mode_leave">header_mode="leave"</a>')


def headers_parse(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite("<table border='1'>")
    for h, v in req.headers.items():
        yield from resp.awrite("<tr><td>%s</td><td>%s</td></tr>\r\n" % (h, v))
    yield from resp.awrite("</table>")

def headers_skip(req, resp):
    yield from picoweb.start_response(resp)
    assert not hasattr(req, "headers")
    yield from resp.awrite("No <tt>req.headers</tt>.")

def headers_leave(req, resp):
    yield from picoweb.start_response(resp)
    assert not hasattr(req, "headers")
    yield from resp.awrite("Reading headers directly from input request:")
    yield from resp.awrite("<pre>")
    while True:
        l = yield from req.reader.readline()
        if l == b"\r\n":
            break
        yield from resp.awrite(l)
    yield from resp.awrite("</pre>")


ROUTES = [
    ("/", index),
    ("/mode_parse", headers_parse, {"headers": "parse"}),
    ("/mode_skip", headers_skip, {"headers": "skip"}),
    ("/mode_leave", headers_leave, {"headers": "leave"}),
]


import ulogging as logging
logging.basicConfig(level=logging.INFO)
#logging.basicConfig(level=logging.DEBUG)

app = picoweb.WebApp(__name__, ROUTES)
# You could set the default header parsing mode here like this:
# app.headers_mode = "skip"
app.run(debug=True)


================================================
FILE: examples/example_img.py
================================================
#
# This is a picoweb example showing how to serve images - both static
# image from webapp's static/ dir, and dynamically-generated image.
#
import picoweb


app = picoweb.WebApp(__name__)

@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    yield from resp.awrite(b"Static image: <img src='static/logo.png'><br />")
    yield from resp.awrite(b"Dynamic image: <img src='dyna-logo.png'><br />")

@app.route("/dyna-logo.png")
def squares(req, resp):
    yield from picoweb.start_response(resp, "image/png")
    yield from resp.awrite(
        b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\x00\x00\x000\x00\x00\x000\x01\x03\x00\x00\x00m\xcck"
        b"\xc4\x00\x00\x00\x06PLTE\xff\xff\xff\x00\x00\x00U\xc2\xd3~\x00\x00\x00\xd2IDAT\x18\xd3c`"
        b"\x80\x01\xc6\x06\x08-\x00\xe1u\x80)\xa6\x040\xc5\x12\x00\xa18\xc0\x14+\x84\xe2\xf5\x00Sr"
        b"\x10J~\x0e\x98\xb2\xff\x83\x85\xb2\x86P\xd2\x15\x10\x95\x10\x8a\xff\x07\x98b\xff\x80L\xb1"
        b"\xa5\x83-?\x95\xff\x00$\xf6\xeb\x7f\x01\xc8\x9eo\x7f@\x92r)\x9fA\x94\xfc\xc4\xf3/\x80\x94"
        b"\xf8\xdb\xff'@F\x1e\xfcg\x01\xa4\xac\x1e^\xaa\x01R6\xb1\x8f\xff\x01);\xc7\xff \xca\xfe\xe1"
        b"\xff_@\xea\xff\xa7\xff\x9f\x81F\xfe\xfe\x932\xbd\x81\x81\xb16\xf0\xa4\x1d\xd0\xa3\xf3\xfb"
        b"\xba\x7f\x02\x05\x97\xff\xff\xff\x14(\x98\xf9\xff\xff\xb4\x06\x06\xa6\xa8\xfa\x7fQ\x0e\x0c"
        b"\x0c\xd3\xe6\xff\xcc\x04\xeaS]\xfet\t\x90\xe2\xcc\x9c6\x01\x14\x10Q )\x06\x86\xe9/\xc1\xee"
        b"T]\x02\xa68\x04\x18\xd0\x00\x00\xcb4H\xa2\x8c\xbd\xc0j\x00\x00\x00\x00IEND\xaeB`\x82"
    )


import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/example_unicode.py
================================================
#
# This is a picoweb example showing rendering of template
# with Unicode (UTF-8) characters.
#
import picoweb


app = picoweb.WebApp(__name__)

@app.route("/")
def index(req, resp):
    yield from picoweb.start_response(resp)
    data = {"chars": "абвгд", "var1": "α", "var2": "β", "var3": "γ"}
    yield from app.render_template(resp, "unicode.tpl", (data,))

import ulogging as logging
logging.basicConfig(level=logging.INFO)

app.run(debug=True)


================================================
FILE: examples/static/style.css
================================================
.green {
    color: darkgreen;
}


================================================
FILE: examples/templates/unicode.tpl
================================================
{% args d %}
<html>
Some Cyrillic characters: {{d["chars"]}}<br>
Some Greek characters: {{d["var1"]}} = {{d["var2"]}} + {{d["var3"]}}<br>
</html>


================================================
FILE: picoweb/__init__.py
================================================
# Picoweb web pico-framework for Pycopy, https://github.com/pfalcon/pycopy
# Copyright (c) 2014-2020 Paul Sokolovsky
# SPDX-License-Identifier: MIT
import sys
import gc
import micropython
import utime
import uio
import ure as re
import uerrno
import uasyncio as asyncio
import pkg_resources

from .utils import parse_qs

SEND_BUFSZ = 128


def get_mime_type(fname):
    # Provide minimal detection of important file
    # types to keep browsers happy
    if fname.endswith(".html"):
        return "text/html"
    if fname.endswith(".css"):
        return "text/css"
    if fname.endswith(".png") or fname.endswith(".jpg"):
        return "image"
    return "text/plain"

def sendstream(writer, f):
    buf = bytearray(SEND_BUFSZ)
    while True:
        l = f.readinto(buf)
        if not l:
            break
        yield from writer.awrite(buf, 0, l)


def jsonify(writer, dict):
    import ujson
    yield from start_response(writer, "application/json")
    yield from writer.awrite(ujson.dumps(dict))

def start_response(writer, content_type="text/html; charset=utf-8", status="200", headers=None):
    yield from writer.awrite("HTTP/1.0 %s NA\r\n" % status)
    yield from writer.awrite("Content-Type: ")
    yield from writer.awrite(content_type)
    if not headers:
        yield from writer.awrite("\r\n\r\n")
        return
    yield from writer.awrite("\r\n")
    if isinstance(headers, bytes) or isinstance(headers, str):
        yield from writer.awrite(headers)
    else:
        for k, v in headers.items():
            yield from writer.awrite(k)
            yield from writer.awrite(": ")
            yield from writer.awrite(v)
            yield from writer.awrite("\r\n")
    yield from writer.awrite("\r\n")

def http_error(writer, status):
    yield from start_response(writer, status=status)
    yield from writer.awrite(status)


class HTTPRequest:

    def __init__(self):
        pass

    def read_form_data(self):
        size = int(self.headers[b"Content-Length"])
        data = yield from self.reader.readexactly(size)
        form = parse_qs(data.decode())
        self.form = form

    def parse_qs(self):
        form = parse_qs(self.qs)
        self.form = form


class WebApp:

    def __init__(self, pkg, routes=None, serve_static=True):
        if routes:
            self.url_map = routes
        else:
            self.url_map = []
        if pkg and pkg != "__main__":
            self.pkg = pkg.split(".", 1)[0]
        else:
            self.pkg = None
        if serve_static:
            self.url_map.append((re.compile("^/(static/.+)"), self.handle_static))
        self.mounts = []
        self.inited = False
        # Instantiated lazily
        self.template_loader = None
        self.headers_mode = "parse"

    def parse_headers(self, reader):
        headers = {}
        while True:
            l = yield from reader.readline()
            if l == b"\r\n":
                break
            k, v = l.split(b":", 1)
            headers[k] = v.strip()
        return headers

    def _handle(self, reader, writer):
        if self.debug > 1:
            micropython.mem_info()

        close = True
        req = None
        try:
            request_line = yield from reader.readline()
            if request_line == b"":
                if self.debug >= 0:
                    self.log.error("%s: EOF on request start" % reader)
                yield from writer.aclose()
                return
            req = HTTPRequest()
            # TODO: bytes vs str
            request_line = request_line.decode()
            method, path, proto = request_line.split()
            if self.debug >= 0:
                self.log.info('%.3f %s %s "%s %s"' % (utime.time(), req, writer, method, path))
            path = path.split("?", 1)
            qs = ""
            if len(path) > 1:
                qs = path[1]
            path = path[0]

            #print("================")
            #print(req, writer)
            #print(req, (method, path, qs, proto), req.headers)

            # Find which mounted subapp (if any) should handle this request
            app = self
            while True:
                found = False
                for subapp in app.mounts:
                    root = subapp.url
                    #print(path, "vs", root)
                    if path[:len(root)] == root:
                        app = subapp
                        found = True
                        path = path[len(root):]
                        if not path.startswith("/"):
                            path = "/" + path
                        break
                if not found:
                    break

            # We initialize apps on demand, when they really get requests
            if not app.inited:
                app.init()

            # Find handler to serve this request in app's url_map
            found = False
            for e in app.url_map:
                pattern = e[0]
                handler = e[1]
                extra = {}
                if len(e) > 2:
                    extra = e[2]

                if path == pattern:
                    found = True
                    break
                elif not isinstance(pattern, str):
                    # Anything which is non-string assumed to be a ducktype
                    # pattern matcher, whose .match() method is called. (Note:
                    # Django uses .search() instead, but .match() is more
                    # efficient and we're not exactly compatible with Django
                    # URL matching anyway.)
                    m = pattern.match(path)
                    if m:
                        req.url_match = m
                        found = True
                        break

            if not found:
                headers_mode = "skip"
            else:
                headers_mode = extra.get("headers", self.headers_mode)

            if headers_mode == "skip":
                while True:
                    l = yield from reader.readline()
                    if l == b"\r\n":
                        break
            elif headers_mode == "parse":
                req.headers = yield from self.parse_headers(reader)
            else:
                assert headers_mode == "leave"

            if found:
                req.method = method
                req.path = path
                req.qs = qs
                req.reader = reader
                close = yield from handler(req, writer)
            else:
                yield from start_response(writer, status="404")
                yield from writer.awrite("404\r\n")
            #print(req, "After response write")
        except Exception as e:
            if self.debug >= 0:
                self.log.exc(e, "%.3f %s %s %r" % (utime.time(), req, writer, e))
            yield from self.handle_exc(req, writer, e)

        if close is not False:
            yield from writer.aclose()
        if __debug__ and self.debug > 1:
            self.log.debug("%.3f %s Finished processing request", utime.time(), req)

    def handle_exc(self, req, resp, e):
        # Can be overriden by subclasses. req may be not (fully) initialized.
        # resp may already have (partial) content written.
        # NOTE: It's your responsibility to not throw exceptions out of
        # handle_exc(). If exception is thrown, it will be propagated, and
        # your webapp will terminate.
        # This method is a coroutine.
        return
        yield

    def mount(self, url, app):
        "Mount a sub-app at the url of current app."
        # Inspired by Bottle. It might seem that dispatching to
        # subapps would rather be handled by normal routes, but
        # arguably, that's less efficient. Taking into account
        # that paradigmatically there's difference between handing
        # an action and delegating responisibilities to another
        # app, Bottle's way was followed.
        app.url = url
        self.mounts.append(app)
        # TODO: Consider instead to do better subapp prefix matching
        # in _handle() above.
        self.mounts.sort(key=lambda app: len(app.url), reverse=True)

    def route(self, url, **kwargs):
        def _route(f):
            self.url_map.append((url, f, kwargs))
            return f
        return _route

    def add_url_rule(self, url, func, **kwargs):
        # Note: this method skips Flask's "endpoint" argument,
        # because it's alleged bloat.
        self.url_map.append((url, func, kwargs))

    def _load_template(self, tmpl_name):
        if self.template_loader is None:
            import utemplate.source
            self.template_loader = utemplate.source.Loader(self.pkg, "templates")
        return self.template_loader.load(tmpl_name)

    def render_template(self, writer, tmpl_name, args=()):
        tmpl = self._load_template(tmpl_name)
        for s in tmpl(*args):
            yield from writer.awritestr(s)

    def render_str(self, tmpl_name, args=()):
        #TODO: bloat
        tmpl = self._load_template(tmpl_name)
        return ''.join(tmpl(*args))

    def sendfile(self, writer, fname, content_type=None, headers=None):
        if not content_type:
            content_type = get_mime_type(fname)
        try:
            with pkg_resources.resource_stream(self.pkg, fname) as f:
                yield from start_response(writer, content_type, "200", headers)
                yield from sendstream(writer, f)
        except OSError as e:
            if e.args[0] == uerrno.ENOENT:
                yield from http_error(writer, "404")
            else:
                raise

    def handle_static(self, req, resp):
        path = req.url_match.group(1)
        print(path)
        if ".." in path:
            yield from http_error(resp, "403")
            return
        yield from self.sendfile(resp, path)

    def init(self):
        """Initialize a web application. This is for overriding by subclasses.
        This is good place to connect to/initialize a database, for example."""
        self.inited = True

    def serve(self, loop, host, port):
        # Actually serve client connections. Subclasses may override this
        # to e.g. catch and handle exceptions when dealing with server socket
        # (which are otherwise unhandled and will terminate a Picoweb app).
        # Note: name and signature of this method may change.
        loop.create_task(asyncio.start_server(self._handle, host, port))
        loop.run_forever()

    def run(self, host="127.0.0.1", port=8081, debug=False, lazy_init=False, log=None):
        if log is None and debug >= 0:
            import ulogging
            log = ulogging.getLogger("picoweb")
            if debug > 0:
                log.setLevel(ulogging.DEBUG)
        self.log = log
        gc.collect()
        self.debug = int(debug)
        self.init()
        if not lazy_init:
            for app in self.mounts:
                app.init()
        loop = asyncio.get_event_loop()
        if debug > 0:
            print("* Running on http://%s:%s/" % (host, port))
        self.serve(loop, host, port)
        loop.close()


================================================
FILE: picoweb/utils.py
================================================
def unquote_plus(s):
    # TODO: optimize
    s = s.replace("+", " ")
    arr = s.split("%")
    arr2 = [chr(int(x[:2], 16)) + x[2:] for x in arr[1:]]
    return arr[0] + "".join(arr2)

def parse_qs(s):
    res = {}
    if s:
        pairs = s.split("&")
        for p in pairs:
            vals = [unquote_plus(x) for x in p.split("=", 1)]
            if len(vals) == 1:
                vals.append(True)
            old = res.get(vals[0])
            if old is not None:
                if not isinstance(old, list):
                    old = [old]
                    res[vals[0]] = old
                old.append(vals[1])
            else:
                res[vals[0]] = vals[1]
    return res

#print(parse_qs("foo"))
#print(parse_qs("fo%41o+bar=+++1"))
#print(parse_qs("foo=1&foo=2"))


================================================
FILE: requirements-cpython.txt
================================================
pycopy-cpython-uasyncio
utemplate


================================================
FILE: sdist_upip.py
================================================
#
# This module overrides distutils (also compatible with setuptools) "sdist"
# command to perform pre- and post-processing as required for MicroPython's
# upip package manager.
#
# Preprocessing steps:
#  * Creation of Python resource module (R.py) from each top-level package's
#    resources.
# Postprocessing steps:
#  * Removing metadata files not used by upip (this includes setup.py)
#  * Recompressing gzip archive with 4K dictionary size so it can be
#    installed even on low-heap targets.
#
import sys
import os
import zlib
from subprocess import Popen, PIPE
import glob
import tarfile
import re
import io

from distutils.filelist import FileList
from setuptools.command.sdist import sdist as _sdist


def gzip_4k(inf, fname):
    comp = zlib.compressobj(level=9, wbits=16 + 12)
    with open(fname + ".out", "wb") as outf:
        while 1:
            data = inf.read(1024)
            if not data:
                break
            outf.write(comp.compress(data))
        outf.write(comp.flush())
    os.rename(fname, fname + ".orig")
    os.rename(fname + ".out", fname)


FILTERS = [
    # include, exclude, repeat
    (r".+\.egg-info/(PKG-INFO|requires\.txt)", r"setup.py$"),
    (r".+\.py$", r"[^/]+$"),
    (None, r".+\.egg-info/.+"),
]


outbuf = io.BytesIO()

def filter_tar(name):
    fin = tarfile.open(name, "r:gz")
    fout = tarfile.open(fileobj=outbuf, mode="w")
    for info in fin:
#        print(info)
        if not "/" in info.name:
            continue
        fname = info.name.split("/", 1)[1]
        include = None

        for inc_re, exc_re in FILTERS:
            if include is None and inc_re:
                if re.match(inc_re, fname):
                    include = True

            if include is None and exc_re:
                if re.match(exc_re, fname):
                    include = False

        if include is None:
            include = True

        if include:
            print("including:", fname)
        else:
            print("excluding:", fname)
            continue

        farch = fin.extractfile(info)
        fout.addfile(info, farch)
    fout.close()
    fin.close()


def make_resource_module(manifest_files):
        resources = []
        # Any non-python file included in manifest is resource
        for fname in manifest_files:
            ext = fname.rsplit(".", 1)[1]
            if ext != "py":
                resources.append(fname)

        if resources:
            print("creating resource module R.py")
            resources.sort()
            last_pkg = None
            r_file = None
            for fname in resources:
                try:
                    pkg, res_name = fname.split("/", 1)
                except ValueError:
                    print("not treating %s as a resource" % fname)
                    continue
                if last_pkg != pkg:
                    last_pkg = pkg
                    if r_file:
                        r_file.write("}\n")
                        r_file.close()
                    r_file = open(pkg + "/R.py", "w")
                    r_file.write("R = {\n")

                with open(fname, "rb") as f:
                    r_file.write("%r: %r,\n" % (res_name, f.read()))

            if r_file:
                r_file.write("}\n")
                r_file.close()


class sdist(_sdist):

    def run(self):
        self.filelist = FileList()
        self.get_file_list()
        make_resource_module(self.filelist.files)

        r = super().run()

        assert len(self.archive_files) == 1
        print("filtering files and recompressing with 4K dictionary")
        filter_tar(self.archive_files[0])
        outbuf.seek(0)
        gzip_4k(outbuf, self.archive_files[0])

        return r


# For testing only
if __name__ == "__main__":
    filter_tar(sys.argv[1])
    outbuf.seek(0)
    gzip_4k(outbuf, sys.argv[1])


================================================
FILE: setup.py
================================================
from setuptools import setup
import sdist_upip


setup(name='picoweb',
      version='1.8.2',
      description="A very lightweight, memory-efficient async web framework \
for Pycopy (https://github.com/pfalcon/pycopy) and its uasyncio module.",
      long_description=open('README.rst').read(),
      url='https://github.com/pfalcon/picoweb',
      author='Paul Sokolovsky',
      author_email='pfalcon@users.sourceforge.net',
      license='MIT',
      cmdclass={'sdist': sdist_upip.sdist},
      packages=['picoweb'],
      # Note: no explicit dependency on 'utemplate', if a specific app uses
      # templates, it must depend on it. Likewise, don't depend on
      # pycopy-ulogging as application might not use logging.
      install_requires=['pycopy-uasyncio', 'pycopy-pkg_resources'])


================================================
FILE: templates/squares.tpl
================================================
{% args req %}
<html>
Request path: '{{req.path}}'<br>
<table border="1">
{% for i in range(5) %}
<tr><td> {{i}} </td><td> {{"%2d" % i ** 2}} </td></tr>
{% endfor %}
</table>
</html>
Download .txt
gitextract_h09r58tg/

├── LICENSE
├── Makefile
├── README.rst
├── example_webapp.py
├── example_webapp2.py
├── examples/
│   ├── README.md
│   ├── app1.py
│   ├── app2.py
│   ├── example_app_router.py
│   ├── example_basic_auth.py
│   ├── example_basic_auth_deco.py
│   ├── example_eventsource.py
│   ├── example_eventsource_push.py
│   ├── example_extra_headers.py
│   ├── example_form.py
│   ├── example_global_exc.py
│   ├── example_header_modes.py
│   ├── example_img.py
│   ├── example_unicode.py
│   ├── static/
│   │   └── style.css
│   └── templates/
│       └── unicode.tpl
├── picoweb/
│   ├── __init__.py
│   └── utils.py
├── requirements-cpython.txt
├── sdist_upip.py
├── setup.py
└── templates/
    └── squares.tpl
Download .txt
SYMBOL INDEX (65 symbols across 18 files)

FILE: example_webapp.py
  function index (line 9) | def index(req, resp):
  function squares (line 19) | def squares(req, resp):
  function hello (line 26) | def hello(req, resp):

FILE: example_webapp2.py
  function index (line 11) | def index(req, resp):
  function squares (line 16) | def squares(req, resp):

FILE: examples/app1.py
  function index (line 11) | def index(req, resp):

FILE: examples/app2.py
  function index (line 11) | def index(req, resp):

FILE: examples/example_app_router.py
  function index (line 15) | def index(req, resp):

FILE: examples/example_basic_auth.py
  function index (line 12) | def index(req, resp):

FILE: examples/example_basic_auth_deco.py
  function require_auth (line 15) | def require_auth(func):
  function index (line 37) | def index(req, resp):

FILE: examples/example_eventsource.py
  function index (line 10) | def index(req, resp):
  function events (line 33) | def events(req, resp):

FILE: examples/example_eventsource_push.py
  function index (line 16) | def index(req, resp):
  function events (line 40) | def events(req, resp):
  function push_event (line 59) | def push_event(ev):
  function push_count (line 77) | def push_count():

FILE: examples/example_extra_headers.py
  function index (line 12) | def index(req, resp):
  function styles (line 36) | def styles(req, resp):

FILE: examples/example_form.py
  function index (line 11) | def index(req, resp):
  function index (line 27) | def index(req, resp):

FILE: examples/example_global_exc.py
  class ExcWebApp (line 14) | class ExcWebApp(picoweb.WebApp):
    method handle_exc (line 16) | async def handle_exc(self, req, resp, exc):
  function index (line 34) | def index(req, resp):
  function case1 (line 43) | def case1(req, resp):
  function case2 (line 48) | def case2(req, resp):

FILE: examples/example_header_modes.py
  function index (line 8) | def index(req, resp):
  function headers_parse (line 17) | def headers_parse(req, resp):
  function headers_skip (line 24) | def headers_skip(req, resp):
  function headers_leave (line 29) | def headers_leave(req, resp):

FILE: examples/example_img.py
  function index (line 11) | def index(req, resp):
  function squares (line 17) | def squares(req, resp):

FILE: examples/example_unicode.py
  function index (line 11) | def index(req, resp):

FILE: picoweb/__init__.py
  function get_mime_type (line 19) | def get_mime_type(fname):
  function sendstream (line 30) | def sendstream(writer, f):
  function jsonify (line 39) | def jsonify(writer, dict):
  function start_response (line 44) | def start_response(writer, content_type="text/html; charset=utf-8", stat...
  function http_error (line 62) | def http_error(writer, status):
  class HTTPRequest (line 67) | class HTTPRequest:
    method __init__ (line 69) | def __init__(self):
    method read_form_data (line 72) | def read_form_data(self):
    method parse_qs (line 78) | def parse_qs(self):
  class WebApp (line 83) | class WebApp:
    method __init__ (line 85) | def __init__(self, pkg, routes=None, serve_static=True):
    method parse_headers (line 102) | def parse_headers(self, reader):
    method _handle (line 112) | def _handle(self, reader, writer):
    method handle_exc (line 221) | def handle_exc(self, req, resp, e):
    method mount (line 231) | def mount(self, url, app):
    method route (line 245) | def route(self, url, **kwargs):
    method add_url_rule (line 251) | def add_url_rule(self, url, func, **kwargs):
    method _load_template (line 256) | def _load_template(self, tmpl_name):
    method render_template (line 262) | def render_template(self, writer, tmpl_name, args=()):
    method render_str (line 267) | def render_str(self, tmpl_name, args=()):
    method sendfile (line 272) | def sendfile(self, writer, fname, content_type=None, headers=None):
    method handle_static (line 285) | def handle_static(self, req, resp):
    method init (line 293) | def init(self):
    method serve (line 298) | def serve(self, loop, host, port):
    method run (line 306) | def run(self, host="127.0.0.1", port=8081, debug=False, lazy_init=Fals...

FILE: picoweb/utils.py
  function unquote_plus (line 1) | def unquote_plus(s):
  function parse_qs (line 8) | def parse_qs(s):

FILE: sdist_upip.py
  function gzip_4k (line 27) | def gzip_4k(inf, fname):
  function filter_tar (line 50) | def filter_tar(name):
  function make_resource_module (line 84) | def make_resource_module(manifest_files):
  class sdist (line 119) | class sdist(_sdist):
    method run (line 121) | def run(self):
Condensed preview — 27 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (49K chars).
[
  {
    "path": "LICENSE",
    "chars": 1104,
    "preview": "The MIT License (MIT)\n\nCopyright (c) 2014-2018 Paul Sokolovsky and contributors\n\nPermission is hereby granted, free of c"
  },
  {
    "path": "Makefile",
    "chars": 69,
    "preview": "PREFIX = ~/.micropython/lib\n\nall:\n\ninstall:\n\tcp -a picoweb $(PREFIX)\n"
  },
  {
    "path": "README.rst",
    "chars": 8998,
    "preview": "picoweb\n=======\n\npicoweb is a \"micro\" web micro-framework (thus, \"pico-framework\") for\nradically unbloated web applicati"
  },
  {
    "path": "example_webapp.py",
    "chars": 1679,
    "preview": "#\n# This is a picoweb example showing a centralized web page route\n# specification (classical Django style).\n#\nimport ur"
  },
  {
    "path": "example_webapp2.py",
    "chars": 574,
    "preview": "#\n# This is a picoweb example showing a web page route\n# specification using view decorators (Flask style).\n#\nimport pic"
  },
  {
    "path": "examples/README.md",
    "chars": 720,
    "preview": "More picoweb examples\n=====================\n\nThis directory contains additional examples (beyond a couple available\nin t"
  },
  {
    "path": "examples/app1.py",
    "chars": 376,
    "preview": "#\n# This is an example of a (sub)application, which can be made a part of\n# bigger site using \"app mount\" feature, see e"
  },
  {
    "path": "examples/app2.py",
    "chars": 376,
    "preview": "#\n# This is an example of a (sub)application, which can be made a part of\n# bigger site using \"app mount\" feature, see e"
  },
  {
    "path": "examples/example_app_router.py",
    "chars": 445,
    "preview": "#\n# This is an example of running several sub-applications in one bigger\n# application, by \"mounting\" them under specifi"
  },
  {
    "path": "examples/example_basic_auth.py",
    "chars": 782,
    "preview": "#\n# This is a picoweb example showing handling of HTTP Basic authentication.\n#\nimport ubinascii\n\nimport picoweb\n\n\napp = "
  },
  {
    "path": "examples/example_basic_auth_deco.py",
    "chars": 1130,
    "preview": "#\n# This is a picoweb example showing handling of HTTP Basic authentication\n# using a decorator. Note: using decorator i"
  },
  {
    "path": "examples/example_eventsource.py",
    "chars": 1420,
    "preview": "#\n# This is a picoweb example showing a Server Side Events (SSE) aka\n# EventSource handling. Each connecting client gets"
  },
  {
    "path": "examples/example_eventsource_push.py",
    "chars": 2082,
    "preview": "#\n# This is a picoweb example showing a Server Side Events (SSE) aka\n# EventSource handling. All connecting clients get "
  },
  {
    "path": "examples/example_extra_headers.py",
    "chars": 1353,
    "preview": "#\n# This is a picoweb example showing the usage of\n# extra headers in responses.\n#\nimport picoweb\nimport ure as re\n\napp "
  },
  {
    "path": "examples/example_form.py",
    "chars": 1217,
    "preview": "#\n# This is a picoweb example showing how to handle form data.\n#\nimport picoweb\n\n\napp = picoweb.WebApp(__name__)\n\n\n@app."
  },
  {
    "path": "examples/example_global_exc.py",
    "chars": 1801,
    "preview": "#\n# This is a picoweb example showing a how to \"globally\" handle exceptions\n# during request processing. Note that you c"
  },
  {
    "path": "examples/example_header_modes.py",
    "chars": 1819,
    "preview": "#\n# This is a picoweb example showing various header parsing modes.\n#\nimport ure as re\nimport picoweb\n\n\ndef index(req, r"
  },
  {
    "path": "examples/example_img.py",
    "chars": 1669,
    "preview": "#\n# This is a picoweb example showing how to serve images - both static\n# image from webapp's static/ dir, and dynamical"
  },
  {
    "path": "examples/example_unicode.py",
    "chars": 451,
    "preview": "#\n# This is a picoweb example showing rendering of template\n# with Unicode (UTF-8) characters.\n#\nimport picoweb\n\n\napp = "
  },
  {
    "path": "examples/static/style.css",
    "chars": 33,
    "preview": ".green {\n    color: darkgreen;\n}\n"
  },
  {
    "path": "examples/templates/unicode.tpl",
    "chars": 146,
    "preview": "{% args d %}\n<html>\nSome Cyrillic characters: {{d[\"chars\"]}}<br>\nSome Greek characters: {{d[\"var1\"]}} = {{d[\"var2\"]}} + "
  },
  {
    "path": "picoweb/__init__.py",
    "chars": 11136,
    "preview": "# Picoweb web pico-framework for Pycopy, https://github.com/pfalcon/pycopy\n# Copyright (c) 2014-2020 Paul Sokolovsky\n# S"
  },
  {
    "path": "picoweb/utils.py",
    "chars": 791,
    "preview": "def unquote_plus(s):\n    # TODO: optimize\n    s = s.replace(\"+\", \" \")\n    arr = s.split(\"%\")\n    arr2 = [chr(int(x[:2], "
  },
  {
    "path": "requirements-cpython.txt",
    "chars": 34,
    "preview": "pycopy-cpython-uasyncio\nutemplate\n"
  },
  {
    "path": "sdist_upip.py",
    "chars": 3858,
    "preview": "#\n# This module overrides distutils (also compatible with setuptools) \"sdist\"\n# command to perform pre- and post-process"
  },
  {
    "path": "setup.py",
    "chars": 794,
    "preview": "from setuptools import setup\nimport sdist_upip\n\n\nsetup(name='picoweb',\n      version='1.8.2',\n      description=\"A very "
  },
  {
    "path": "templates/squares.tpl",
    "chars": 183,
    "preview": "{% args req %}\n<html>\nRequest path: '{{req.path}}'<br>\n<table border=\"1\">\n{% for i in range(5) %}\n<tr><td> {{i}} </td><t"
  }
]

About this extraction

This page contains the full source code of the pfalcon/picoweb GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 27 files (44.0 KB), approximately 11.7k tokens, and a symbol index with 65 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!