API

Hub module

class flask_websub.hub.Hub(storage, celery=None, **config)[source]

This is the API to the hub package. The constructor requires a storage object, and also accepts a couple of optional configuration values (the defaults are shown as well):

  • BACKOFF_BASE=8.0: When a hub URL cannot be reached, exponential backoff is used to control retrying. This parameter scales the whole process. Lowering it means trying more frequently, but also for a shorter time. Highering it means the reverse.
  • MAX_ATTEMPTS=10: The amount of attempts the retrying process makes.
  • PUBLISH_SUPPORTED=False: makes it possible to do a POST request to the hub endpoint with mode=publish. This is nice for testing, but as it does no input validation, you should not leave this enabled in production.
  • SIGNATURE_ALGORITHM=’sha512’: The algorithm to sign a content notification body with. Other possible values are sha1, sha256 and sha384.
  • REQUEST_TIMEOUT=3: Specifies how long to wait before considering a request to have failed.
  • HUB_MIN_LEASE_SECONDS: The minimal lease_seconds value the hub will accept
  • HUB_DEFAULT_LEASE_SECONDS: The lease_seconds value the hub will use if the subscriber does not have a preference
  • HUB_MAX_LEASE_SECONDS: The maximum lease_seconds value the hub will accept

You can pass in a celery object too, or do that later using init_celery. It is required to do so before actually using the hub, though.

User-facing properties have doc strings. Other properties should be considered implementation details.

build_blueprint(url_prefix='')[source]

Build a blueprint containing a Flask route that is the hub endpoint.

cleanup_expired_subscriptions

Removes any expired subscriptions from the backing data store. It takes no arguments, and is a celery task.

counter = count(0)
endpoint_hook()[source]

Override this method to hook into the endpoint handling. Anything this method returns will be forwarded to validation functions when subscribing.

init_celery(celery)[source]

Registers the celery tasks on the hub object.

register_validator(f)[source]

Register f as a validation function for subscription requests. It gets a callback_url and topic_url as its arguments, and should return None if the validation succeeded, or a string describing the problem otherwise.

schedule_cleanup

schedule_cleanup(every_x_seconds=A_DAY): schedules the celery task cleanup_expired_subscriptions as a recurring event, the frequency of which is determined by its parameter. This is not a celery task itself (as the cleanup is only scheduled), and is a convenience function.

send_change_notification

Allows you to notify subscribers of a change to a topic_url. This is a celery task, so you probably will actually want to call hub.send_change_notification.delay(topic_url, updated_content). The last argument is optional. If passed in, it should be an object with two properties: headers (dict-like), and content (a base64-encoded string). If left out, the updated content will be fetched from the topic url directly.

class flask_websub.hub.SQLite3HubStorage(path)[source]
CLEANUP_EXPIRED_SUBSCRIPTIONS_SQL = "\n delete from hub where expiration_time <= strftime('%s', 'now')\n "
DELITEM_SQL = 'delete from hub where topic_url=? and callback_url=?'
GET_CALLBACKS_SQL = "\n select callback_url, secret from hub\n where topic_url=? and expiration_time > strftime('%s', 'now')\n "
SETITEM_SQL = "\n insert or replace into hub(topic_url, callback_url, expiration_time,\n secret)\n values (?, ?, strftime('%s', 'now') + ?, ?)\n "
TABLE_SETUP_SQL = '\n create table if not exists hub(\n topic_url text not null,\n callback_url text not null,\n secret text,\n expiration_time integer not null,\n primary key (topic_url, callback_url)\n )\n '
cleanup_expired_subscriptions()[source]

If your storage backend enforces the expiration times, there’s nothing more to do. If it does not do so by default, you should override this method, and remove all expired entries.

get_callbacks(topic_url)[source]

A generator function that should return tuples with the following values for each item in storage that has a matching topic_url:

  • callback_url
  • secret

Note that expired objects should not be yielded.

Subscriber module

class flask_websub.subscriber.Subscriber(storage, temp_storage, **config)[source]

A subscriber takes the following constructor arguments:

  • an AbstractSubscriberStorage instance for long-term data storage
  • an AbstractTempSubscriberStorage instance for short-term data storage
  • configuration values (optional); they are (with their default values):
    • REQUEST_TIMEOUT=3: Specifies how long to wait before considering a request to have failed.
    • MAX_BODY_SIZE=1024 * 1024: the maximum body size of a notification, larger requests will be rejected. The default is 1MiB.

It exposes the following methods:

  • subscribe
  • unsubscribe
  • renew
  • renew_close_to_expiration
  • cleanup

It also exposes a property: blueprint, which you can use as an argument to app.register_blueprint().

The subscriber of course also needs to be able to notify you when a notification from the hub is sent. You can register one or more functions to handle this for you. Similarly, you can register handlers for subscription successes and failures (as this is an asynchronous process, the above methods will only tell you about errors they can detect up-front.) You can pass your handler functions to:

  • add_listener; a handler should expect (topic_url, callback_url, body) as arguments on a notification.
  • add_error_handler; a handler should expect (topic_url, callback_url, reason) as arguments.
  • add_success_handler; a handler should expect (topic_url, callback_url, mode) as arguments. Mode is a string: either ‘subscribe’ or ‘unsubscribe’.
