Skip to content

Update for type-safe ActivityRecord fields and legacy migration helpers#260

Open
jonathanMLDev wants to merge 4 commits into
cppalliance:developfrom
jonathanMLDev:develop
Open

Update for type-safe ActivityRecord fields and legacy migration helpers#260
jonathanMLDev wants to merge 4 commits into
cppalliance:developfrom
jonathanMLDev:develop

Conversation

@jonathanMLDev
Copy link
Copy Markdown
Collaborator

@jonathanMLDev jonathanMLDev commented Jun 2, 2026

Summary

Completes typed ActivityRecord support for the portable activity DTO used at tracker sync boundaries. Replaces untyped str fields on core.protocols.ActivityRecord with structured types and adds a shared migration layer for legacy string-keyed export/bridge payloads.

Addresses the valid scope of the issue: typed occurred_at, activity_type, source_system, and actor_external_id, plus legacy migration and Pyright boundary checks. Out of scope: Slack/mailing ActivityRecord adopters, eval narrative updates.

Changes

core/activity_types (new)

  • SourceSystemStrEnum (github, discord)
  • ActivityType — branded frozen dataclass with github_issue() and discord_message(subtype) factories
  • ActorExternalIdNewType + actor_external_id() helper
  • LegacyActivityRecordDictTypedDict for export/bridge JSON
  • parse_activity_occurred_at / ensure_activity_occurred_at — aware UTC datetimes via core.utils.datetime_parsing
  • migrate_legacy_activity_fields — string/legacy input → typed tuple
  • activity_record_to_legacy_dict — inverse for to_legacy_dict() on dataclasses

core/protocols

  • ActivityRecord protocol properties now use the types above (occurred_at: datetime | None)

Tracker implementations

  • github_activity_tracker/protocol_impl.pyGitHubActivityRecord uses typed fields; from_issue, from_legacy_dict, to_legacy_dict
  • discord_activity_tracker/protocol_impl.pyDiscordActivityRecord uses typed fields; from_converted_export_dict, to_legacy_dict

Tests and typing

  • core/tests/test_activity_types.py — parse, migrate, round-trip, unknown source_system
  • core/pyright_samples/activity_record_assignment_negative.py — Pyright-negative sample for invalid ActivityRecord assignments
  • core/tests/test_protocols.py — existing subprocess Pyright test wired to the new sample

Docs

  • core/README.md — lists activity_types.py
  • CHANGELOG.md — already notes ActivityRecord typing under [Unreleased]

Test plan

  • pytest core/tests/test_activity_types.py core/tests/test_protocols.py discord_activity_tracker/tests/test_protocol_impl.py github_activity_tracker/tests/test_protocol_impl.py
  • Pyright clean on core/activity_types.py, core/protocols.py, both protocol_impl modules, and activity_record_assignment_negative.py
  • CI: lint, pyright, test jobs

Notes

  • Legacy Discord/GitHub export paths still emit/consume ISO Z strings via to_legacy_dict() / migrate_legacy_activity_fields; no ORM migration required (ActivityRecord is not a Django model).
  • Only GitHub and Discord implement ActivityRecord today; other collectors unchanged.

Related issue

Summary by CodeRabbit

  • New Features

    • Introduced structured type system for activity records with strongly-typed source systems, activity types, and actor IDs.
    • Added timezone-aware UTC timestamp handling for activity occurrence times.
    • Added migration utilities to convert between legacy and typed activity record formats.
  • Refactor

    • Updated activity tracker implementations to use typed fields instead of string-based values.
  • Documentation

    • Updated public API documentation to reflect new activity type definitions.
  • Tests

    • Added comprehensive test coverage for activity type parsing, migration, and serialization.
  • Chores

    • Updated dependency constraints for stability.

@jonathanMLDev jonathanMLDev self-assigned this Jun 2, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements type safety for the ActivityRecord protocol by introducing a new core.activity_types module with typed primitives (ActivityType, SourceSystem, ActorExternalId), updating the protocol definition, refactoring GitHub and Discord implementations to use these types, validating with comprehensive tests and Pyright negative cases, and updating documentation.

Changes

Activity Record Type Safety

