Skip to content

System integration link: address PR review follow-ups#7550

Merged
adamsachs merged 8 commits intomainfrom
asachs/system-integration-followups
Mar 4, 2026
Merged

System integration link: address PR review follow-ups#7550
adamsachs merged 8 commits intomainfrom
asachs/system-integration-followups

Conversation

@adamsachs
Copy link
Copy Markdown
Contributor

@adamsachs adamsachs commented Mar 3, 2026

Ticket ENG-2590

Description Of Changes

Address follow-up items from @galvana's code review on #7432 (system integration link). These are code quality improvements with no behavioral changes:

  • Eliminate lazy-load footgun: from_orm no longer accesses obj.system (which could trigger an implicit lazy load). System info is now passed explicitly via SQL joins in the repository layer.
  • Detach-safe resolve methods: resolve_connection_config and resolve_system now return lightweight ConnectionConfigRef/SystemRef dataclasses instead of ORM models, preventing DetachedInstanceError if called without an explicit session.
  • Reduce route handler duplication: Added SystemLinkResponse.from_entity() classmethod, used in both get_system_links and set_system_links.
  • Fix validation order: Moved TooManyLinksError check after connection config lookup in set_links, so a missing connection returns 404 instead of 400.
  • Remove dead code: Removed empty __table_args__ from SystemConnectionConfigLink model and unnecessary cleanup_links autouse test fixtures.

Code Changes

  • src/fides/system_integration_link/entities.py - Added ConnectionConfigRef and SystemRef dataclasses; updated from_orm to accept explicit system info params
  • src/fides/system_integration_link/repository.py - Read methods use SQL joins instead of joinedload; write methods use _to_entity_with_system helper; resolve methods return ref entities
  • src/fides/system_integration_link/routes.py - Use SystemLinkResponse.from_entity() in both handlers
  • src/fides/system_integration_link/schemas.py - Added SystemLinkResponse.from_entity() classmethod
  • src/fides/system_integration_link/service.py - Reordered validation: connection config lookup before length check
  • src/fides/system_integration_link/models.py - Removed empty __table_args__
  • tests/system_integration_link/test_repository.py - Removed cleanup_links autouse fixture and unused import
  • tests/system_integration_link/test_routes.py - Removed cleanup_links autouse fixture and unused import
  • tests/system_integration_link/test_service.py - Removed cleanup_links autouse fixture and unused import

Steps to Confirm

  1. All existing system_integration_link tests pass without modification (behavior unchanged)
  2. No new lazy-load warnings in logs when using link repository methods
  3. Confirmed that system/integration linking functionality exposed in admin UI still works as expected 👍

Pre-Merge Checklist

  • Issue requirements met
  • All CI pipelines succeeded
  • CHANGELOG.md updated
    • Updates unreleased work already in Changelog, no new entry necessary
  • UX feedback:
    • No UX review needed
  • Followup issues:
    • No followup issues
  • Database migrations:
    • No migrations
  • Documentation:
    • No documentation updates required

Summary by CodeRabbit

  • Refactor

    • Link management streamlined: lookups now attach system identifiers consistently and responses are produced via a central converter.
  • New Features

    • Lightweight reference objects for connections and systems simplify returned data.
    • System identifier in link entities and responses is now required (non-nullable).
    • Link timestamps are now consistently populated (non-optional).
  • Tests

    • Automatic test cleanup fixtures for link records removed.

- Remove lazy-load footgun in from_orm; pass system info explicitly via SQL joins
- Return lightweight ref entities from resolve_connection_config/resolve_system
- Add SystemLinkResponse.from_entity() to reduce route handler duplication
- Move TooManyLinksError check after connection config lookup in set_links
- Remove empty __table_args__ from SystemConnectionConfigLink model
- Remove unnecessary cleanup_links autouse fixtures from tests

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 3, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

2 Skipped Deployments
Project Deployment Actions Updated (UTC)
fides-plus-nightly Ignored Ignored Preview Mar 4, 2026 9:02pm
fides-privacy-center Ignored Ignored Mar 4, 2026 9:02pm

Request Review

adamsachs and others added 2 commits March 3, 2026 10:47
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ype annotation

