Metadata-Version: 2.4
Name: pywebutil
Version: 2.0
Summary: Common utilities and handler code for Python web apps
Author-email: Ryan Barrett <webutil@ryanb.org>
License-Expression: CC0-1.0
Project-URL: Homepage, https://github.com/snarfed/webutil
Project-URL: Documentation, https://webutil.readthedocs.io/
Keywords: App Engine,Flask,Google Cloud,HTTP,web,webmention,utilities
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Operating System :: OS Independent
Classifier: Intended Audience :: Developers
Classifier: Development Status :: 5 - Production/Stable
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: cachetools>=3.1
Requires-Dist: cryptography>=35.0.0
Requires-Dist: domain2idna>=1.12
Requires-Dist: Flask>=2.3
Requires-Dist: grpcio>=1.59.0
Requires-Dist: humanize>=4.0
Requires-Dist: requests>=2.0
Requires-Dist: requests-hardened>=1.0
Requires-Dist: werkzeug>=2.3
Provides-Extra: tests
Requires-Dist: flask-caching>=2.1.0; extra == "tests"
Requires-Dist: httplib2>=0.18; extra == "tests"
Requires-Dist: lxml>=4.6; extra == "tests"
Requires-Dist: oauth2client>=4.1.3; extra == "tests"
Requires-Dist: oauthlib>=3.1; extra == "tests"
Requires-Dist: prawcore>=2.0; extra == "tests"
Requires-Dist: python-tumblpy>=1.1; extra == "tests"
Requires-Dist: tweepy>=4.0; extra == "tests"
Requires-Dist: ujson>=5.7.0; extra == "tests"
Requires-Dist: urllib3>=1.26; extra == "tests"
Requires-Dist: webob>=1.8; extra == "tests"
Requires-Dist: websockets>=11.0; extra == "tests"
Provides-Extra: html
Requires-Dist: beautifulsoup4>=4.11.0; extra == "html"
Requires-Dist: mf2py>=1.1.3; extra == "html"
Requires-Dist: mf2util>=0.5.2; extra == "html"
Provides-Extra: google-cloud
Requires-Dist: google-api-python-client>=2.0; extra == "google-cloud"
Requires-Dist: google-cloud-logging>=3.4.0; extra == "google-cloud"
Requires-Dist: google-cloud-ndb>=2.3.0; extra == "google-cloud"
Provides-Extra: docs
Requires-Dist: sphinx>=7.0.0; extra == "docs"
Requires-Dist: sphinx-rtd-theme>=3.0.0; extra == "docs"
Dynamic: license-file

