feat: implement GroupMe SDK with groups, members, messages #2
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "1-feat-implement-groupme-sdk-with-groups-m"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Pure Python SDK wrapping the GroupMe REST API (
api.groupme.com/v3). Usesurllib.requestwithX-Access-Tokenheader auth. Follows minio-sdk patterns for project structure, packaging, and CI.Changes
src/groupme_sdk/client.py-- HTTP client with_requestbase method, token auth via env var or constructorsrc/groupme_sdk/exceptions.py-- Exception hierarchy: GroupMeError, GroupMeHTTPError, GroupMeNotFoundErrorsrc/groupme_sdk/groups.py-- create_group, list_groups, get_group, update_group, destroy_groupsrc/groupme_sdk/members.py-- add_member (by phone/email/user_id), remove_member, list_memberssrc/groupme_sdk/messages.py-- send_message with uuid4 source_guidsrc/groupme_sdk/__init__.py-- re-exports GroupMeClient and exceptionstests/test_groups.py-- 11 unit tests for group operations (mocked HTTP)tests/test_members.py-- 9 unit tests for member operations (mocked HTTP)tests/test_messages.py-- 7 unit tests for messages + client init (mocked HTTP)tests/test_integration.py-- Full lifecycle test (create group, send message, list members, update, destroy)pyproject.toml-- hatchling build, ruff lint, pytest config.woodpecker.yaml-- CI: lint, test (unit only), publish to Forgejo PyPI on main.gitignore-- Python bytecode, venv, build artifactsTest Plan
pytest tests/ -v -m "not integration"Review Checklist
Related
plan-wkqCloses #1
Self-Review
Reviewed full diff (852 additions across 14 files). No issues found.
Checklist
from e), response body preserved for debugging_requestextractsresponsefield from GroupMe JSON envelopeurllib.request.urlopen, no network callsNo changes needed. Ready for human review.
PR #2 Review
DOMAIN REVIEW
Tech stack: Pure Python SDK wrapping the GroupMe REST API. Uses
urllib.request(no external HTTP deps), hatchling for packaging, ruff for lint/format, pytest for tests, Woodpecker CI for lint/test/publish. Follows the minio-sdk pattern established for internal SDKs.Architecture quality: Clean mixin pattern (
GroupsMixin,MembersMixin,MessagesMixin) composed intoGroupMeClientvia multiple inheritance.TYPE_CHECKINGguard avoids circular imports for theself: GroupMeClienttype hints on mixin methods. Exception hierarchy is well-structured:GroupMeError->GroupMeHTTPError->GroupMeNotFoundError. The_requestmethod correctly unwraps the GroupMe API envelope (responsefield).Python/PEP compliance:
from __future__ import annotationsused consistently. Good.E,F,W,Irules,py312, 120 line length). Good.Test coverage: 27 unit tests across 3 test files + 1 integration test. Coverage spans:
CI config: Matches minio-sdk pattern. Lint -> test (unit only,
-m "not integration") -> publish on main. Secrets handled viafrom_secret. No credentials in code.Comparison to minio-sdk pattern:
.woodpecker.yamlvs minio-sdk's.woodpecker.yml-- different extension but Woodpecker accepts both)src/groupme_sdk/)BLOCKERS
1. DRY violation: test helper functions duplicated 3x
_make_client(),_mock_response(), and_mock_http_error()are copy-pasted identically acrosstests/test_groups.py,tests/test_members.py, andtests/test_messages.py. This is the exact same anti-pattern called out in the basketball-api PR #96 epilogue item #4 ("DRY:_make_clienttest helper duplicated -- identical helper across 3 test files. Extract totests/conftest.py").Extract all three helpers to
tests/conftest.pyas fixtures or module-level functions. This prevents divergence if the mock pattern needs updating (e.g., adding timeout handling to_mock_response).This is a BLOCKER per the generic code quality checklist (DRY violation). While not in an auth/security path, three identical 15-line functions across three files is significant enough duplication to block. The platform has already documented this exact pattern as a known debt in another SDK.
NITS
Missing
README.md--pyproject.tomldeclaresreadme = "README.md"but no README is included in the PR. The build will succeed (hatchling treats missing readme as empty), but the Forgejo PyPI page and the repo landing page will have no documentation. Add at minimum: purpose, install instructions, usage example, env var reference.Dead
noqacomments --tests/test_messages.pyhas two# noqa: B017comments (lines catching broadException). However, the ruff config only enables["E", "F", "W", "I"]-- theB(flake8-bugbear) ruleset is not active. Thesenoqacomments suppress nothing. Either enableBrules in ruff config or remove the dead comments.File extension inconsistency -- This repo uses
.woodpecker.yamlwhile minio-sdk uses.woodpecker.yml. Both work, but consistency across internal SDKs helps when scripting or grepping across repos. Suggest aligning on one.list_groupsquery param construction --f"/groups?page={page}&per_page={per_page}"constructs query parameters via string interpolation. For an SDK that may gain more complex query params later, considerurllib.parse.urlencodefor proper escaping. Not a risk today (both params are ints), but a pattern to watch.send_messagereturn fallback --result.get("message", result)falls back to the entire response envelope if"message"key is missing. This silently returns unexpected data rather than failing explicitly. Consider raisingGroupMeErrorif the expected key is absent.SOP COMPLIANCE
1-feat-implement-groupme-sdk-with-groups-mreferences issue #1plan-wkqreferencedforgejo_admin/basketball-api#155referencedfeat: implement GroupMe SDK with groups, members, messages)Note on plan phase: The plan
plan-wkqdoes not have a dedicated GroupMe phase. The SDK work is tracked viabasketball-api#155which is a standalone issue. This is acceptable -- not all work requires a plan phase perfeedback_todos_plan_pipeline.md.PROCESS OBSERVATIONS
VERDICT: NOT APPROVED
One blocker: test helper duplication (3 identical functions across 3 files). Extract
_make_client(),_mock_response(), and_mock_http_error()totests/conftest.py. The missing README is a strong nit that should also be addressed before merge.QA blocker and nits addressed in commit
78f9f8a:Blocker fix: Extracted
_make_client(),_mock_response(), and_mock_http_error()from all three test files intotests/conftest.pyas shared helpers (make_client,mock_response,mock_http_error). Removed the leading underscore since they are now module-level public helpers imported explicitly.Nit fixes:
# noqa: B017comments fromtest_messages.py(B ruleset not enabled in ruff config).woodpecker.yamlto.woodpecker.ymlto match platform patternAll 27 unit tests pass. Ruff lint and format checks clean.
PR #2 Review (Re-review)
Previous review was NOT APPROVED with one blocker: test helper DRY violation (copy-pasted helpers across 3 test files). This re-review verifies the fix.
BLOCKER FIX VERIFICATION
The blocker is resolved. Specifically:
tests/conftest.pynow centralizes three shared helpers:make_client(),mock_response(), andmock_http_error(). All have proper type hints and docstrings.test_groups.py,test_members.py, andtest_messages.pyall usefrom tests.conftest import make_client, mock_http_error, mock_response. No copy-pasted helper definitions remain.noqacomments in the diff..woodpecker.yml(correct extension, not.yaml).DOMAIN REVIEW
Tech stack: Pure Python SDK, urllib.request, hatchling build, pytest, ruff, Woodpecker CI.
Python/PEP compliance:
self: GroupMeClientin mixins with properTYPE_CHECKINGguard. Clean.from __future__ import annotationsused consistently across all modules.Security:
X-Access-Tokenheader, not query parameter. Correct.GROUPME_ACCESS_TOKENis not set.Error handling:
GroupMeError->GroupMeHTTPError->GroupMeNotFoundError.urllib.error.URLErrorcaught and wrapped.HTTPErrorbody read and preserved._request.Test coverage: 27 unit tests across groups (11), members (9), messages + client init (7). Coverage includes happy paths, 404/401/500 error paths, edge cases (empty groups, missing identifiers, multiple identifiers), and request body verification (source_guid). Plus 1 integration test with try/finally cleanup. Solid.
BLOCKERS
None.
NITS
Imprecise exception assertion (
tests/test_messages.py,TestSendMessage.test_send_message_to_nonexistent_group): Usespytest.raises(Exception)instead ofpytest.raises(GroupMeNotFoundError). Every other 404 test in the suite uses the specific exception type. This one should match for consistency and to catch regressions where the exception type changes.Inline query params (
src/groupme_sdk/groups.py,list_groups): Query parameters are built via f-string (f"/groups?page={page}&per_page={per_page}"). This works for integer params but would break if values needed URL encoding. Considerurllib.parse.urlencodefor future-proofing. Non-blocking since both params are integers.SOP COMPLIANCE
1-feat-implement-groupme-sdk-with-groups-mreferences issue #1)plan-wkq) and parent issue (forgejo_admin/basketball-api#155).woodpecker.yml(correct extension)PROCESS OBSERVATIONS
VERDICT: APPROVED