Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Recorded GitHub API responses, re-generated rather than edited by hand
tests/cassettes/** linguist-generated=true
35 changes: 35 additions & 0 deletions .github/workflows/live-api.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Run the test suite against the live GitHub API once a week, to catch
# changes on GitHub's side that the recorded cassettes can't show.
name: live-api

on:
schedule:
# Mondays at 06:00 UTC
- cron: "0 6 * * 1"
workflow_dispatch:

permissions:
contents: read

jobs:
tests:
if: github.repository == 'executablebooks/github-activity'
runs-on: ubuntu-24.04
env:
GITHUB_ACCESS_TOKEN: "${{ github.token }}"
# Identical requests are only sent once, so the run fits within the
# GITHUB_TOKEN rate limit, see tests/conftest.py
GITHUB_ACTIVITY_LIVE_TESTS: "1"

steps:
- uses: actions/checkout@v7
- uses: actions/setup-python@v7
with:
python-version: "3.14"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -e ".[testing]"

- name: Run tests
run: pytest
9 changes: 2 additions & 7 deletions .github/workflows/tests.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -41,15 +41,9 @@ jobs:

tests:
runs-on: ubuntu-24.04
env:
GITHUB_ACCESS_TOKEN: "${{ github.token }}"
strategy:
# max-parallel declared to reduce rate limitation issues
max-parallel: 1
matrix:
include:
# Only test oldest supported and latest python version to reduce
# GitHub API calls, as they can get rate limited
- python-version: 3.x
- python-version: "3.10"

Expand All @@ -63,8 +57,9 @@ jobs:
python -m pip install --upgrade pip
pip install -e ".[testing]"

# GitHub API responses are replayed from tests/cassettes/
- name: Run tests
run: pytest
run: pytest --block-network

docs:
runs-on: ubuntu-24.04
Expand Down
19 changes: 19 additions & 0 deletions docs/contribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,25 @@ This will install the local version of the package and run the test suite.
nox -s test
```

The tests don't call the GitHub API.
Instead they replay responses recorded with [`pytest-recording`](https://github.com/kiwicom/pytest-recording), which are stored in `tests/cassettes/`.

If you change the requests github-activity makes, the tests will fail with `CannotOverwriteExistingCassetteException`.
Re-record the cassettes with a GitHub token (read-only access to public repositories is enough) and commit them:

```bash
GITHUB_ACCESS_TOKEN=... nox -s test -- --record-mode=rewrite
```

Tokens are filtered out of the recordings.

Because the cassettes can't show changes on GitHub's side, the `live-api` workflow also runs the tests against the real GitHub API once a week.
To do the same locally, run:

```bash
GITHUB_ACCESS_TOKEN=... GITHUB_ACTIVITY_LIVE_TESTS=1 nox -s test
```

## Build the documentation

The easiest way to build the documentation locally is using `nox`.
Expand Down
4 changes: 2 additions & 2 deletions github_activity/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -171,12 +171,12 @@ def load_config_and_defaults(args):
setattr(args, argname, config.get(configname, ARG_DEFAULTS.get(configname)))


def main():
def main(argv=None):
if not _git_installed_check():
print("git is required to run github-activity", file=sys.stderr)
sys.exit(1)

args = parser.parse_args()
args = parser.parse_args(argv)
if args.target and args._target:
raise ValueError(
"target cannot be passed as both a positional and keyword argument"
Expand Down
28 changes: 16 additions & 12 deletions github_activity/github_activity.py
Original file line number Diff line number Diff line change
Expand Up @@ -212,25 +212,29 @@ def get_activity(
since_dt_str = f"{since_dt:%Y-%m-%dT%H:%M:%SZ}"
until_dt_str = f"{until_dt:%Y-%m-%dT%H:%M:%SZ}"

# GitHub App requests using user access tokens return no nodes for mixed
# issue/PR searches, so we search for each kind separately.
kinds = ["issue", "pr"]
if kind:
allowed_kinds = ["issue", "pr"]
if kind not in allowed_kinds:
raise ValueError(f"Kind must be one of {allowed_kinds}, got {kind}")
search_query += f" type:{kind}"
if kind not in kinds:
raise ValueError(f"Kind must be one of {kinds}, got {kind}")
kinds = [kind]

# Query for both opened and closed issues/PRs in this window
print(f"Running search query:\n{search_query}\n\n", file=sys.stderr)
query_data = []
all_bot_users = set()
for activity_type in ["created", "closed"]:
ii_search_query = (
search_query + f" {activity_type}:{since_dt_str}..{until_dt_str}"
)
qu = GitHubGraphQlQuery(ii_search_query, auth=auth)
qu.request()
query_data.append(qu.data)
# Collect bot users from each query
all_bot_users.update(qu.data.attrs.get("bot_users", set()))
for ii_kind in kinds:
ii_search_query = (
search_query
+ f" type:{ii_kind} {activity_type}:{since_dt_str}..{until_dt_str}"
)
qu = GitHubGraphQlQuery(ii_search_query, auth=auth)
qu.request()
query_data.append(qu.data)
# Collect bot users from each query
all_bot_users.update(qu.data.attrs.get("bot_users", set()))

query_data = (
pd.concat(query_data).drop_duplicates(subset=["id"]).reset_index(drop=True)
Expand Down
5 changes: 5 additions & 0 deletions github_activity/graphql.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,11 @@ def is_bot(user_dict):
self.data = pd.DataFrame(self.issues_and_or_prs)
self.data.attrs["bot_users"] = bot_users

# Issues don't have the PR only fields, so make sure the columns always exist
for column in ["mergedBy", "mergeCommit", "baseRefName", "reviews", "commits"]:
if column not in self.data:
self.data[column] = None

# Add some extra fields
def get_login(user):
return user["login"] if pd.notna(user) else user
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ Source = "https://github.com/executablebooks/github-activity"
testing = [
"pytest",
"pytest-cov",
"pytest-recording",
"pytest-regressions",
]
sphinx = [
Expand Down
Loading