- Use outerjoin instead of inner join so links with deleted systems
  are still returned (with None system info) rather than silently dropped
- Make _to_entity_with_system an instance method (called via self)
- Remove unnecessary string forward reference on SystemRef type

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@adamsachs adamsachs marked this pull request as ready for review March 3, 2026 16:45
@adamsachs adamsachs requested a review from a team as a code owner March 3, 2026 16:45
@adamsachs adamsachs requested review from thabofletcher and removed request for a team March 3, 2026 16:45
@adamsachs adamsachs requested review from galvana and removed request for thabofletcher March 3, 2026 16:45
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 3, 2026

Greptile Summary

This PR is a clean code-quality follow-up to #7432, addressing reviewer feedback with no intended behavioral changes. It eliminates the lazy-load footgun by passing system info explicitly via SQL joins, introduces lightweight ConnectionConfigRef/SystemRef dataclasses to make resolve methods detach-safe, reduces response-construction duplication via SystemLinkResponse.from_entity(), fixes validation ordering so a missing connection returns 404 before 400, removes empty __table_args__, and drops unnecessary cleanup_links autouse fixtures from tests.

Key changes:

  • entities.py: New ConnectionConfigRef and SystemRef dataclasses; from_orm now receives system info as explicit params.
  • repository.py: Read methods use outerjoin instead of joinedload; write methods delegate to _to_entity_with_system; resolve methods return lightweight ref objects instead of full ORM models.
  • service.py: Validation reordering — connection config lookup now precedes the TooManyLinksError check, so an unknown connection correctly returns 404 rather than 400.
  • schemas.py: SystemLinkResponse.from_entity() classmethod added; TYPE_CHECKING guard used to avoid circular imports.
  • Tests: cleanup_links autouse fixtures removed, consistent with project convention of relying on automatic DB teardown between test runs.

Confidence Score: 4/5

  • This PR is safe to merge — all changes are code-quality improvements with no behavioral changes and one minor style nit.
  • Changes are well-scoped refactors with comprehensive existing test coverage. No logic bugs, security concerns, or missing test coverage were identified. The only finding is a minor style issue where _to_entity_with_system should be a @staticmethod since it doesn't use instance state.
  • No files require special attention. The style issue in repository.py is trivial and doesn't affect behavior.

Last reviewed commit: 7d3a552

Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

9 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines +154 to +164
def _to_entity_with_system(
self, link: SystemConnectionConfigLink, *, session: Session
) -> SystemIntegrationLinkEntity:
"""Convert an ORM link to an entity, fetching system info via a join."""
stmt = select(System.fides_key, System.name).where(System.id == link.system_id)
row = session.execute(stmt).first()
return SystemIntegrationLinkEntity.from_orm(
link,
system_fides_key=row.fides_key if row else None,
system_name=row.name if row else None,
)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

_to_entity_with_system should be a @staticmethod

This method does not reference self anywhere in its body — it operates entirely on the link and session parameters. Per project convention, methods that don't require instance state should be declared as @staticmethod.

Adding @staticmethod and dropping self from the signature would make this explicit. The existing call sites (e.g. self._to_entity_with_system(link, session=session)) will continue to work unchanged since Python allows calling static methods through instances.

Context Used: Rule from dashboard - Use static methods instead of instance methods when the method doesn't require instance state. (source)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

noted. this general point is coming into conflict with our standard repository pattern, so we do need to reconcile that more broadly. but i think this approach is consistent with the repository class as it stands now, so i'm OK keeping this as-is.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 4, 2026

📝 Walkthrough

Walkthrough

Adds lightweight reference dataclasses and changes entity construction to accept explicit system metadata; repository methods now return refs and construct entities with provided system keys; response factory added; minor ORM/typing tweaks; autouse test cleanup fixtures removed.

Changes