webutil [![Circle CI](https://circleci.com/gh/snarfed/webutil.svg?style=svg)](https://circleci.com/gh/snarfed/webutil) [![Coverage Status](https://coveralls.io/repos/github/snarfed/webutil/badge.svg?branch=main)](https://coveralls.io/github/snarfed/webutil?branch=main)
===

Common utilities and handler code for Python web apps.

* Install with `pip install webutil`
* Supports Python 3.9+
* [Documentation](https://oauth-dropins.readthedocs.io/)

webutil is dedicated to the public domain. You may also use it under the [CC0 public domain dedication](https://creativecommons.org/share-your-work/public-domain/cc0/).


Contents
---
* [`flask_util`](https://oauth-dropins.readthedocs.io/en/stable/source/oauth_dropins.webutil.html#module-oauth_dropins.webutil.flask_util): [Flask](https://flask.palletsprojects.com/) decorators and handlers for caching, exception handling, regular expression URL routing, domain-wide redirects, rate limiting, headers, and serving XRD and JRD templates.
* [`logs`](https://oauth-dropins.readthedocs.io/en/stable/source/oauth_dropins.webutil.html#module-oauth_dropins.webutil.logs): Flask request handler that collects full trace logs from [Google Cloud Logging](https://cloud.google.com/logging/docs), formats them nicely, and serves them
* [`models`](https://oauth-dropins.readthedocs.io/en/stable/source/oauth_dropins.webutil.html#module-oauth_dropins.webutil.models): minor utility [Model classes](https://googleapis.dev/python/python-ndb/latest/model.html) for the [Google Cloud Datastore](https://console.cloud.google.com/datastore/) [ndb library](https://github.com/googleapis/python-ndb)
* [`testutil`](https://oauth-dropins.readthedocs.io/en/stable/source/oauth_dropins.webutil.html#module-oauth_dropins.webutil.testutil): misc utilities and helpers for [unittest](https://docs.python.org/3.9/library/unittest.html), [requests](http://python-requests.org), and [urllib](https://docs.python.org/3.9/library/urllib.html)
* [`util`](https://oauth-dropins.readthedocs.io/en/stable/source/oauth_dropins.webutil.html#module-oauth_dropins.webutil.util): wide variety of utilities for data structures, web code, etc.
* [`webmention`](https://oauth-dropins.readthedocs.io/en/stable/source/oauth_dropins.webutil.html#module-oauth_dropins.webutil.webmention): [Webmention](https://webmention.net/) endpoint discovery and sending


Changelog
---

### 2.0 - 2026-06-29

* `appengine_info`:
  * Add [Cloud Run](https://docs.cloud.google.com/run/docs/) support.
* `flask_util`:
  * Add `block`, a `before_request` handler that blocks requests by client IP CIDR and/or `User-Agent`.
* `models`:
  * Add new `WriteOnce`, `WriteOnceBlobProperty` ndb property classes. (Moved here from [arroba](https://github.com/snarfed/arroba/).)
  * Add support for `$ENCRYPTED_PROPERTY_KEY` environment variable.
* `testutil`:
  * Add new `@head_returns_200` decorator.
  * Drop `mox` from `TestCase`.
  * Add new `unittest.mock` and `requests` helpers to `TestCase`.
* `util`:
  * `requests_*`: add new `log_data` boolean kwarg.


### 1.0 - 2026-05-27

Initial PyPI release. Moved out of oauth-dropins, where it's been maintained for the last 10+ years, into its own dedicated package, `pywebutil`.


Release instructions
---
Here's how to package, test, and ship a new release.

1. Pull from remote to make sure we're at head.
    ```sh
    git checkout main
    git pull
    ```
1. Run the unit tests.
    ```sh
    source local/bin/activate.csh
    gcloud emulators firestore start --host-port=:8089 --database-mode=datastore-mode < /dev/null >& /dev/null &
    python -m unittest discover
    ```
1. Bump the version number in `pyproject.toml` and `docs/conf.py`. `git grep` the old version number to make sure it only appears in the changelog. Change the current changelog entry in `README.md` for this new version from _unreleased_ to the current date.
1. Build the docs. If you added any new modules, add them to the appropriate file(s) in `docs/source/`. Then run `./docs/build.sh`.
1. ```sh
   setenv ver X.Y
   git commit -am "release v$ver"
   ```
1. Upload to [test.pypi.org](https://test.pypi.org/) for testing.
    ```sh
    uv build
    twine upload -r pypitest dist/pywebutil-$ver.tar.gz
    ```
1. Install from test.pypi.org.
    ```sh
    cd /tmp
    python -m venv local
    source local/bin/activate.csh
    pip install --upgrade pip
    pip install -i https://test.pypi.org/simple --extra-index-url https://pypi.org/simple pywebutil
    ```
1. Smoke test that the code trivially loads and runs.
    ```sh
    python
    # run test code below
    ```
    Test code to paste into the interpreter:
    ```py
    from webutil import util
    util.__file__
    util.UrlCanonicalizer()('http://asdf.com')
    # should print 'https://asdf.com/'
    exit()
    ```
1. Tag the release in git. In the tag message editor, delete the generated comments at bottom, leave the first line blank (to omit the release "title" in github), put `### Notable changes` on the second line, then copy and paste this version's changelog contents below it.
    ```sh
    git tag -a v$ver --cleanup=verbatim
    git push
    git push --tags
    ```
1. [Click here to draft a new release on GitHub.](https://github.com/snarfed/webutil/releases/new) Enter `vX.Y` in the _Tag version_ box. Leave _Release title_ empty. Copy `### Notable changes` and the changelog contents into the description text box.
1. Upload to [pypi.org](https://pypi.org/)!
    ```sh
    twine upload dist/pywebutil-$ver.tar.gz
    ```