Layer / File(s) Summary
Core activity types and migration helpers
core/activity_types.py
ActivityType, SourceSystem(StrEnum), ActorExternalId NewType, and helpers for parsing/formatting occurred_at datetimes; migrate_legacy_activity_fields and activity_record_to_legacy_dict provide bidirectional conversion between legacy string-keyed payloads and typed components.
ActivityRecord protocol update
core/protocols.py
Added imports and updated ActivityRecord property stubs: source_system: strSourceSystem, occurred_at: strdatetime | None, activity_type: strActivityType, actor_external_id: strActorExternalId.
GitHub protocol implementation with typed fields
github_activity_tracker/protocol_impl.py
Refactored GitHubActivityRecord to use typed fields; added __post_init__ for datetime normalization, to_legacy_dict() for serialization, and from_legacy_dict() constructor; updated from_issue to use migration helper.
Discord protocol implementation with typed fields
discord_activity_tracker/protocol_impl.py
Refactored DiscordActivityRecord to use typed fields; added __post_init__ and to_legacy_dict(); refactored from_converted_export_dict() to defensively extract fields and use migration helper for normalization.
Core activity types tests
core/tests/test_activity_types.py
Comprehensive tests for parse_activity_occurred_at, ensure_activity_occurred_at, ActivityType factories, actor ID coercion, legacy field migration, round-trip serialization, and error handling for unknown source systems.
Protocol and negative-case type checking tests
core/tests/test_protocols.py, core/pyright_samples/activity_record_assignment_negative.py
Updated protocol test imports and Discord assertions; added Pyright negative-case sample showing intentionally invalid occurred_at: str typing; added Pyright test runner that validates type checking catches the violation.
Discord protocol implementation tests
discord_activity_tracker/tests/test_protocol_impl.py
Updated test imports and assertions to validate typed source_system, activity_type, and actor_external_id fields.
Documentation and dependency updates
CHANGELOG.md, core/README.md, docs/Core_public_API.md, docs/service_api/core_protocols.md, requirements.in
CHANGELOG entry describing protocol updates; core/README adds activity_types.py to module list; public API and protocol docs updated to reflect field type changes; PyJWT constraint added for transitive dependency management.

Sequence Diagram(s)

sequenceDiagram
  participant LegacyDict as Legacy String Dict
  participant MigrateFn as migrate_legacy_activity_fields
  participant TypedFields as Typed Components
  participant SerializeFn as activity_record_to_legacy_dict
  participant NewDict as New Legacy Dict
  LegacyDict->>MigrateFn: source_system, occurred_at, activity_type, actor_id
  MigrateFn->>TypedFields: SourceSystem, datetime, ActivityType, ActorExternalId
  TypedFields->>SerializeFn: normalized typed values
  SerializeFn->>NewDict: formatted iso strings, enum values
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • cppalliance/boost-data-collector#202: Introduces typed core.activity_types primitives and updates core.protocols.ActivityRecord definition, directly evolving the portable protocol shape.
  • cppalliance/boost-data-collector#217: Adds the scripts/generate_service_docs.py tooling that auto-generates core_protocols documentation from the updated core/protocols.py.

Suggested reviewers

  • wpak-ai

Poem

🐰 Types ascend where strings once dwelt,
DateTime zones and enums felt,
Legacy dicts to typed flight,
GitHub, Discord—now typed right! ✨
Migration helpers keep data tight. 🔐

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: updating ActivityRecord fields to use type-safe types and adding legacy migration helpers.
Linked Issues check ✅ Passed The PR fully addresses all acceptance criteria from #252: occurred_at is now datetime | None [252], activity_type is ActivityType enum [252], source_system is SourceSystem StrEnum [252], actor_external_id is ActorExternalId NewType [252], migration helpers provided [252], and Pyright type checking added [252].
Out of Scope Changes check ✅ Passed All changes are directly scoped to #252 objectives: new core/activity_types module, typed ActivityRecord protocol, GitHub and Discord tracker implementations updated with typed fields and conversion helpers, comprehensive tests added, and documentation updated. Requirements.in PyJWT constraint is a minimal dependency fix supporting the work.
Description check ✅ Passed The pull request description comprehensively covers the purpose, changes across all modified files, test execution, and related documentation updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@jonathanMLDev
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

✅ Actions performed

Full review triggered.

@jonathanMLDev
Copy link
Copy Markdown
Collaborator Author

@coderabbitai full review again

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 2, 2026

@jonathanMLDev: Sure, triggering a full review of the PR now!

✅ Actions performed

Full review triggered.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/tests/test_protocols.py (1)

