Skip to content

refactor(utils): extract shared truncate() helper, replace six inline _truncate() implementations#1080

Merged
muddlebee merged 5 commits intoTracer-Cloud:mainfrom
udit-rawat:refactor/shared-truncation-helper
Apr 29, 2026
Merged

refactor(utils): extract shared truncate() helper, replace six inline _truncate() implementations#1080
muddlebee merged 5 commits intoTracer-Cloud:mainfrom
udit-rawat:refactor/shared-truncation-helper

Conversation

@udit-rawat
Copy link
Copy Markdown
Contributor

Problem

Six modules independently defined a private _truncate() function with inconsistent implementations — different ellipsis characters (... vs ), different limits (200, 500, 4000, 4096), and no shared source of truth. Every new delivery channel or integration had to reinvent the same logic.

Affected files: discord_delivery.py, telegram_delivery.py, azure_sql.py, mysql.py, mariadb.py, and gitlab_writeback.py.

Solution

Added app/utils/truncation.py with a single truncate(text, limit, ellipsis="...") helper. Migrated all six call sites to use it. Each module's limit constant stays where it is — only the function body moves.

Tests

Added tests/utils/test_truncation.py covering:

  • text under limit → returned unchanged
  • text over limit → truncated with correct ellipsis
  • text at exact limit → not truncated
  • Unicode ellipsis variant ()
  • default ellipsis behaviour

Closes #1056

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Apr 29, 2026

Greptile Summary

This PR extracts six identical private _truncate() implementations into a single app/utils/truncation.py helper and updates all call sites. All six migrations preserve the original behaviour faithfully — the Discord/Telegram paths correctly pass ellipsis="…" to match the old limit - 1 logic, and the GitLab path produces the same body[:3997] + "..." result through the new helper.

Confidence Score: 5/5

Safe to merge; all six migrations are behaviourally equivalent and the only findings are P2 style/edge-case issues.

All remaining findings are P2: an unguarded negative-slice edge case that no current caller can trigger, and a parameter name that shadows a built-in. No correctness regressions were introduced.

app/utils/truncation.py — minor edge-case guard and parameter naming.

Important Files Changed

Filename Overview
app/utils/truncation.py New shared utility; correct for all current callers, but has an unguarded negative-slice edge case when limit < len(ellipsis), and parameter name shadows Python built-in.
tests/utils/test_truncation.py Good parametrised test suite; covers equal-limit case but is missing a test for limit < len(ellipsis) where negative slicing would silently produce an oversized result.
app/nodes/publish_findings/gitlab_writeback.py Migrated to shared truncate(); behaviour is equivalent to the original (body[:3997] + "..."). New _GITLAB_MR_NOTE_LIMIT constant improves readability.
app/utils/discord_delivery.py Migrated to shared truncate() with explicit ellipsis="…"; behaviour is equivalent since len("…")==1, matching the old limit-1 calculation.
app/utils/telegram_delivery.py Migrated to shared truncate() with explicit ellipsis="…"; behaviour preserved correctly.
app/integrations/azure_sql.py Private _truncate() removed; call sites updated to pass _QUERY_TRUNCATE_LEN explicitly — behaviour unchanged.
app/integrations/mariadb.py Private _truncate() removed; call sites updated to pass _QUERY_TRUNCATE_LEN explicitly — behaviour unchanged.
app/integrations/mysql.py Private _truncate() removed; call sites updated to pass _QUERY_TRUNCATE_LEN explicitly — behaviour unchanged.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    subgraph callers["Call sites"]
        F["discord_delivery (ellipsis=…)"]
        G["telegram_delivery (ellipsis=…)"]
        H["gitlab_writeback (ellipsis=...)"]
        I["azure_sql (ellipsis=...)"]
        J["mysql (ellipsis=...)"]
        K["mariadb (ellipsis=...)"]
    end

    callers --> A["truncate(text, limit, ellipsis)"]
    A --> B{len text <= limit}
    B -- yes --> C[return text unchanged]
    B -- no --> D["text[ : limit - len(ellipsis)] + ellipsis"]
    D --> E[return truncated string]
Loading

Reviews (1): Last reviewed commit: "refactor: extract shared truncate() help..." | Re-trigger Greptile

Comment thread app/utils/truncation.py Outdated
def truncate(text: str, limit: int, ellipsis: str = "...") -> str:
if len(text) <= limit:
return text
return text[: limit - len(ellipsis)] + ellipsis
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.

P2 Negative slice when limit < len(ellipsis)

When limit is smaller than the length of the ellipsis string, text[:limit - len(ellipsis)] evaluates to a negative index (text[:-N]), stripping characters from the end of the original text and then appending the full ellipsis — producing a result longer than limit. For example, truncate("hello", 2, "...") returns "hel..." (6 chars) instead of the expected 2-char result. None of the current callers hit this case, but the helper has no guard and the test suite doesn't cover it.

Suggested change
return text[: limit - len(ellipsis)] + ellipsis
return text[: max(0, limit - len(ellipsis))] + ellipsis

Comment thread app/utils/truncation.py Outdated
"""Shared text truncation utility."""


def truncate(text: str, limit: int, ellipsis: str = "...") -> str:
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.

P2 ellipsis shadows the Python built-in

The parameter name ellipsis shadows the Python built-in singleton of the same name. While it doesn't cause a bug here, it's a lint target under ruff's A002 (argument-shadows-builtin) rule. A name like suffix or trail avoids the shadow without changing the public API concept.

@udit-rawat
Copy link
Copy Markdown
Contributor Author

@muddlebee all six inline _truncate() implementations replaced with the shared helper in app/utils/truncation.py. Renamed ellipsis param to suffix to avoid the builtin shadow, added max(0, ...) guard when limit is smaller than the suffix. Ready for review

Comment thread app/utils/truncation.py Outdated
def truncate(text: str, limit: int, suffix: str = "...") -> str:
if len(text) <= limit:
return text
cut = max(0, limit - len(suffix))
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

When limit < len(suffix) (e.g. truncate("abc", 2, "...")), cut=0 and the return value is just suffix (length 3), which silently exceeds limit.

Consider all the edge cases here.. and add proper unit tests for all edge cases.

Comment thread tests/utils/test_truncation.py Outdated


@pytest.mark.parametrize(
"text,limit,ellipsis,expected",
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

The parametrize header uses ellipsis as a field name — it shadows the Python built-in. Rename to suffix to match the actual parameter name and avoid any future confusion.

Comment thread tests/utils/test_truncation.py
@udit-rawat
Copy link
Copy Markdown
Contributor Author

@muddlebee addressed both comments — renamed ellipsis to suffix in the parametrize header, added limit < len(suffix) edge case handling (return suffix[:limit]), and added full edge case test coverage including limit=0, empty
string, empty suffix, and unicode suffix. Also added an explicit assertion that the GitLab MR note body is capped at exactly 4000 chars. Ready for review.

@muddlebee muddlebee merged commit 0b3d784 into Tracer-Cloud:main Apr 29, 2026
7 checks passed
@muddlebee
Copy link
Copy Markdown
Collaborator

@udit-rawat nice work. 🚀

@github-actions
Copy link
Copy Markdown
Contributor

🎊 Achievement unlocked: PR Merged. @udit-rawat passed code review, survived CI, and shipped. Respect. 🤝


👋 Join us on Discord - OpenSRE : hang out, contribute, or hunt for features and issues. Everyone's welcome.

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.

Extract repeated _truncate() implementations into a shared app/utils/truncation.py helper

2 participants