Cohort / File(s) Summary
Reference Types & Entity
src/fides/system_integration_link/entities.py
Added ConnectionConfigRef(id: str, key: str) and SystemRef(id: str, fides_key: str) dataclasses. Updated SystemIntegrationLinkEntity field types (created_at, updated_atdatetime; system_fides_keystr) and changed from_orm signature to require system_fides_key (and optional system_name) and use those values.
ORM Model
src/fides/system_integration_link/models.py
Removed an empty __table_args__ declaration from SystemConnectionConfigLink.
Repository Layer
src/fides/system_integration_link/repository.py
Queries now join to System to fetch fides_key/name and construct entities with that metadata; added helper _to_entity_with_system; resolve_connection_config/resolve_system now return ConnectionConfigRef/SystemRef instead of full ORM objects.
Routes & Schemas
src/fides/system_integration_link/routes.py, src/fides/system_integration_link/schemas.py
Added SystemLinkResponse.from_entity(entity) and replaced manual response construction with SystemLinkResponse.from_entity(e) in route handlers.
Service Layer
src/fides/system_integration_link/service.py
Imported SystemRef, annotated system_map: dict[str, SystemRef], and moved the TooManyLinksError check earlier in processing.
Tests
tests/system_integration_link/...
Removed imports of SystemConnectionConfigLink and removed autouse cleanup_links fixtures from test_repository.py, test_routes.py, and test_service.py, eliminating automatic teardown logic.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through joins and tiny refs,

Replaced the bulky with nimble steps,
Keys in paw, names in tune,
Responses tidy, tests less prune,
A little rabbit dance — code refreshed with pep! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 38.46% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'System integration link: address PR review follow-ups' is vague and generic; it describes the PR as addressing follow-ups without conveying specific changes. Consider a more specific title that highlights a key improvement, such as 'System integration link: eliminate lazy-load footgun and simplify resolve methods'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description check ✅ Passed The PR description is well-structured and addresses the template with detailed explanation of changes, code modifications, confirmation steps, and completed pre-merge checklist items.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch asachs/system-integration-followups

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

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/fides/system_integration_link/schemas.py`:
- Around line 26-31: from_entity can pass None into
SystemLinkResponse.system_fides_key (a required str) when the joined System is
missing; update the from_entity method to ensure a string is always provided by
replacing system_fides_key=entity.system_fides_key with a safe fallback (e.g.,
system_fides_key=entity.system_fides_key or "") so Pydantic validation never
fails for outer-joined rows; refer to the from_entity method,
SystemIntegrationLinkEntity, and SystemLinkResponse when making the change.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5c0a70fd-9a3f-407f-9141-0e5d8c707ba4

📥 Commits

Reviewing files that changed from the base of the PR and between a8084fc and 7e85878.

📒 Files selected for processing (9)
  • src/fides/system_integration_link/entities.py
  • src/fides/system_integration_link/models.py
  • src/fides/system_integration_link/repository.py
  • src/fides/system_integration_link/routes.py
  • src/fides/system_integration_link/schemas.py
  • src/fides/system_integration_link/service.py
  • tests/system_integration_link/test_repository.py
  • tests/system_integration_link/test_routes.py
  • tests/system_integration_link/test_service.py
💤 Files with no reviewable changes (4)
  • tests/system_integration_link/test_service.py
  • src/fides/system_integration_link/models.py
  • tests/system_integration_link/test_routes.py
  • tests/system_integration_link/test_repository.py

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

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/fides/system_integration_link/schemas.py`:
- Around line 25-31: The created_at type on SystemIntegrationLinkEntity
currently mismatches the DB constraint and SystemLinkResponse: update
SystemIntegrationLinkEntity.created_at to be non-optional (created_at: datetime)
so it matches the ORM column (nullable=False, server_default=func.now()) and the
SystemLinkResponse.created_at: datetime, or alternatively change
SystemLinkResponse.created_at to Optional[datetime] if you intend created_at to
be nullable; adjust the from_entity classmethod in SystemLinkResponse
accordingly to reflect the chosen non-optional/optional type.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: cb929453-ed57-476f-b4ab-328a269c3b4d

📥 Commits

Reviewing files that changed from the base of the PR and between 7e85878 and 6772c38.

📒 Files selected for processing (1)
  • src/fides/system_integration_link/schemas.py

These fields map to NOT NULL DB columns with server_default, so they
are always populated. Aligns the entity type with the DB constraint
and the SystemLinkResponse schema.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Copy link
Copy Markdown
Contributor

@JadeCara JadeCara left a comment

Choose a reason for hiding this comment

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

