Skip to main content

dwho

Shared building blocks for Python services: plugin and module lifecycles, configuration, notifications, Redis, SQL objects and Linux filesystem events. Used by Covenant and Auton. The name is a nod to Doctor Who, alongside Sonicprobe, HTTPdis (TARDIS) and Auton. This is an independent project, not an official Doctor Who product.

Where it fits

  • sonicprobe provides general helpers, workers, networking and SQL adapters.
  • httpdis provides HTTP routing, requests and responses.
  • dwho provides the shared application conventions on top of these libraries.

Use dwho when extending an existing service in this ecosystem. It is a library, not a standalone daemon or a replacement for every web framework.

Installation and compatibility

python -m pip install dwho

Linux is required for inotify. Dependencies with native code, such as pycurl, may need a compiler and libcurl development headers when wheels are unavailable. The declared interpreter range remains Python 2.7 and Python 3.5+; modern Python is preferred for new deployments. Legacy Python requires legacy dependency versions. CI records which interpreters pass; it does not certify every plugin, Redis server or database driver. pyasyncore supplies pyinotify's removed standard-library dependency on Python 3.12+.

Load trusted plugins

from dwho.classes.libloader import DwhoLibLoader
modules = DwhoLibLoader.load_dir('my_service.plugins', '/etc/my-service/plugins')

Each visible .py file (except names ending in __init__.py) executes once per module name. Later calls reuse sys.modules; this is not a hot-reload API. Modules register in sys.modules before execution, and failed loads are removed so they can be retried. Python 2.7 uses imp; modern interpreters use importlib. Plugin files execute arbitrary Python and must be trusted.

Redis

from dwho.adapters.redis import DWhoAdapterRedis

config = {'general': {'redis': {
    'cache': {'url': 'redis://127.0.0.1:6379/0'},
    'events': {'url': 'redis://127.0.0.1:6379/1'},
}}}
adapter = DWhoAdapterRedis(config)
try:
    adapter.set_key('example', 'hello', expire=60, prefix='cache')
finally:
    adapter.disconnect()

Connections are cached per server name. prefix selects configured names; write operations return a mapping of server names to results.

Notifications and filesystem events

DWhoPushNotifications(config_path=...) reads YAML files from a directory. Each has general.uri, optional general.tags, general.async and a template file path. Supported schemes are HTTP(S), Redis and subproc.

# notifications/webhook.yml
general:
  uri: 'https://example.org/events/${target}'
  tags: [all]
from dwho.classes.notifiers import DWhoPushNotifications
notify = DWhoPushNotifications(config_path='/etc/my-service/notifications')
notify({'target': 'worker'}, names=['webhook'])

Mako templates and subprocess configurations are trusted administrator inputs. Each invocation receives its own configuration and variables. Subprocess timeout is configured at the YAML top level with timeout (seconds); shutdown attempts terminate, then kill after one second, and reaps the direct child. Commands should not daemonize or leave descendants holding their output pipes.

Inotify dispatch chooses the most specific matching directory, using path components rather than string prefixes. Filtering plugins for one event no longer changes the configured list for later events.

Encryption and migration

For new data use authenticated encryption with a random 16, 24 or 32 byte key:

from Crypto.Random import get_random_bytes
from dwho.helpers.crypto import DWhoCryptoHelper
key = get_random_bytes(32)  # Persist securely; do not regenerate when reading.
token = DWhoCryptoHelper.serialize_authenticated(key, {'status': 'ready'})
assert DWhoCryptoHelper.unserialize_authenticated(key, token) == {'status': 'ready'}

The dwho-eax-v1: format uses AES-EAX and JSON serialization. Altered ciphertext, incorrect keys and legacy tokens are rejected by the authenticated reader. encrypt_authenticated / decrypt_authenticated handle raw bytes; text inputs are encoded as UTF-8 and decrypted output is bytes. JSON preserves JSON types, not arbitrary Python objects.

Existing encrypt / decrypt retain the historical CBC wire format and key truncation, including its unusual treatment of exactly 24/32 byte keys. CBC does not authenticate data. serialize uses pickle protocol 2 for legacy readers. unserialize accepts ordinary data but blocks arbitrary pickle globals by default; custom Python objects now require trusted=True. That option can execute code: use it only for known, trusted historical records, never attacker-controlled data. Do not expose legacy CBC deserialization as a public endpoint.

Migration is explicit: read trusted legacy data, write authenticated JSON, and upgrade all readers before switching writers. Older dwho cannot read EAX tokens. Store keys separately from encrypted records. This is not a password KDF.

SQL objects

DWhoObjectSQLBase keeps values bound as parameters. Column names in conditions, updates, searches and ordering must be simple identifiers (optionally qualified, e.g. jobs.id); sorting accepts ASC or DESC. SQL expressions in these fields are now rejected. Empty IN lists match nothing. A zero limit returns no rows. Use explicit reviewed queries through the SQL adapter for expression-based queries.

Development and releases

