Python Library Usage
March 11, 2026 ยท View on GitHub
Good Egg can be used as a Python library to integrate trust scoring into your own applications, bots, or CI pipelines.
Prerequisites
- Python 3.12 or later
- A GitHub personal access token (classic or fine-grained) with read access to public repositories
Installation
pip install good-egg
Basic Usage
The main entry point is the score_pr_author async function:
import asyncio
import os
from good_egg import score_pr_author
async def main() -> None:
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
token=os.environ["GITHUB_TOKEN"],
)
print(f"User: {result.user_login}")
print(f"Trust level: {result.trust_level}")
if result.flags.get("scoring_skipped"):
pr_count = result.scoring_metadata.get("context_repo_merged_pr_count", 0)
print(f"Scoring skipped -- {pr_count} merged PRs in repo")
else:
print(f"Score: {result.normalized_score:.2f}")
print(f"Merged PRs: {result.total_merged_prs}")
print(f"Unique repos: {result.unique_repos_contributed}")
asyncio.run(main())
Function Signature
async def score_pr_author(
login: str,
repo_owner: str,
repo_name: str,
config: GoodEggConfig | None = None,
token: str | None = None,
cache: object | None = None,
) -> TrustScore:
Parameters:
| Parameter | Type | Description |
|---|---|---|
login | str | GitHub username to score |
repo_owner | str | Owner of the context repository |
repo_name | str | Name of the context repository |
config | GoodEggConfig | None | Custom configuration; defaults are used when None |
token | str | None | GitHub API token; falls back to GITHUB_TOKEN env var |
cache | object | None | Cache instance for response caching (see Cache Usage) |
Skipping Known Contributors
By default, score_pr_author checks whether the user already has merged PRs
in the target repository. If so, it returns immediately with a trust level of
EXISTING_CONTRIBUTOR without running the full scoring pipeline. To force
full scoring:
from good_egg import GoodEggConfig, score_pr_author
config = GoodEggConfig(skip_known_contributors=False)
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
config=config,
)
When scoring is skipped, result.flags["scoring_skipped"] is True and
result.scoring_metadata["context_repo_merged_pr_count"] contains the
number of merged PRs found.
Custom Configuration
Pass a GoodEggConfig to customize scoring behaviour:
from good_egg import GoodEggConfig, score_pr_author
config = GoodEggConfig(
thresholds={"high_trust": 0.8, "medium_trust": 0.4},
graph_scoring={"alpha": 0.9},
recency={"half_life_days": 90},
)
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
config=config,
)
Scoring Model Selection
The default model is v3 (Diet Egg). To use an older model, set
scoring_model on the config:
from good_egg import GoodEggConfig, score_pr_author
# v3 (default) -- merge rate only
config = GoodEggConfig()
# v2 -- graph + merge rate + account age
config = GoodEggConfig(scoring_model="v2")
# v1 -- graph only
config = GoodEggConfig(scoring_model="v1")
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
config=config,
)
# v3 and v2 results include component scores
if result.component_scores:
print(f"Merge rate: {result.component_scores.get('merge_rate')}")
# v3 includes a fresh account advisory
if result.fresh_account and result.fresh_account.is_fresh:
print(f"Fresh account: {result.fresh_account.account_age_days} days old")
print(f"Scoring model: {result.scoring_model}")
You can also load configuration from a YAML file:
from good_egg.config import load_config
from good_egg import score_pr_author
config = load_config(".good-egg.yml")
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
config=config,
)
Return Type: TrustScore
The score_pr_author function returns a TrustScore Pydantic model with
the following fields:
| Field | Type | Description |
|---|---|---|
user_login | str | GitHub username that was scored |
context_repo | str | Repository used as scoring context |
raw_score | float | Pre-normalization score: merge rate (v3), logit (v2), or graph score (v1) |
normalized_score | float | Normalized score (0.0 - 1.0) |
trust_level | TrustLevel | HIGH, MEDIUM, LOW, UNKNOWN, BOT, or EXISTING_CONTRIBUTOR |
account_age_days | int | Age of the GitHub account in days |
total_merged_prs | int | Total number of merged pull requests |
unique_repos_contributed | int | Number of distinct repositories |
top_contributions | list[ContributionSummary] | Top repositories contributed to |
language_match | bool | Whether the user's top language matches the context repo |
flags | dict[str, bool] | Flags (is_bot, is_new_account, etc.) |
scoring_model | str | Scoring model used: v1, v2, or v3 |
component_scores | dict[str, float] | Component breakdown (v3: merge_rate; v2: graph_score, merge_rate, log_account_age) |
scoring_metadata | dict[str, Any] | Internal scoring details |
fresh_account | FreshAccountAdvisory | None | Advisory for accounts under 365 days old (None for bots and existing contributors) |
TrustScore is a Pydantic model, so you can serialize it:
# To dict
data = result.model_dump()
# To JSON string
json_str = result.model_dump_json()
Cache Usage
Pass a Cache instance to avoid redundant GitHub API calls across
multiple scoring operations:
from good_egg.cache import Cache
from good_egg.config import load_config
from good_egg import score_pr_author
config = load_config()
cache = Cache(ttls=config.cache_ttl.to_seconds())
try:
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
config=config,
cache=cache,
)
finally:
cache.close()
The cache is backed by SQLite and persists between runs. Cache TTLs are
configured in the cache_ttl section of the configuration file.
Error Handling
Good Egg defines a hierarchy of exceptions in good_egg.exceptions:
from good_egg.exceptions import (
GoodEggError,
GitHubAPIError,
RateLimitExhaustedError,
UserNotFoundError,
RepoNotFoundError,
CacheError,
ConfigError,
InsufficientDataError,
)
Exception Hierarchy
GoodEggError (base)
GitHubAPIError (status_code, rate_limit_remaining)
RateLimitExhaustedError (reset_at)
UserNotFoundError (login)
RepoNotFoundError (repo)
CacheError
ConfigError
InsufficientDataError
Example
from good_egg import score_pr_author
from good_egg.exceptions import (
RateLimitExhaustedError,
UserNotFoundError,
)
try:
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
)
except UserNotFoundError as exc:
print(f"User {exc.login} not found")
except RateLimitExhaustedError as exc:
print(f"Rate limited until {exc.reset_at.isoformat()}")
except GoodEggError as exc:
print(f"Scoring failed: {exc}")
Async Patterns
score_pr_author is an async function. If you are calling it from
synchronous code, use asyncio.run():
import asyncio
from good_egg import score_pr_author
result = asyncio.run(
score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
)
)
If you already have a running event loop (e.g. inside a web framework),
call it directly with await:
result = await score_pr_author(
login="octocat",
repo_owner="octocat",
repo_name="Hello-World",
)