One super small comment, overall very clean. Approved


class SystemLinkResponse(BaseModel):
system_fides_key: str
system_fides_key: Optional[str] = None
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I am not sure this should be Optional. This alters the OpenAPI contract, since clients that previously expected a required string field will now see it as optional. In practice this should never be None (the FK is NOT NULL and the system FK has ondelete="CASCADE", so the outerjoin always finds a row).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is minor - totally understand the type mismatching.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

ha, the funny thing is i made it optional because of a previous AI code review comment that it technically could be empty because we're doing an outer join on the underlying query, and the only reason i switched to doing an outer join was because of an AI ask that we make the query more 'defensive'...

so we've really got these LLMs talking in circles. honestly i just think i shoudl go back to my original implementation that had an inner join, because as the comment here points out - we're cascading deletes, and the whole point of the join record is to actually join records...

The FK is NOT NULL with ondelete=CASCADE, so the joined System row is
always present. Switch outerjoin → join, remove None fallbacks, and
make system_fides_key non-optional across entity, schema, and repository.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
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.

🧹 Nitpick comments (1)
src/fides/system_integration_link/repository.py (1)

154-164: Consider defensive handling for missing system.

The .one() call on line 159 will raise NoResultFound if the system doesn't exist. While the FK constraint with CASCADE delete should prevent orphaned links, there's a brief TOCTOU window after flush but before commit where the system could be deleted by a concurrent transaction.

For a write operation this risk is minimal, but you could consider using .one_or_none() with an explicit error message for easier debugging if this ever occurs.

♻️ Optional: Defensive handling with clearer error
     def _to_entity_with_system(
         self, link: SystemConnectionConfigLink, *, session: Session
     ) -> SystemIntegrationLinkEntity:
         """Convert an ORM link to an entity, fetching system info via a join."""
         stmt = select(System.fides_key, System.name).where(System.id == link.system_id)
-        row = session.execute(stmt).one()
+        row = session.execute(stmt).one_or_none()
+        if row is None:
+            raise RuntimeError(
+                f"System {link.system_id} not found for link {link.id}"
+            )
         return SystemIntegrationLinkEntity.from_orm(
             link,
             system_fides_key=row.fides_key,
             system_name=row.name,
         )
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/fides/system_integration_link/repository.py` around lines 154 - 164, The
current _to_entity_with_system method uses session.execute(...).one() which will
raise NoResultFound if System row is missing; change it to use .one_or_none()
(or .first()) when executing the select(System.fides_key,
System.name).where(System.id == link.system_id) and explicitly handle a None
result by raising a clear, descriptive error (or returning a well-defined
fallback) before calling SystemIntegrationLinkEntity.from_orm; reference the
symbols _to_entity_with_system, System, SystemIntegrationLinkEntity, and
session.execute/.one() so the change is applied in that conversion helper.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/fides/system_integration_link/repository.py`:
- Around line 154-164: The current _to_entity_with_system method uses
session.execute(...).one() which will raise NoResultFound if System row is
missing; change it to use .one_or_none() (or .first()) when executing the
select(System.fides_key, System.name).where(System.id == link.system_id) and
explicitly handle a None result by raising a clear, descriptive error (or
returning a well-defined fallback) before calling
SystemIntegrationLinkEntity.from_orm; reference the symbols
_to_entity_with_system, System, SystemIntegrationLinkEntity, and
session.execute/.one() so the change is applied in that conversion helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2747d440-22f0-4fc5-8b72-34ca1dc7b1f0

📥 Commits

Reviewing files that changed from the base of the PR and between 79f62f8 and b52557f.

📒 Files selected for processing (3)
  • src/fides/system_integration_link/entities.py
  • src/fides/system_integration_link/repository.py
  • src/fides/system_integration_link/schemas.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/fides/system_integration_link/schemas.py

@adamsachs adamsachs added this pull request to the merge queue Mar 4, 2026
Merged via the queue into main with commit 41f7587 Mar 4, 2026
57 of 58 checks passed
@adamsachs adamsachs deleted the asachs/system-integration-followups branch March 4, 2026 21:58
mfbrown pushed a commit that referenced this pull request Mar 12, 2026
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
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.

2 participants