Skip to content

Adding a new Support Table for an object variation

In this section, we will explain how to add a new support table for an object type. If you have created one, or need help creating one, and think it will help other people (it probably will), please open a pull request or issue on the repository.

Preparations

First, in order to generate a support table, you need to have all the applications from the Fediverse pasture installed, so

docker compose \
    --file akkoma.yml \
    --file firefish.yml \
    --file mitra.yml \
    --file mastodon41.yml \
    --file mastodon42.yml \
    --file sharkey.yml up

should bring them up. We will assume in the following that you have done this. Similarly, running

docker compose --file pasture.yml run pasture_runner

should drop you into an interactive shell of this container. We will refer to this shell as the runner shell.

Tip

The dockerfile for pasture_runner is in fediverse_pasture/dockerfiles/pasture/Dockerfile. In case of problems it is useful to check that one uses it with the python package version.

Inputs

The inputs are specified by creating an InputData object, which is defined as follows. We note that the class definition might be a bit overwhelming, so you might want to skip to the example.

inputs.types.InputData dataclass

Dataclass describing an input for an object support table

Parameters:

Name Type Description Default
title str

Title of the support table

required
frontmatter str

Frontmatter describing why the support table exists

required
examples List[Dict]

List of dictionaries being added to the object

required
filename str

Name of generated markdown file

required
support_table bool

Show a support table, i.e. one table table for all applications

False
support_title str | None

title for the entry corresponding to the activity

None
support_result Dict[str, Callable[Dict, str]]

Maps each application to the string to be shown

field(default_factory=dict)
detail_table bool

Show a detail table, i.e. a table for each applications

False
detail_title Dict[str, str]

Maps application to the title line

field(default_factory=dict)
detail_extractor Dict[str, Callable[Dict, List[str]]]

Maps application to the corresponding fields. The result will be detail_extractor["activity"] + detail_extractor[application] + ["example link"]

field(default_factory=dict)
Source code in inputs/types.py
@dataclass
class InputData:
    """Dataclass describing an input for an object support table

    :param title: Title of the support table
    :param frontmatter: Frontmatter describing why the support table exists
    :param examples: List of dictionaries being added to the object
    :param filename: Name of generated markdown file

    :param support_table: Show a support table, i.e. one table table for all applications
    :param support_title: title for the entry corresponding to the activity
    :param support_result: Maps each application to the string to be shown

    :param detail_table: Show a detail table, i.e. a table for each applications
    :param detail_title: Maps application to the title line
    :param detail_extractor: Maps application to the corresponding fields. The result will be `detail_extractor["activity"] + detail_extractor[application] + ["example link"]`
    """

    title: str
    frontmatter: str
    examples: List[Dict]
    filename: str

    support_table: bool = False
    support_title: str | None = None
    support_result: Dict[str, Callable[Dict, str]] = field(default_factory=dict)

    detail_table: bool = False
    detail_extractor: Dict[str, Callable[Dict, List[str]]] = field(default_factory=dict)
    detail_title: Dict[str, str] = field(default_factory=dict)

    @property
    def docs_path(self):
        return to_docs_path(self.filename)

    def support_for_app(self, entry: Entry, app: str):
        extractor = value_from_dict_for_app(self.support_result, app)
        return entry.apply_to(app, extractor)

    def detail_for_app(self, entry: Entry, app: str):
        extractor = value_from_dict_for_app(self.detail_extractor, app)
        return entry.apply_to(app, extractor)

    def detail_title_for_app(self, app: str):
        return value_from_dict_for_app(self.detail_title, app)

Example

We now do an example, which just changes the text of the submitted message. For, this we start with

fediverse-pasture/work/inputs/example.py
from .types import InputData

examples = [
    {
        "content": "This is my example"
    }
]

data = InputData(
    title="My example",
    frontmatter="Just testing",
    filename="example.md",
    examples=examples,
)

which is all we need to generate a page that contains the examples section.

Warning

The object needs to be named data due to the logic to collect all InputData objects defined in the file inputs/__init__.py.

We are now ready to run this in the runner shell via:

python support_table.py --name example mastodon41 mastodon42 firefish mitra

Then by running outside of the runner

cd fediverse-pasture/work
poetry install
poetry run python generate.py 