python -m pip install -e . mock
python -m unittest discover -s tests -v
python -m pip install build twine
python -m build
python -m twine check --strict dist/*

Tests use temporary files, SQLite, mocked Redis and actual local subprocesses. No production service is contacted. See .github/workflows/tests.yml for the compatibility matrix and .github/workflows/pypi.yml for release gates.

To release, update VERSION, RELEASE and both version fields in setup.yml together, then merge the tested PR. The publication workflow validates the package, creates vX.Y.Z on master and publishes the same artifacts using PyPI Trusted Publishing (decryptus/dwho, pypi.yml, environment pypi). Existing version tags are never moved. Ordinary commits on an already tagged version do not republish it.

License: GPL-3.0-or-later. See LICENSE.

See the September 2026 code and architecture review (French).

Metadata

Release files for dwho 0.3.60

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for dwho 0.3.60
File Size Uploaded
dwho-0.3.60.tar.gz 48.3 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for dwho 0.3.60
File Interpreter ABI Platform
dwho-0.3.60-py2.py3-none-any.whl Python 3, Python 2 none any Details

Total release size: 95.5 kB

Release files / dwho-0.3.60.tar.gz

Download URL dwho-0.3.60.tar.gz
Size 48.3 kB
Tags Source
SHA-256 checksum
How to use checksums
6b81251da6d831938313009aaea930a0ba02a7e18b420a5b9d37de7ab6ed1ec0
BLAKE2b-256 checksum
How to use checksums
1c7286c3fc8812d2888541b82783ee09cdc2c932035572d494b651a027365fa4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release files / dwho-0.3.60-py2.py3-none-any.whl

Download URL dwho-0.3.60-py2.py3-none-any.whl
Size 47.2 kB
Tags Python 2 Python 3
SHA-256 checksum
How to use checksums
4531e4b65334482df0eb53c18b0563b1e1292ecc30d0c65366cb7f119574ae36
BLAKE2b-256 checksum
How to use checksums
8921400092c01e8f8ad7f76ad394980e7d8f03d582e5a3986b75492081cece05
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 22, 2026.

Transparency log

Release history Release notifications | RSS feed

0.3.63

2 release files

0.3.62

2 release files

0.3.61

2 release files

This release

0.3.60 This release

2 release files

0.3.59

2 release files

0.3.56

2 release files

0.3.55

2 release files

0.3.54

2 release files

0.3.53

2 release files

0.3.49

2 release files

0.3.47

2 release files

0.3.46

2 release files

0.3.45

2 release files

0.3.44

2 release files

0.3.43

2 release files

0.3.34

2 release files

0.3.33

2 release files

0.3.32

2 release files

0.3.31

2 release files

0.3.30

2 release files

0.3.29

2 release files

0.3.28

2 release files

0.3.27

2 release files

0.3.26

2 release files

0.3.24

2 release files

0.3.23

2 release files

0.3.22

2 release files

0.3.19

2 release files

0.3.18

2 release files

0.3.17

2 release files

0.3.16

2 release files

0.3.15

2 release files

0.3.14

2 release files

0.3.12

2 release files

0.3.11

2 release files

0.3.9

2 release files

0.3.8

2 release files

0.3.7

2 release files

0.3.5

2 release files

0.3.4

2 release files

0.3.3

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.2.98

1 release file

0.2.97

1 release file

0.2.96

1 release file

0.2.94

1 release file

0.2.93

1 release file

0.2.92

1 release file

0.2.91

1 release file

0.2.90

1 release file

0.2.89

1 release file

0.2.88

1 release file

0.2.87

1 release file

0.2.86

1 release file

0.2.85

1 release file

0.2.84

1 release file

0.2.83

1 release file

0.2.82

1 release file

0.2.81

1 release file

0.2.80

1 release file

0.2.79

1 release file

0.2.77

1 release file

0.2.76

1 release file

0.2.75

1 release file

0.2.74

1 release file

0.2.73

1 release file

0.2.72

1 release file

0.2.71

1 release file

0.2.70

1 release file

0.2.69

1 release file

0.2.68

1 release file

0.2.67

1 release file

0.2.66

1 release file

0.2.65

1 release file

0.2.64

1 release file

0.2.63

1 release file

0.2.61

1 release file

0.2.60

1 release file

0.2.58

1 release file

0.2.55

1 release file

0.2.53

1 release file

0.2.52

1 release file

0.2.50

1 release file

0.2.49

1 release file

0.2.48

1 release file

0.2.47

1 release file

0.2.46

1 release file

0.2.45

1 release file

0.2.44

1 release file

0.2.43

1 release file

0.2.42

1 release file

0.2.41

1 release file

0.2.40

1 release file

0.2.39

1 release file

0.2.38

1 release file

0.2.37

1 release file

0.2.36

1 release file

0.2.35

1 release file

0.2.34

1 release file

0.2.33

1 release file

0.2.32

1 release file

0.2.31

1 release file

0.2.30

1 release file

0.2.29

1 release file

0.2.28

1 release file

0.2.27

1 release file

0.2.26

1 release file

0.2.25

1 release file

0.2.24

1 release file

0.2.22

1 release file

0.2.21

1 release file

0.2.20

1 release file

0.2.19

1 release file

0.2.18

1 release file

0.2.14

1 release file

0.2.13

1 release file

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