Metadata-Version: 2.1
Name: understory
Version: 0.0.356
Summary: Social web framework
License: AGPL-3.0-or-later
Keywords: IndieWeb
Author: Angelo Gladding
Author-email: angelo@ragt.ag
Requires-Python: >=3.9,<3.11
Classifier: License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Requires-Dist: Pygments (>=2.11.2,<3.0.0)
Requires-Dist: Unidecode (>=1.2.0,<2.0.0)
Requires-Dist: acme-tiny (>=4.1.0,<5.0.0)
Requires-Dist: certifi (>=2021.10.8,<2022.0.0)
Requires-Dist: clicks (>=0.0.51,<0.0.52)
Requires-Dist: cssselect (>=1.1.0,<2.0.0)
Requires-Dist: dnspython (>=2.1.0,<3.0.0)
Requires-Dist: emoji (>=1.2.0,<2.0.0)
Requires-Dist: gevent (>=21.1.2,<22.0.0)
Requires-Dist: gunicorn (>=20.1.0,<21.0.0)
Requires-Dist: hstspreload (>=2021.12.1,<2022.0.0)
Requires-Dist: httpagentparser (>=1.9.1,<2.0.0)
Requires-Dist: jsonpatch (>=1.32,<2.0)
Requires-Dist: lxml (>=4.8.0,<5.0.0)
Requires-Dist: mf2py (>=1.1.2,<2.0.0)
Requires-Dist: mf2util (>=0.5.1,<0.6.0)
Requires-Dist: mimeparse (>=0.1.3,<0.2.0)
Requires-Dist: pendulum (>=2.1.2,<3.0.0)
Requires-Dist: pillow (>=9.0.1,<10.0.0)
Requires-Dist: pycryptodome (>=3.10.1,<4.0.0)
Requires-Dist: pyscreenshot (>=3.0,<4.0)
Requires-Dist: pyvirtualdisplay (>=3.0,<4.0)
Requires-Dist: regex (>=2021.7.6,<2022.0.0)
Requires-Dist: requests[socks] (>=2.27.1,<3.0.0)
Requires-Dist: restrictedpython (>=5.2,<6.0)
Requires-Dist: selenium (>=4.1.2,<5.0.0)
Requires-Dist: semver (>=2.13.0,<3.0.0)
Requires-Dist: slrzd (>=0.0.8,<0.0.9)
Requires-Dist: sqlyte (>=0.0.19,<0.0.20)
Requires-Dist: toml (>=0.10.2,<0.11.0)
Requires-Dist: waitress (>=2.0.0,<3.0.0)
Requires-Dist: watchdog (>=2.1.3,<3.0.0)
Project-URL: Homepage, https://understory.stream
Description-Content-Type: text/markdown

# Understory
Social web framework

## Create a web app

You should use `Poetry`.

```shell
poetry init
poetry add understory
```

Create the following file structure:

```
example
├── __init__.py
├── static
│   └── screen.css
└── templates
    ├── __init__.py
    └── index.html
```

**example/\_\_init\_\_.py**

```python
import web
from understory import indieauth

app = web.application(__name__, mounts=[indieauth.client.app])


@app.control("")
class Landing:
    def get(self):
        return app.view.index(web.tx.user)
```

**example/static/screen.css**

```css
p.greeting { color: #f00; font-size: 5em; }
```

**example/templates/\_\_init\_\_.py**

```python
import random

from understory.indieauth import web_sign_in

__all__ = ["random", "web_sign_in_form"]
```

**example/templates/index.html**

```html
$def with (user, greeting)

$if user.session:
    $ greeting = random.choice(["hello", "hi", "howdy"])
    <p class=greeting>$greeting $user.name ($user.url)</p>
$else:
    $:web_sign_in_form()
```

### Serve it locally

```shell
poetry run web serve example
```

The server will automatically reload on changes to the source code.

### Host it in the cloud

```shell
poetry run web host example
```

## URL parsing

Defaults to safe-mode and raises DangerousURL eagerly. Up-to-date public
suffix and HSTS support.

    >>> url = skutterbug.uri("example.cnpy.gdn/foo/bar?id=38")
    >>> url.host
    'example.cnpy.gdn'
    >>> url.suffix
    'cnpy.gdn'
    >>> url.is_hsts()
    True

## Cache

uses SQLite

    >>> cache = skutterbug.cache()
    >>> cache["indieweb.org/note"].entry["summary"]
    'A note is a post that is typically short unstructured* plain text, written & posted quickly, that has its own permalink page.'
    >>> cache["indieweb.org/note"].entry["summary"]  # served from cache
    'A note is a post that is typically short unstructured* plain text, written & posted quickly, that has its own permalink page.'

### Microformat parsing

Parse `mf2` from `HTML`. Analyze vocabularies for stability/interoperability.

## Browser

uses Firefox via Selenium

    >>> # browser = skutterbug.Firefox()
    >>> # browser.go("en.wikipedia.org/wiki/Pasta")
    >>> # browser.shot("wikipedia-pasta.png")