108-144: 💤 Low value

Python version inconsistency between negative Pyright tests.

This test uses pythonVersion: "3.13" (line 118) while test_pyright_negative_protocol_assignment_file uses pythonVersion: "3.11" (line 158). Consider aligning both to the same version for consistency.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/tests/test_protocols.py` around lines 108 - 144, The test function
test_pyright_negative_activity_record_assignment_file sets "pythonVersion":
"3.13" in the pyright config JSON, which is inconsistent with
test_pyright_negative_protocol_assignment_file that uses "3.11"; update the
pythonVersion in the JSON written in
test_pyright_negative_activity_record_assignment_file to match the other test
(e.g., "3.11") so both negative Pyright tests use the same Python version.
core/activity_types.py (1)

93-101: ⚡ Quick win

Redundant conversion: avoid converting datetime to string and back.

On line 98, when raw is already a datetime, the code converts it to a string (str(raw)) only to parse it back. This is inefficient and unnecessary—just call ensure_activity_occurred_at(raw) directly on line 97.

♻️ Proposed refactor to eliminate redundant conversion
 def _parse_occurred_at(raw: str | datetime | None) -> datetime | None:
     if raw is None:
         return None
     if isinstance(raw, datetime):
         return ensure_activity_occurred_at(raw)
-    text = str(raw).strip()
+    text = raw.strip()
     if not text:
         return None
     return parse_activity_occurred_at(text)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@core/activity_types.py` around lines 93 - 101, The _parse_occurred_at
function currently converts the input to string and parses it even when raw is
already a datetime; instead, detect datetime first and early-return
ensure_activity_occurred_at(raw) so you never call str(raw) for datetime values.
Update _parse_occurred_at to call ensure_activity_occurred_at(raw) and return
immediately when isinstance(raw, datetime), and only run str(raw).strip() +
parse_activity_occurred_at(text) for non-datetime inputs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@core/tests/test_activity_types.py`:
- Around line 45-48: The test test_ensure_activity_occurred_at_converts_non_utc
is using a UTC datetime but should verify conversion from a non-UTC zone; change
the input datetime in the test to use a non-UTC tzinfo (for example
timezone(timedelta(hours=-5))) and call ensure_activity_occurred_at, then assert
the returned datetime has tzinfo == timezone.utc and that the instant/time value
was correctly normalized to UTC (i.e., the converted time represents the same
absolute moment as the original non-UTC input).

---

Nitpick comments:
In `@core/activity_types.py`:
- Around line 93-101: The _parse_occurred_at function currently converts the
input to string and parses it even when raw is already a datetime; instead,
detect datetime first and early-return ensure_activity_occurred_at(raw) so you
never call str(raw) for datetime values. Update _parse_occurred_at to call
ensure_activity_occurred_at(raw) and return immediately when isinstance(raw,
datetime), and only run str(raw).strip() + parse_activity_occurred_at(text) for
non-datetime inputs.

In `@core/tests/test_protocols.py`:
- Around line 108-144: The test function
test_pyright_negative_activity_record_assignment_file sets "pythonVersion":
"3.13" in the pyright config JSON, which is inconsistent with
test_pyright_negative_protocol_assignment_file that uses "3.11"; update the
pythonVersion in the JSON written in
test_pyright_negative_activity_record_assignment_file to match the other test
(e.g., "3.11") so both negative Pyright tests use the same Python version.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a740a90b-1853-4914-9356-9e40dd7b06db

📥 Commits

Reviewing files that changed from the base of the PR and between c6be808 and b33beaa.

⛔ Files ignored due to path filters (2)
  • requirements-dev.lock is excluded by !**/*.lock
  • requirements.lock is excluded by !**/*.lock
📒 Files selected for processing (13)
  • CHANGELOG.md
  • core/README.md
  • core/activity_types.py
  • core/protocols.py
  • core/pyright_samples/activity_record_assignment_negative.py
  • core/tests/test_activity_types.py
  • core/tests/test_protocols.py
  • discord_activity_tracker/protocol_impl.py
  • discord_activity_tracker/tests/test_protocol_impl.py
  • docs/Core_public_API.md
  • docs/service_api/core_protocols.md
  • github_activity_tracker/protocol_impl.py
  • requirements.in

Comment thread core/tests/test_activity_types.py
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Type safety: str → proper types in ActivityRecord

1 participant