repomatic.forge module¶
Read a repository’s metrics from whichever forge hosts it.
Answers one question for one repository: how many accounts follow it, when it
was created, when it last shipped, and when it was last touched. GitHub, GitLab
(on any instance) and Forgejo or Gitea (likewise) each expose that through a
different API, and repo_metrics() picks the right one from the URL’s
host.
One call per repository on every forge, which is what lets a single sampler
collect every metric repomatic.metrics records rather than one call per
metric family.
- class repomatic.forge.Forge(*values)[source]¶
Bases:
EnumForge software a host runs, which owns how to read its API.
Supporting a fourth forge means a new member plus its collector behind
metrics(), which keeps the config-side validation and the dispatch from drifting:forge_of()resolves declared values through the member lookup, so a[tool.repomatic.metrics] forgesentry naming anything else fails there rather than at sampling time.- FORGEJO = 'forgejo'¶
- GITHUB = 'github'¶
- GITLAB = 'gitlab'¶
- metrics(host, path)[source]¶
Read one repository through this forge’s API.
- Parameters:
- Return type:
- Returns:
The repository’s metrics, or
Nonewhen unreadable.- Raises:
RuntimeError – When a GitHub call fails.
- repomatic.forge.FORGE_APIS: dict[str, Forge] = {'codeberg.org': Forge.FORGEJO, 'github.com': Forge.GITHUB, 'gitlab.com': Forge.GITLAB}¶
Forge software each known host runs, which is what selects the API to call.
Never guessed from the host name: an unknown host raises instead, so a subject landing on a fourth kind of forge has to declare how to read it rather than silently sampling nothing. Extend it through
[tool.repomatic.metrics] forges, which a repository uses to name the self-hosted instances it tracks (salsa.debian.orgruns GitLab,gitlab.archlinux.orgtoo).
- repomatic.forge.FORGE_USER_AGENT = 'repomatic forge metrics collector'¶
Sent to every forge API, where a browser identity backfires.
Several self-hosted GitLab instances answer a browser user-agent with a page rather than a payload, returning kilobytes of HTML where the same URL fetched under a plain agent returns a small JSON document. Nothing errors, so the symptom is a subject quietly missing from the readings rather than a failed run.
- repomatic.forge.GITHUB_HOST = 'github.com'¶
The one host whose deep collectors exist.
An exact star reconstruction reads per-star timestamps, and an archive backfill mines
github.compages: both are GitHub-only, so a subject elsewhere is skipped by them rather than failed.
- repomatic.forge.GITHUB_METRICS_QUERY = '\nquery($owner: String!, $name: String!) {\n repository(owner: $owner, name: $name) {\n createdAt\n stargazerCount\n latestRelease { publishedAt }\n defaultBranchRef { target { ... on Commit { committedDate } } }\n refs(refPrefix: "refs/tags/", first: 1,\n orderBy: {field: TAG_COMMIT_DATE, direction: DESC}) {\n nodes {\n target {\n ... on Commit { committedDate }\n ... on Tag { target { ... on Commit { committedDate } } }\n }\n }\n }\n }\n}\n'¶
Reads a repository’s whole metric set in one call.
One call where REST needs four, and correct where REST is not:
/tagsanswers in an order nobody should assume, so a fallback trusting it can date a live project a decade into the past. Ordering onTAG_COMMIT_DATEstates the question instead of hoping the default matches it.The commit date is read off the default branch rather than from the repository’s
pushedAt, which any push to any branch bumps.
- class repomatic.forge.ForgeMetrics(stars, created=None, release=None, release_source=None, commit=None)[source]¶
Bases:
objectOne repository’s metrics, as any forge reports them.
- created: str | None = None¶
ISO date the repository was opened.
The one date a star count is known to be zero, which is what gives a history an origin and a by-age chart something to align on.
- release_source: str | None = None¶
Where
releasecame from: areleaseobject, or a baretag.Recorded because the two are not the same claim. A release is something the project announced; a tag is only the newest thing it labelled, which is the closest available answer for the many projects that never cut a release.
- commit: str | None = None¶
ISO date of the newest commit on the default branch.
The half of the activity reading that stays true for a rolling repository. A widely used package archive can go a decade without tagging a release while being committed to several times a day: a release date alone would report it as long dead.
- readings()[source]¶
Yield each metric this reading carries, as
(metric id, value).The bridge between a typed forge answer and the metric store, which holds every value as text. A metric the forge did not answer yields nothing rather than an empty string, so a project with no release adds no row instead of a blank one.
createdis deliberately absent: it is not a metric but the origin of one, recorded by the sampler as astarsreading of zero on that date.
- repomatic.forge.split_repo_url(url)[source]¶
Split a repository URL into its host and its owner/name path.
- Parameters:
url (
str) – Anhttps://host/owner/namerepository URL.- Return type:
- Returns:
The
(host, path)pair.- Raises:
ValueError – When the URL carries no host or no owner/name path.
- repomatic.forge.canonical_url(subject)[source]¶
Normalize a configured subject into the URL the store keys on.
A bare
owner/nameis GitHub, which is what a repository declaring a handful of peers writes. Anything else is already a URL and only needs its trailing decoration removed. One spelling in the store keeps a subject addressable whichever way its configuration named it.- Parameters:
subject (
str) – Anowner/nameslug or a full repository URL.- Return type:
- Returns:
The canonical
https://host/owner/nameURL.- Raises:
ValueError – When neither shape parses.
- repomatic.forge.forge_of(url, extra_forges=None)[source]¶
Name the forge software running the host of url.
- Parameters:
- Return type:
- Returns:
The
Forgethe host runs.- Raises:
ValueError – When the host is not declared anywhere, which is deliberate: a silently unsampled subject is worse than a loud one.
- repomatic.forge.newest_dated(release, tag)[source]¶
Pick whichever of a project’s newest release and newest tag is more recent.
Not a preference for releases: plenty of projects carry a tag newer than their latest release object, some by close to a year, so always reading the release would report them as idle. ISO dates compare as strings, which is the whole of the arithmetic here.
- repomatic.forge.forge_json(url)[source]¶
Read one JSON document from a forge’s public API.
Covers every forge but GitHub, whose authentication
ghalready carries. The instances read here (GitLab and Forgejo) serve their project metadata to anonymous callers, so no token is involved and none is asked for.
- repomatic.forge.github_metrics(path)[source]¶
Read a GitHub repository through
GITHUB_METRICS_QUERY.- Parameters:
path (
str) – The repository’sowner/namepath.- Return type:
- Returns:
The repository’s metrics.
- Raises:
RuntimeError – When the
ghcall fails.
- repomatic.forge.gitlab_metrics(host, path)[source]¶
Read a GitLab project, on whichever instance hosts it.
- Parameters:
- Return type:
- Returns:
The project’s metrics, or
Nonewhen unreadable.
- repomatic.forge.forgejo_metrics(host, path)[source]¶
Read a Forgejo or Gitea repository, on whichever instance hosts it.
- Parameters:
- Return type:
- Returns:
The repository’s metrics, or
Nonewhen unreadable.
- repomatic.forge.repo_metrics(url, extra_forges=None)[source]¶
Read one repository, through whichever API its host speaks.
- Parameters:
- Return type:
- Returns:
The repository’s metrics, or
Nonewhen the forge could not be read.- Raises:
ValueError – When the host declares no forge.
RuntimeError – When a GitHub call fails.