build_blueprint(url_prefix='')[source]

Build a blueprint that contains the endpoints for callback URLs of the current subscriber. Only call this once per instance. Arguments:

  • url_prefix; this allows you to prefix the callback URLs in your app.
cleanup()[source]
get_active_subscription(callback_id)[source]
renew(callback_id)[source]

Renew the subscription given by callback_id with the hub. Note that this should work even when the subscription has expired.

renew_close_to_expiration(margin_in_seconds=86400)[source]

Automatically renew subscriptions that are close to expiring, or have already expired. margin_in_seconds determines if a subscription is in fact close to expiring. By default, said margin is set to be a single day (24 hours).

This is a long-running method for any non-trivial usage of the subscriber module, as renewal requires several http requests, and subscriptions are processed serially. Because of that, it is recommended to run this method in a celery task.

safe_post_request(url, **opts)[source]
subscribe(**subscription_request)[source]

Subscribe to a certain topic. All arguments are keyword arguments. They are:

  • topic_url: the url of the topic to subscribe to.
  • hub_url: the url of the hub that the topic url links to.
  • secret (optional): a secret to use in the communication. If AUTO_SET_SECRET is enabled (and it is by default), the library creates a random secret for you, unless you override it.
  • lease_seconds (optional): the lease length you request from the hub. Note that the hub may override it. If it’s not given, the hub gets to decide by itself.
  • requests_opts (optional): allows you to pass in extra options for the initial subscribe requests. Handy when a hub e.g. demands authentication. It’s against the spec, but these things happen.

Note that, while possible, it is not always necessary to find the topic_url and hub_url yourself. If you have a WebSub-supporting URL, you can find them using the discover function. That makes calling this function as simple as:

subscriber.subscribe(**discover('http://some_websub_supporting.url'))

This function returns a callback_id. This value is an implementation detail, so you should not ascribe any meaning to it other than it being a unique identifier of the subscription.

subscribe_impl(callback_id=None, **request)[source]
unsubscribe(callback_id)[source]

Ask the hub to cancel the subscription for callback_id, then delete it from the local database if successful.

flask_websub.subscriber.discover(url, timeout=None)[source]

Discover the hub url and topic url of a given url. Firstly, by inspecting the page’s headers, secondarily by inspecting the content for link tags.

timeout determines how long to wait for the url to load. It defaults to 3.

class flask_websub.subscriber.WerkzeugCacheTempSubscriberStorage(cache)[source]
pop(callback_id)[source]

Get a subscription request as stored by __setitem__, return it, and remove the request from the store. Make sure the request has not expired!

If there is no value for callback_id, raise a KeyError.

class flask_websub.subscriber.SQLite3TempSubscriberStorage(path)[source]
CLEANUP_SQL = "\n delete from subscriber_temp where expiration_time > strftime('%s', 'now')\n "
TABLE_NAME = 'subscriber_temp'
cleanup()[source]

Remove any expired subscription requests from the store. If your backend handles this automatically, there is no need to override this method.

pop(callback_id)
class flask_websub.subscriber.SQLite3SubscriberStorage(path)[source]
CLOSE_TO_EXPIRATION_SQL = "\n select callback_id, mode, topic_url, hub_url, secret, lease_seconds\n from subscriber where expiration_time < strftime('%s', 'now') + ?\n "
TABLE_NAME = 'subscriber'
close_to_expiration(margin_in_seconds)[source]

Return an iterator of subscriptions that are near (or already past) their expiration time. margin_in_seconds specifies what ‘near’ is.

Note that the key ‘callback_id’ needs to be included in the resulting object as well!

pop(callback_id)

Publisher module

flask_websub.publisher.init_publisher(app)[source]

Calling this with your flask app as argument is required for the publisher decorator to work.

flask_websub.publisher.publisher(self_url=None, hub_url=None)[source]

This decorator makes it easier to implement a websub publisher. You use it on an endpoint, and Link headers will automatically be added. To also include these links in your template html/atom/rss (and you should!) you can use the following to get the raw links:

  • {{ websub_self_url }}
  • {{ websub_hub_url }}

And the following to get them wrapped in <link tags>:

  • {{ websub_self_link }}
  • {{ websub_hub_link }}

If hub_url is not given, the hub needs to be a flask_websub one and the hub and publisher need to share their application for the url to be auto-discovered. If that is not the case, you need to set config[‘HUB_URL’].

If self_url is not given, the url of the current request will be used. Note that this includes url query arguments. If this is not what you want, override it.

Errors module

exception flask_websub.errors.DiscoveryError[source]

For errors during canonical topic url and hub url discovery

exception flask_websub.errors.FlaskWebSubError[source]

Base class for flask_websub errors

exception flask_websub.errors.NotificationError[source]

Raised when the input of the send_change_notification task is invalid

exception flask_websub.errors.SubscriberError[source]

For errors while subscribing to a hub