Skip to main content

Core functionality of bovine needed to build client to server applications

Project description


This package contains two essential parts of bovine. First it defines BovineActor, which contains all the necessities to write ActivityPub Clients. Furthermore, this package contains the cryptographic routines to verify HTTP signatures.

Furthermore, the folder examples contains a few examples on how BovineActor can be used. The cryptographic routines are used in bovine_fedi to verify signatures.

Example: Make a post aka Faking at being a Server

While ActivityPub specifies Server to Server and Client to Server, they really are just two sides of the same coin. In this example, we will work through how to use BovineActor to post a message.

Without having an ActivityPub Server supporting Client to Server, this will require a bit of setup. This setup will build a stub server that just allows other ActivityPub servers to associate us with a domain.


Stuff needed:

  • Generate Keys
  • Webfinger
  • Actor object
  • How to deploy
  • How to post

Using BovineActor

One can import it via from bovine import BovineActor. Then one can either use it via:

async with BovineActor(config) as actor:
# or
actor = BovineActor(config)
await actor.init()

Here the config object can be present in two variants. First it can contain the keys host and private_key, where host is the domain the ActivityPub Actor is on and private_key is a mutlicodec encoded Ed25519 key, whose corresponding did-key has been added to the Actor. In this case Moo-Auth-1 will be used. The second variant is to use HTTP Signatures, where the keys account_url, public_key_url, and private_key need to be present. Alternatively, to passing a config object, one can use BovineActor.from_file(path_to_toml_file).

Making a post

BovineActor contains two factories to create ActivityStreams Objects and ActivityStreams Activities. One can obtain them by running

activity_factory, object_factory = actor.factories

The simplest usage example is a create wrapping a note, that looks like:

activity_factory, object_factory = actor.factories
note = object_factory.note("Hello").as_public().build()
create = activity_factory.create(note).build()

The result should be the something equivalent to the json

  "@context": "",
  "type": "Create",
  "actor": "https://domain/actor",
  "object": {
    "attributedTo": "https://domain/actor",
    "type": "Note",
    "content": "Hello",
    "published": "2023-03-25T08:12:32Z",
    "to": "as:Public",
    "cc": "https://domain/followers_collection"
  "published": "2023-03-25T08:12:32Z",
  "to": "as:Public",
  "cc": "https://domain/followers_collection"

The details depend on the used actor and will likely contain superfluous elements until the creation process is improved. We can now send this activity to our outbox using

await actor.send_to_outbox(create)

Note: This is different from what we did in the first example, where we used await, create). The difference is that in the first example, we faked being a server, now we are actually using Client To Server.

The inbox and outbox

By running

inbox = await actor.inbox()
outbox = await actor.outbox()

one can obtain CollectionHelper objects. These are meant to make it easier to interact with collection objects. In the simplest use case, one can use

await inbox.next_item()

to get the items from the inbox one after the other. It is also possible to print a summary of all elements that have been fetched from the inbox using await inbox.summary(). Finally, it is possible to iterate over the inbox via

async for item in inbox.iterate(max_number=3):

Proxying elements

We have already seen the difference between using post directly to an inbox and posting to the actor's outbox using send_to_outbox. A similar pattern applies to fetching objects. Both of these commands often have a similar result

await actor.get(object_id)
await actor.proxy_element(object_id)

However, they do different things:

  • The first actor.get sends a webrequest to the server object_id is on and retrieves it
  • The second actor.proxy_element sends a request to the actor's server for the object. This request is then either answered from the server's object store or by the server fetching the object. The cache behavior is up to the server. Depending of the evolution of proxyUrl of an Actor, more options might be added here.

As most servers don't support Moo-Auth-1, using proxy_element is the only way to obtain foreign objects, when using it.

Event Source

The event source is demonstrated in examples/ First, the event source will be specified in a FEP to come. It provides a way to receive updates from the server, whenever a new element is added to the inbox or outbox. The basic usage is

event_source = await actor.event_source()
async for event in event_source:
    if event and
        data = json.loads(

If you plan on writing long running applications, the event source does not automatically reconnect, so you will need to implement this. mechanical_bull uses the event source in this way.

Project details

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

bovine-0.0.8.tar.gz (26.6 kB view hashes)

Uploaded Source

Built Distribution

bovine-0.0.8-py3-none-any.whl (42.5 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page