you should generate a file site/support_tables/generated/example.md. By now adding

site/mkdocs.yml
nav:
  - "Home":
      - ...
  - "Support Tables":
      - ...
      - "Objects":
          - ...
          - support_tables/generated/example.md

and running

cd site
poetry run mkdocs serve

you should be able to inspect your new support table at http://127.0.0.1:8000/support_tables/generated/example/.

Adding a support table

In order to create something, more interesting we will include in our examples the case of setting the id to null, i.e.

examples = [
    {"content": "This is my example"},
    {"content": "now without id", "id": None},
]

Next, we modify the definition of data to

data = InputData(
    title="My example",
    frontmatter="Just testing",
    filename="example.md",
    examples=examples,
    support_table=True,
    support_title="id",
    support_result={
        "activity": lambda x: x["object"]["id"] or "",
        "mastodon": lambda x: "✅" if x else "❌",
        "firefish": lambda x: "✅" if x else "❌",
    },
)

Now, following the steps to generate the support table should lead to

id firefish mastodon 4.1 mastodon 4.2 mitra Example
http://pasture_one_actor/actor/vS1EfgDabf8 Example 1
Example 2

This demonstrates that id is needed for most Fediverse applications to parse your object.

The support_result functions

The dictionary in support_result works as follows. First, activity represents the ActivityPub object been send to the the Fediverse applications. Second, the application get resolved by name. If the name doesn’t exist, it is attempted to be resolved using a profile, see

inputs.types.app_to_profile_map = {'mitra': 'mastodon', 'mastodon 4.1': 'mastodon', 'mastodon 4.2': 'mastodon', 'gotosocial': 'mastodon', 'sharkey': 'mastodon', 'akkoma': 'mastodon'} module-attribute

Then the given function is applied to the dictionary shown in example for the application. In our case, we show the id for activity and in the other cases a ✅ if the result is non null and ❌ otherwise. Inspecting the other files in inputs can lead to inspiration as the functions in inputs.utils

inputs.utils

escape_markdown(text)

Escapes markdown characters, necessary to display markdown (as done for firefish)

Source code in inputs/utils.py
def escape_markdown(text):
    """Escapes markdown characters, necessary to display markdown (as done for firefish)"""

    text = text.replace("`", "\\`")
    text = text.replace("*", "\\*")
    text = text.replace("_", "\\_")
    text = text.replace("[", "\\[")
    text = text.replace("]", "\\]")
    return text
format_as_json(data, small=False)

Displays a dictionary as pretty printed json.

Parameters:

Name Type Description Default
small

If true sets font-size to 75%.

False
Source code in inputs/utils.py
def format_as_json(data, small=False):
    """Displays a dictionary as pretty printed json.

    :param small: If true sets font-size to 75%."""
    style = "line-height:1;"
    if small:
        style += "font-size:75%;"

    return [
        "".join(
            f"<pre style='{style}'>{sanitize_backslash(x)}</pre>"
            for x in json.dumps(data, indent=2).split("\n")
        )
    ]
pre_format(text)

Escapes html text to pre formatted markdown

Source code in inputs/utils.py
def pre_format(text):
    """Escapes html text to pre formatted markdown"""
    if text is None:
        return [""]
    if isinstance(text, bool):
        return ["true" if text else "false"]
    return ["".join(f"<pre>{html.escape(x)}</pre>" for x in text.split("\n"))]
safe_first_element(item)

Returns the first element of a list, otherwise None

Source code in inputs/utils.py
def safe_first_element(item):
    """Returns the first element of a list, otherwise None"""
    if not item or not isinstance(item, list) or len(item) == 0:
        return None
    return item[0]

Building all the support tables

We note that all the support tables can be build by running

python support_table.py --all --all_apps

in the runner shell. This will store all the information in the test_results.sqlite database file. In order to generate nicer diffs in version control, this database file is not added to git instead a toml file generated from it. This can be done via

python store.py --save

The full support table command is

python support_table.py

Usage:

python support_table.py [OPTIONS] [APPS]...

Options:

  --name TEXT     Run for  single input file
  --all           Run for all input files
  --actor         Collect the actor profiles from the applications
  --verify_actor  Collect the verify actor result for the applications
  --all_apps      Run with all known apps
  --help          Show this message and exit.