studylife-cli 1.9.0__tar.gz → 1.9.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/workflows/ci.yml +18 -0
- studylife_cli-1.9.2/.github/workflows/dependabot-auto-merge.yml +77 -0
- studylife_cli-1.9.2/.github/workflows/dependabot-lockfiles.yml +80 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.gitignore +5 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/CHANGELOG.md +14 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/PKG-INFO +6 -1
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/README.md +5 -0
- studylife_cli-1.9.2/fuzz/fuzz_models_and_callback.py +68 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/pyproject.toml +6 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/_version.py +2 -2
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/login.py +6 -2
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/uv.lock +15 -1
- studylife_cli-1.9.0/.github/workflows/dependabot-auto-merge.yml +0 -33
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/ISSUE_TEMPLATE/bug_report.yml +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/ISSUE_TEMPLATE/feature_request.yml +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/dependabot.yml +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.github/workflows/scorecard.yml +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/.releaserc.json +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/LICENSE +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/SECURITY.md +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/__init__.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/cli.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/client.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/credentials.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/models.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/src/studylife_cli/tui.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/tests/test_cli.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/tests/test_client.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/tests/test_login.py +0 -0
- {studylife_cli-1.9.0 → studylife_cli-1.9.2}/tests/test_tui.py +0 -0
|
@@ -10,6 +10,11 @@ concurrency:
|
|
|
10
10
|
group: ci-${{ github.event.pull_request.number || github.ref }}
|
|
11
11
|
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
|
|
12
12
|
|
|
13
|
+
# Least-privilege default for GITHUB_TOKEN (CodeQL actions/missing-workflow-permissions):
|
|
14
|
+
# jobs that need more declare it themselves.
|
|
15
|
+
permissions:
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
13
18
|
jobs:
|
|
14
19
|
lint:
|
|
15
20
|
runs-on: ubuntu-latest
|
|
@@ -41,6 +46,19 @@ jobs:
|
|
|
41
46
|
# (unlike studylife-webhooks/studylife-developers): this is a CLI, it
|
|
42
47
|
# never runs as a server, so there's nothing to containerize.
|
|
43
48
|
# ---------------------------------------------------------------------
|
|
49
|
+
# Coverage-guided fuzzing of the API models and the login callback parser (fuzz/): a short
|
|
50
|
+
# fixed budget per run, enough to catch a crash on the next code change.
|
|
51
|
+
fuzz:
|
|
52
|
+
runs-on: ubuntu-latest
|
|
53
|
+
steps:
|
|
54
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
55
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
56
|
+
with:
|
|
57
|
+
version: "0.5.8"
|
|
58
|
+
- run: uv python install 3.12
|
|
59
|
+
- run: uv sync --frozen --group fuzz
|
|
60
|
+
- run: uv run python fuzz/fuzz_models_and_callback.py -max_total_time=30 -rss_limit_mb=1024
|
|
61
|
+
|
|
44
62
|
get-version:
|
|
45
63
|
needs: [lint, test]
|
|
46
64
|
if: (github.event_name == 'push' || github.event_name == 'workflow_dispatch')
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
name: Dependabot auto-merge
|
|
2
|
+
|
|
3
|
+
# Merges Dependabot patch and minor updates once every required check is green; majors stay
|
|
4
|
+
# manual. Runs on pull_request_target so the base branch's workflow definition and the repo
|
|
5
|
+
# secrets apply (a Dependabot-triggered pull_request run gets neither). Merges with
|
|
6
|
+
# AUTOMERGE_TOKEN, a fine-grained PAT (contents + pull requests: write): a merge performed with
|
|
7
|
+
# GITHUB_TOKEN would never start the release workflow, because GitHub does not run workflows for
|
|
8
|
+
# events caused by GITHUB_TOKEN. Deliberately no checkout - PR code is never executed here.
|
|
9
|
+
on:
|
|
10
|
+
pull_request_target:
|
|
11
|
+
# auto_merge_enabled: a PR that is already behind when someone arms auto-merge would
|
|
12
|
+
# otherwise wait for the next push to be brought up to date.
|
|
13
|
+
types: [opened, synchronize, reopened, auto_merge_enabled]
|
|
14
|
+
# Safety net for a PR armed while behind when nothing else is pushed for a while.
|
|
15
|
+
schedule:
|
|
16
|
+
- cron: "23 * * * *"
|
|
17
|
+
# The default branch requires pull requests to be up to date before they merge (ruleset).
|
|
18
|
+
# Every push to it therefore leaves the open Dependabot PRs "behind"; the update-stale job
|
|
19
|
+
# below brings them back onto the tip so their auto-merge can proceed without a manual rebase.
|
|
20
|
+
push:
|
|
21
|
+
branches: [main]
|
|
22
|
+
|
|
23
|
+
# GITHUB_TOKEN only reads PR metadata; the merge itself runs with AUTOMERGE_TOKEN (above).
|
|
24
|
+
permissions:
|
|
25
|
+
pull-requests: read
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
auto-merge:
|
|
29
|
+
if: github.event_name == 'pull_request_target' && github.event.action != 'auto_merge_enabled' && github.actor == 'dependabot[bot]'
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
env:
|
|
32
|
+
HAS_TOKEN: ${{ secrets.AUTOMERGE_TOKEN != '' }}
|
|
33
|
+
steps:
|
|
34
|
+
- id: meta
|
|
35
|
+
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0
|
|
36
|
+
with:
|
|
37
|
+
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
38
|
+
- if: env.HAS_TOKEN == 'true' && (steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor')
|
|
39
|
+
env:
|
|
40
|
+
GH_TOKEN: ${{ secrets.AUTOMERGE_TOKEN }}
|
|
41
|
+
run: gh pr merge --auto --merge "${{ github.event.pull_request.html_url }}"
|
|
42
|
+
- if: env.HAS_TOKEN != 'true'
|
|
43
|
+
run: echo "AUTOMERGE_TOKEN is not set - leaving this PR for a manual merge"
|
|
44
|
+
|
|
45
|
+
update-stale:
|
|
46
|
+
if: github.event_name == 'push' || github.event_name == 'schedule' || github.event.action == 'auto_merge_enabled'
|
|
47
|
+
runs-on: ubuntu-latest
|
|
48
|
+
env:
|
|
49
|
+
HAS_TOKEN: ${{ secrets.AUTOMERGE_TOKEN != '' }}
|
|
50
|
+
steps:
|
|
51
|
+
- if: env.HAS_TOKEN == 'true'
|
|
52
|
+
env:
|
|
53
|
+
GH_TOKEN: ${{ secrets.AUTOMERGE_TOKEN }}
|
|
54
|
+
REPO: ${{ github.repository }}
|
|
55
|
+
# The default branch requires PRs to be up to date before they merge, so every push
|
|
56
|
+
# here leaves the other open PRs "behind". This brings them back onto the tip: every
|
|
57
|
+
# non-draft PR that has auto-merge enabled or was opened by a person (a Dependabot PR
|
|
58
|
+
# left for a manual decision, i.e. a major bump, is not rebuilt on every push - whoever
|
|
59
|
+
# merges it updates it). GitHub computes the merge state lazily after a push, so UNKNOWN
|
|
60
|
+
# is polled for a while; DIRTY (conflicts) cannot be updated automatically.
|
|
61
|
+
run: |
|
|
62
|
+
for n in $(gh pr list --repo "$REPO" --state open --json number --jq '.[].number'); do
|
|
63
|
+
info="$(gh pr view "$n" --repo "$REPO" --json isDraft,author,autoMergeRequest --jq '"\(.isDraft) \(.author.login) \(.autoMergeRequest != null)"')"
|
|
64
|
+
set -- $info
|
|
65
|
+
draft=$1; author=$2; auto=$3
|
|
66
|
+
[ "$draft" = "true" ] && continue
|
|
67
|
+
if [ "$auto" != "true" ] && [ "$author" = "app/dependabot" ]; then continue; fi
|
|
68
|
+
for attempt in 1 2 3 4 5 6; do
|
|
69
|
+
state="$(gh pr view "$n" --repo "$REPO" --json mergeStateStatus --jq .mergeStateStatus)"
|
|
70
|
+
[ "$state" != "UNKNOWN" ] && break
|
|
71
|
+
sleep 10
|
|
72
|
+
done
|
|
73
|
+
echo "#$n ($author, auto-merge=$auto): $state"
|
|
74
|
+
if [ "$state" = "BEHIND" ]; then
|
|
75
|
+
gh api -X PUT "repos/$REPO/pulls/$n/update-branch" >/dev/null && echo " updated onto main" || echo "::warning::could not update #$n"
|
|
76
|
+
fi
|
|
77
|
+
done
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
name: Dependabot lock files
|
|
2
|
+
|
|
3
|
+
# Dependabot regenerates the lock file of the project whose manifest it bumped, but not the
|
|
4
|
+
# lock files of the projects that depend on it (NuGet records a reference's dependencies in
|
|
5
|
+
# the referencing project's packages.lock.json) - and its uv lock output has shipped unparsable
|
|
6
|
+
# files. Locked-mode restores then fail on the PR. This job runs on Dependabot's own PRs,
|
|
7
|
+
# regenerates every lock file with the same tool versions CI uses and pushes the result back
|
|
8
|
+
# onto the PR branch, after which the normal CI run and the auto-merge proceed.
|
|
9
|
+
#
|
|
10
|
+
# Deliberately a plain pull_request run (no pull_request_target): the PR's own restore runs in
|
|
11
|
+
# the untrusted context with a read-only GITHUB_TOKEN, and the only credential involved - the
|
|
12
|
+
# push token - is a Dependabot secret, which GitHub exposes solely to runs Dependabot triggered
|
|
13
|
+
# and only to the single step that pushes. Nothing from the PR ever sees it.
|
|
14
|
+
on:
|
|
15
|
+
pull_request:
|
|
16
|
+
types: [opened, synchronize, reopened]
|
|
17
|
+
|
|
18
|
+
permissions:
|
|
19
|
+
contents: read
|
|
20
|
+
|
|
21
|
+
jobs:
|
|
22
|
+
refresh:
|
|
23
|
+
if: github.actor == 'dependabot[bot]'
|
|
24
|
+
runs-on: ubuntu-latest
|
|
25
|
+
env:
|
|
26
|
+
HAS_TOKEN: ${{ secrets.AUTOMERGE_TOKEN != '' }}
|
|
27
|
+
HEAD_REF: ${{ github.head_ref }}
|
|
28
|
+
steps:
|
|
29
|
+
- if: env.HAS_TOKEN != 'true'
|
|
30
|
+
run: echo "AUTOMERGE_TOKEN is not set as a Dependabot secret - lock files are not refreshed automatically"
|
|
31
|
+
- if: env.HAS_TOKEN == 'true'
|
|
32
|
+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
33
|
+
with:
|
|
34
|
+
ref: ${{ github.head_ref }}
|
|
35
|
+
# Full history so the base branch's uv.lock is available locally (private repos
|
|
36
|
+
# cannot be fetched anonymously later, and the push token stays out of this step).
|
|
37
|
+
fetch-depth: 0
|
|
38
|
+
persist-credentials: false
|
|
39
|
+
- if: env.HAS_TOKEN == 'true' && hashFiles('**/packages.lock.json') != ''
|
|
40
|
+
uses: actions/setup-dotnet@a98b56852c35b8e3190ac28c8c2271da59106c68 # v6.0.0
|
|
41
|
+
with:
|
|
42
|
+
dotnet-version: "10.0.x"
|
|
43
|
+
- if: env.HAS_TOKEN == 'true' && hashFiles('**/packages.lock.json') != ''
|
|
44
|
+
name: Regenerate every packages.lock.json
|
|
45
|
+
run: |
|
|
46
|
+
set -eu
|
|
47
|
+
for lock in $(git ls-files '**/packages.lock.json' 'packages.lock.json'); do
|
|
48
|
+
proj="$(ls "$(dirname "$lock")"/*.csproj | head -1)"
|
|
49
|
+
echo "restoring $proj"
|
|
50
|
+
dotnet restore "$proj" --force-evaluate -p:RestoreLockedMode=false
|
|
51
|
+
done
|
|
52
|
+
- if: env.HAS_TOKEN == 'true' && hashFiles('uv.lock') != ''
|
|
53
|
+
uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
54
|
+
with:
|
|
55
|
+
version: "0.5.8"
|
|
56
|
+
- if: env.HAS_TOKEN == 'true' && hashFiles('uv.lock') != ''
|
|
57
|
+
name: Regenerate uv.lock
|
|
58
|
+
env:
|
|
59
|
+
BASE_REF: ${{ github.base_ref }}
|
|
60
|
+
run: |
|
|
61
|
+
# Dependabot's own uv.lock output has been unparsable, and `uv lock` refuses to start
|
|
62
|
+
# from a broken file - so resolve from the base branch's lock plus the PR's pyproject.
|
|
63
|
+
git show "origin/$BASE_REF:uv.lock" > uv.lock
|
|
64
|
+
uv python install 3.12
|
|
65
|
+
uv lock
|
|
66
|
+
- if: env.HAS_TOKEN == 'true'
|
|
67
|
+
name: Push the refreshed lock files back onto the PR
|
|
68
|
+
env:
|
|
69
|
+
PUSH_TOKEN: ${{ secrets.AUTOMERGE_TOKEN }}
|
|
70
|
+
run: |
|
|
71
|
+
# Only tracked files change here (the lock files); untracked build output stays out.
|
|
72
|
+
git add -u
|
|
73
|
+
if git diff --cached --quiet; then
|
|
74
|
+
echo "lock files already consistent"
|
|
75
|
+
exit 0
|
|
76
|
+
fi
|
|
77
|
+
git config user.name "lukislp"
|
|
78
|
+
git config user.email "koerber.lukas@t-online.de"
|
|
79
|
+
git commit -m "chore(deps): refresh the lock files after the Dependabot update"
|
|
80
|
+
git push "https://x-access-token:${PUSH_TOKEN}@github.com/${GITHUB_REPOSITORY}.git" "HEAD:refs/heads/${HEAD_REF}"
|
|
@@ -1,3 +1,17 @@
|
|
|
1
|
+
## [1.9.2](https://github.com/lukislp/studylife-cli/compare/v1.9.1...v1.9.2) (2026-09-12)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **login:** compare the callback state as bytes so non-ASCII input cannot crash the handler ([#23](https://github.com/lukislp/studylife-cli/issues/23)) ([4b8de15](https://github.com/lukislp/studylife-cli/commit/4b8de157801ee840c7081261cdd524746792283a))
|
|
7
|
+
|
|
8
|
+
## [1.9.1](https://github.com/lukislp/studylife-cli/compare/v1.9.0...v1.9.1) (2026-09-11)
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Bug Fixes
|
|
12
|
+
|
|
13
|
+
* **ci:** read-only GITHUB_TOKEN in the Dependabot auto-merge workflow ([5d4eadf](https://github.com/lukislp/studylife-cli/commit/5d4eadf42f640133e46a8b27b2b2f724e9d8e829))
|
|
14
|
+
|
|
1
15
|
# [1.9.0](https://github.com/lukislp/studylife-cli/compare/v1.8.3...v1.9.0) (2026-09-11)
|
|
2
16
|
|
|
3
17
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: studylife-cli
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.2
|
|
4
4
|
Summary: Command-line client for StudyLife (self-hosted study organizer) - notes, sessions, course goals, and webhooks from your terminal.
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Requires-Python: >=3.12
|
|
@@ -14,6 +14,11 @@ Description-Content-Type: text/markdown
|
|
|
14
14
|
|
|
15
15
|
# studylife-cli
|
|
16
16
|
|
|
17
|
+
[](https://github.com/lukislp/studylife-cli/actions/workflows/ci.yml) [](https://scorecard.dev/viewer/?uri=github.com/lukislp/studylife-cli) [](https://github.com/lukislp/studylife-cli/security/code-scanning)
|
|
18
|
+
[](https://github.com/lukislp/studylife-cli/releases)
|
|
19
|
+
[](LICENSE)
|
|
20
|
+
[](https://www.python.org/)
|
|
21
|
+
|
|
17
22
|
A command-line client for [StudyLife](https://github.com/lukislp/studylife), the self-hosted
|
|
18
23
|
study organizer. Manage notes, sessions, course goals, and webhooks from your terminal, get study
|
|
19
24
|
time reports and exports, or watch a live dashboard - scripts and shells welcome.
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
# studylife-cli
|
|
2
2
|
|
|
3
|
+
[](https://github.com/lukislp/studylife-cli/actions/workflows/ci.yml) [](https://scorecard.dev/viewer/?uri=github.com/lukislp/studylife-cli) [](https://github.com/lukislp/studylife-cli/security/code-scanning)
|
|
4
|
+
[](https://github.com/lukislp/studylife-cli/releases)
|
|
5
|
+
[](LICENSE)
|
|
6
|
+
[](https://www.python.org/)
|
|
7
|
+
|
|
3
8
|
A command-line client for [StudyLife](https://github.com/lukislp/studylife), the self-hosted
|
|
4
9
|
study organizer. Manage notes, sessions, course goals, and webhooks from your terminal, get study
|
|
5
10
|
time reports and exports, or watch a live dashboard - scripts and shells welcome.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""Atheris fuzz harness for the API models and the login callback parser.
|
|
2
|
+
|
|
3
|
+
Contract under test: the StudyLife DTO models either validate a JSON body or raise pydantic's
|
|
4
|
+
ValidationError - never anything else; the loopback callback parser and the constant-time
|
|
5
|
+
state comparison never raise for any query string.
|
|
6
|
+
|
|
7
|
+
Run locally (Linux, needs the atheris wheel):
|
|
8
|
+
uv sync --frozen --group fuzz
|
|
9
|
+
uv run python fuzz/fuzz_models_and_callback.py -max_total_time=60
|
|
10
|
+
CI runs the same harness for a short, fixed time budget (see .github/workflows/ci.yml).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from __future__ import annotations
|
|
14
|
+
|
|
15
|
+
import contextlib
|
|
16
|
+
import sys
|
|
17
|
+
|
|
18
|
+
import atheris
|
|
19
|
+
from pydantic import ValidationError
|
|
20
|
+
|
|
21
|
+
from studylife_cli.credentials import Credentials
|
|
22
|
+
from studylife_cli.login import _parse_callback_query, _states_match
|
|
23
|
+
from studylife_cli.models import (
|
|
24
|
+
Course,
|
|
25
|
+
CourseGoal,
|
|
26
|
+
MetricsSummary,
|
|
27
|
+
Note,
|
|
28
|
+
Session,
|
|
29
|
+
StudyProgramDetail,
|
|
30
|
+
StudyProgramSummary,
|
|
31
|
+
TimerState,
|
|
32
|
+
Webhook,
|
|
33
|
+
Whoami,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
MODELS = (
|
|
37
|
+
Course,
|
|
38
|
+
CourseGoal,
|
|
39
|
+
MetricsSummary,
|
|
40
|
+
Note,
|
|
41
|
+
Session,
|
|
42
|
+
StudyProgramDetail,
|
|
43
|
+
StudyProgramSummary,
|
|
44
|
+
TimerState,
|
|
45
|
+
Webhook,
|
|
46
|
+
Whoami,
|
|
47
|
+
Credentials,
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def test_one_input(data: bytes) -> None:
|
|
52
|
+
fdp = atheris.FuzzedDataProvider(data)
|
|
53
|
+
query = fdp.ConsumeUnicodeNoSurrogates(256)
|
|
54
|
+
result = _parse_callback_query(query)
|
|
55
|
+
_states_match(result.state, fdp.ConsumeUnicodeNoSurrogates(64))
|
|
56
|
+
body = fdp.ConsumeBytes(512)
|
|
57
|
+
for model in MODELS:
|
|
58
|
+
with contextlib.suppress(ValidationError):
|
|
59
|
+
model.model_validate_json(body)
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
# instrument_all() instead of instrument_imports(): the package is loaded through uv's
|
|
64
|
+
# editable-install loader, which the import hook does not see (no coverage feedback,
|
|
65
|
+
# so libFuzzer would never grow its inputs past a few bytes).
|
|
66
|
+
atheris.instrument_all()
|
|
67
|
+
atheris.Setup(sys.argv, test_one_input)
|
|
68
|
+
atheris.Fuzz()
|
|
@@ -68,3 +68,9 @@ extend-immutable-calls = ["typer.Argument", "typer.Option"]
|
|
|
68
68
|
testpaths = ["tests"]
|
|
69
69
|
asyncio_mode = "auto"
|
|
70
70
|
pythonpath = ["src"]
|
|
71
|
+
|
|
72
|
+
[dependency-groups]
|
|
73
|
+
# Fuzzing harness (fuzz/), Linux only - the atheris wheel does not exist for Windows.
|
|
74
|
+
fuzz = [
|
|
75
|
+
"atheris>=2.3",
|
|
76
|
+
]
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '1.9.
|
|
22
|
-
__version_tuple__ = version_tuple = (1, 9,
|
|
21
|
+
__version__ = version = '1.9.2'
|
|
22
|
+
__version_tuple__ = version_tuple = (1, 9, 2)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -64,8 +64,12 @@ def _parse_callback_query(query_string: str) -> CallbackResult:
|
|
|
64
64
|
|
|
65
65
|
def _states_match(received: str, expected: str) -> bool:
|
|
66
66
|
"""Constant-time comparison - the state isn't secret, but there's no reason to prefer a
|
|
67
|
-
timing-observable comparison over a safe one that's just as easy to write.
|
|
68
|
-
|
|
67
|
+
timing-observable comparison over a safe one that's just as easy to write.
|
|
68
|
+
|
|
69
|
+
Compared as UTF-8 bytes: hmac.compare_digest raises TypeError for str arguments with
|
|
70
|
+
non-ASCII characters, and `received` comes straight from whoever hits the loopback
|
|
71
|
+
callback URL (found by fuzz/fuzz_models_and_callback.py)."""
|
|
72
|
+
return hmac.compare_digest(received.encode("utf-8"), expected.encode("utf-8"))
|
|
69
73
|
|
|
70
74
|
|
|
71
75
|
_CALLBACK_PAGE = """<!doctype html>
|
|
@@ -32,6 +32,16 @@ wheels = [
|
|
|
32
32
|
{ url = "https://files.pythonhosted.org/packages/da/35/f2287558c17e29fafc8ef3daf819bb9834061cfa43bff8014f7df7f63bdc/anyio-4.14.2-py3-none-any.whl", hash = "sha256:9f505dda5ac9f0c8309b5e8bd445a8c2bf7246f3ce950121e45ea15bc41d1494", size = 125813 },
|
|
33
33
|
]
|
|
34
34
|
|
|
35
|
+
[[package]]
|
|
36
|
+
name = "atheris"
|
|
37
|
+
version = "3.1.0"
|
|
38
|
+
source = { registry = "https://pypi.org/simple" }
|
|
39
|
+
wheels = [
|
|
40
|
+
{ url = "https://files.pythonhosted.org/packages/0a/88/fd6ad595dafa9c7ce56dbfcaff0c7244988dac3af86c771166c6516ccf6b/atheris-3.1.0-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:ec5e11f21a4c197fe91f7aea2b2de88e623c73a21fc07b105ac6329a1588457b", size = 36875908 },
|
|
41
|
+
{ url = "https://files.pythonhosted.org/packages/4e/18/e19718c384fd7d801d0da7485407daef9af6194b6d8c8818175bec5efec6/atheris-3.1.0-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:f8a9f51ce8369026e8eb7b7174835e8c4c85a1a6db5d9add36c15100779d2a39", size = 36800563 },
|
|
42
|
+
{ url = "https://files.pythonhosted.org/packages/c3/ff/ae7a5bfe99033e510bea4ed09934e636d93777317a48147369bc0dc2b71f/atheris-3.1.0-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:315a0b5c819852b1ffe1ca72efc389c7724881f2c33e4aacb8c6bcec49bd5011", size = 36772569 },
|
|
43
|
+
]
|
|
44
|
+
|
|
35
45
|
[[package]]
|
|
36
46
|
name = "certifi"
|
|
37
47
|
version = "2026.7.22"
|
|
@@ -395,7 +405,7 @@ wheels = [
|
|
|
395
405
|
|
|
396
406
|
[[package]]
|
|
397
407
|
name = "studylife-cli"
|
|
398
|
-
version = "1.
|
|
408
|
+
version = "1.9.2.dev9+g1002804fa.d20260912"
|
|
399
409
|
source = { editable = "." }
|
|
400
410
|
dependencies = [
|
|
401
411
|
{ name = "httpx" },
|
|
@@ -413,6 +423,9 @@ dev = [
|
|
|
413
423
|
{ name = "respx" },
|
|
414
424
|
{ name = "ruff" },
|
|
415
425
|
]
|
|
426
|
+
fuzz = [
|
|
427
|
+
{ name = "atheris" },
|
|
428
|
+
]
|
|
416
429
|
|
|
417
430
|
[package.metadata]
|
|
418
431
|
requires-dist = [
|
|
@@ -431,6 +444,7 @@ dev = [
|
|
|
431
444
|
{ name = "respx", specifier = ">=0.21" },
|
|
432
445
|
{ name = "ruff", specifier = ">=0.7" },
|
|
433
446
|
]
|
|
447
|
+
fuzz = [{ name = "atheris", specifier = ">=2.3" }]
|
|
434
448
|
|
|
435
449
|
[[package]]
|
|
436
450
|
name = "textual"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
name: Dependabot auto-merge
|
|
2
|
-
|
|
3
|
-
# Merges Dependabot patch and minor updates once every required check is green; majors stay
|
|
4
|
-
# manual. Runs on pull_request_target so the base branch's workflow definition and the repo
|
|
5
|
-
# secrets apply (a Dependabot-triggered pull_request run gets neither). Merges with
|
|
6
|
-
# AUTOMERGE_TOKEN, a fine-grained PAT (contents + pull requests: write): a merge performed with
|
|
7
|
-
# GITHUB_TOKEN would never start the release workflow, because GitHub does not run workflows for
|
|
8
|
-
# events caused by GITHUB_TOKEN. Deliberately no checkout - PR code is never executed here.
|
|
9
|
-
on:
|
|
10
|
-
pull_request_target:
|
|
11
|
-
types: [opened, synchronize, reopened]
|
|
12
|
-
|
|
13
|
-
permissions:
|
|
14
|
-
contents: write
|
|
15
|
-
pull-requests: write
|
|
16
|
-
|
|
17
|
-
jobs:
|
|
18
|
-
auto-merge:
|
|
19
|
-
if: github.actor == 'dependabot[bot]'
|
|
20
|
-
runs-on: ubuntu-latest
|
|
21
|
-
env:
|
|
22
|
-
HAS_TOKEN: ${{ secrets.AUTOMERGE_TOKEN != '' }}
|
|
23
|
-
steps:
|
|
24
|
-
- id: meta
|
|
25
|
-
uses: dependabot/fetch-metadata@21025c705c08248db411dc16f3619e6b5f9ea21a # v2.5.0
|
|
26
|
-
with:
|
|
27
|
-
github-token: ${{ secrets.GITHUB_TOKEN }}
|
|
28
|
-
- if: env.HAS_TOKEN == 'true' && (steps.meta.outputs.update-type == 'version-update:semver-patch' || steps.meta.outputs.update-type == 'version-update:semver-minor')
|
|
29
|
-
env:
|
|
30
|
-
GH_TOKEN: ${{ secrets.AUTOMERGE_TOKEN }}
|
|
31
|
-
run: gh pr merge --auto --merge "${{ github.event.pull_request.html_url }}"
|
|
32
|
-
- if: env.HAS_TOKEN != 'true'
|
|
33
|
-
run: echo "AUTOMERGE_TOKEN is not set - leaving this PR for a manual merge"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|