Skip to content

Conversation

@juliolmuller
Copy link
Collaborator

@juliolmuller juliolmuller commented Dec 8, 2025

Resolves #6

Summary by CodeRabbit

  • New Features

    • Added a CPF formatter with both functional and class-based interfaces and rich formatting options (separators, masking, escaping, on-fail hooks)
  • Bug Fixes

    • Enhanced input validation and clearer error reporting for type, length, and masking-range issues
  • Documentation

    • Expanded README with installation, import/usage (functional & OO), options, and contribution/license info
  • Tests

    • Added comprehensive unit tests exercising formatting, options, masking, escaping, and error paths
  • Chores

    • Updated project metadata and test discovery settings

✏️ Tip: You can customize this high-level summary in your review settings.

@juliolmuller juliolmuller requested a review from a team as a code owner December 8, 2025 14:18
@juliolmuller juliolmuller added the enhancement New feature or request label Dec 8, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 8, 2025

Walkthrough

Replaces a trivial greeting with a complete CPF formatting implementation: adds CpfFormatter, CpfFormatterOptions, exceptions, a configurable cpf_fmt(...) entrypoint, expanded exports and metadata, extensive README, and a full test suite while removing the old simple test.

Changes

Cohort / File(s) Change Summary
Packaging & Docs
packages/cpf-fmt/pyproject.toml, packages/cpf-fmt/README.md
Updated project metadata (homepage URL, pytest class discovery, classifier formatting) and replaced README with full documentation, badges, installation and usage examples.
Public API & Entry
packages/cpf-fmt/src/cpf_fmt/__init__.py, packages/cpf-fmt/src/cpf_fmt/cpf_fmt.py
Exposed formatter classes, options, defaults, and exceptions in module exports; replaced static greeting with a typed, parameterized cpf_fmt(...) function that constructs and delegates to CpfFormatter.
Formatter Implementation
packages/cpf-fmt/src/cpf_fmt/cpf_formatter.py, packages/cpf-fmt/src/cpf_fmt/cpf_formatter_options.py, packages/cpf-fmt/src/cpf_fmt/exceptions.py
Added CpfFormatter (formatting, masking, escaping, on_fail callback), CpfFormatterOptions (defaults, validation, merge, hidden-range logic, setattr), and a detailed exception hierarchy for type, option, range, and length errors.
Tests
packages/cpf-fmt/tests/...
packages/cpf-fmt/tests/cpf_formatter_test_cases.py, packages/cpf-fmt/tests/cpf_formatter_class_test.py, packages/cpf-fmt/tests/cpf_formatter_function_test.py, packages/cpf-fmt/tests/cpf_formatter_options_test.py, packages/cpf-fmt/tests/cpf_fmt_test.py
Removed the old trivial test and added an abstract test suite plus concrete tests covering object- and function-style formatting, options defaults/merging, hidden-range validation, on_fail behavior, escaping, and delimiter customization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Areas to focus on during review:

  • options invariants and mutation logic in cpf_formatter_options.py (post_init, merge, setattr, set_hidden_range)
  • masking, separator insertion, normalization, escaping, and on_fail fallback in cpf_formatter.py
  • exception classes: message content, stored fields, and intended usage in exceptions.py
  • public API/export changes in __init__.py and the cpf_fmt(...) signature
  • test coverage alignment and edge-case assertions in tests/*

"I hopped with tiny paws and tidy code,
Dotted every place and stroked each node.
I hid a digit, added dash and cheer,
Tests hopped after — everything's clear! 🐇"

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 7.79% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Implement package cpf-fmt' is a concise, specific summary that clearly describes the main objective of the PR: creating a new CPF formatting package.
Linked Issues check ✅ Passed The PR implements the cpf-fmt package with a complete formatter class, options configuration, error handling, functional API, comprehensive tests, and documentation, fully addressing issue #6's requirement to create the cpf-fmt package.
Out of Scope Changes check ✅ Passed All changes are scoped to the cpf-fmt package implementation: new module structure, formatter logic, tests, and documentation. The pyproject.toml changes (URL update, pytest config) are reasonable package-level adjustments and not out of scope.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/cpf-fmt

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.

Copy link
Contributor

@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: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/cpf-fmt/pyproject.toml (1)

4-4: Critical: Fix package description.

The description incorrectly states "CNPJ (Brazilian personal ID)" when this package is for CPF formatting. CNPJ is a different Brazilian document (company registration).

Apply this diff to fix the description:

-description = "Utility function to format CNPJ (Brazilian personal ID)"
+description = "Utility function to format CPF (Brazilian personal ID)"
🧹 Nitpick comments (5)
packages/cpf-fmt/src/cpf_fmt/cpf_fmt.py (1)

18-27: Prefer keyword arguments when constructing CpfFormatter.

Using positional arguments makes the code fragile to signature changes and reduces readability. Consider using keyword arguments for clarity and maintainability.

Apply this diff to use keyword arguments:

     formatter = CpfFormatter(
-        hidden,
-        hidden_key,
-        hidden_start,
-        hidden_end,
-        dot_key,
-        dash_key,
-        escape,
-        on_fail,
+        hidden=hidden,
+        hidden_key=hidden_key,
+        hidden_start=hidden_start,
+        hidden_end=hidden_end,
+        dot_key=dot_key,
+        dash_key=dash_key,
+        escape=escape,
+        on_fail=on_fail,
     )
packages/cpf-fmt/tests/cpf_formatter_function_test.py (1)

21-31: Prefer keyword arguments when calling cpf_fmt.

Using positional arguments makes the code less readable and more fragile. Consider using keyword arguments for clarity.

Apply this diff:

         return cpf_fmt(
             cpf_string,
-            hidden,
-            hidden_key,
-            hidden_start,
-            hidden_end,
-            dot_key,
-            dash_key,
-            escape,
-            on_fail,
+            hidden=hidden,
+            hidden_key=hidden_key,
+            hidden_start=hidden_start,
+            hidden_end=hidden_end,
+            dot_key=dot_key,
+            dash_key=dash_key,
+            escape=escape,
+            on_fail=on_fail,
         )
packages/cpf-fmt/tests/cpf_formatter_class_test.py (1)

35-45: Prefer keyword arguments when calling formatter.format.

Using positional arguments reduces readability and makes the code more fragile. Consider using keyword arguments.

Apply this diff:

         return self.formatter.format(
             cpf_string,
-            hidden,
-            hidden_key,
-            hidden_start,
-            hidden_end,
-            dot_key,
-            dash_key,
-            escape,
-            on_fail,
+            hidden=hidden,
+            hidden_key=hidden_key,
+            hidden_start=hidden_start,
+            hidden_end=hidden_end,
+            dot_key=dot_key,
+            dash_key=dash_key,
+            escape=escape,
+            on_fail=on_fail,
         )
packages/cpf-fmt/src/cpf_fmt/cpf_formatter_options.py (2)

24-24: Consider using frozen=True for immutable-like behavior.

The dataclass uses frozen=False but employs object.__setattr__ to bypass normal attribute setting and implements a custom __setattr__ for validation. If the intent is to have validated immutability (with controlled mutation), frozen=True would be more explicit and prevent accidental direct field mutation.

However, this would require adjusting the validation logic. The current design works but is somewhat complex.


96-97: Minor: Redundant set_hidden_range call.

The replace() call creates a new instance, which triggers __post_init__, which already calls set_hidden_range(). The subsequent call on line 97 is redundant since new_start and new_end are already the instance's values at this point.

While not harmful, removing this redundancy would slightly improve efficiency.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 995bf75 and 1acba8a.

📒 Files selected for processing (12)
  • packages/cpf-fmt/README.md (1 hunks)
  • packages/cpf-fmt/pyproject.toml (3 hunks)
  • packages/cpf-fmt/src/cpf_fmt/__init__.py (1 hunks)
  • packages/cpf-fmt/src/cpf_fmt/cpf_fmt.py (1 hunks)
  • packages/cpf-fmt/src/cpf_fmt/cpf_formatter.py (1 hunks)
  • packages/cpf-fmt/src/cpf_fmt/cpf_formatter_options.py (1 hunks)
  • packages/cpf-fmt/src/cpf_fmt/exceptions.py (1 hunks)
  • packages/cpf-fmt/tests/cpf_fmt_test.py (0 hunks)
  • packages/cpf-fmt/tests/cpf_formatter_class_test.py (1 hunks)
  • packages/cpf-fmt/tests/cpf_formatter_function_test.py (1 hunks)
  • packages/cpf-fmt/tests/cpf_formatter_options_test.py (1 hunks)
  • packages/cpf-fmt/tests/cpf_formatter_test_cases.py (1 hunks)
💤 Files with no reviewable changes (1)
  • packages/cpf-fmt/tests/cpf_fmt_test.py
🧰 Additional context used
🧬 Code graph analysis (3)
packages/cpf-fmt/tests/cpf_formatter_options_test.py (2)
packages/cpf-fmt/src/cpf_fmt/cpf_formatter_options.py (2)
  • DEFAULT_ON_FAIL (19-21)
  • CpfFormatterOptions (25-145)
packages/cpf-fmt/src/cpf_fmt/exceptions.py (1)
  • CpfFormatterHiddenRangeError (41-55)
packages/cpf-fmt/tests/cpf_formatter_class_test.py (3)
packages/cpf-fmt/src/cpf_fmt/cpf_fmt.py (1)
  • cpf_fmt (6-29)
packages/cpf-fmt/src/cpf_fmt/cpf_formatter_options.py (2)
  • DEFAULT_ON_FAIL (19-21)
  • CpfFormatterOptions (25-145)
packages/cpf-fmt/src/cpf_fmt/cpf_formatter.py (1)
  • CpfFormatter (8-104)
packages/cpf-fmt/tests/cpf_formatter_test_cases.py (4)
packages/cpf-fmt/src/cpf_fmt/cpf_fmt.py (1)
  • cpf_fmt (6-29)
packages/cpf-fmt/src/cpf_fmt/exceptions.py (1)
  • CpfFormatterHiddenRangeError (41-55)
packages/cpf-fmt/tests/cpf_formatter_class_test.py (1)
  • format (23-45)
packages/cpf-fmt/tests/cpf_formatter_function_test.py (1)
  • format (9-31)
🪛 LanguageTool
packages/cpf-fmt/README.md

[typographical] ~28-~28: Consider adding a comma after ‘Internally’ for more clarity.
Context: ...ing class-based resource from cpf_fmt import CpfFormatter # Or using function-based one f...

(RB_LY_COMMA)


[style] ~94-~94: Using many exclamation marks might seem excessive (in this case: 10 exclamation marks for a text that’s 3140 characters long)
Context: ...tion & Support We welcome contributions! Please see our [Contributing Guidelines...

(EN_EXCESSIVE_EXCLAMATION)

🪛 markdownlint-cli2 (0.18.1)
packages/cpf-fmt/README.md

21-21: Dollar signs used before commands without showing output

(MD014, commands-show-output)

🔇 Additional comments (19)
packages/cpf-fmt/README.md (1)

1-111: LGTM! Documentation is comprehensive and well-structured.

The README provides clear installation instructions, usage examples for both object-oriented and functional approaches, and detailed formatting options. The examples are helpful and the documentation structure is easy to follow.

packages/cpf-fmt/pyproject.toml (1)

63-63: LGTM! Test class discovery pattern updated appropriately.

The change from ["Test*"] to ["*Test"] aligns with the test file naming convention used in this PR where test classes have the *Test suffix (e.g., CpfFormatterOptionsTest, CpfFormatterClassTest).

packages/cpf-fmt/tests/cpf_formatter_options_test.py (1)

16-302: LGTM! Comprehensive test coverage for options handling.

The test suite thoroughly covers:

  • Default value initialization
  • Option persistence and merging
  • Boundary validation for hidden ranges
  • Type validation for callbacks
  • Error cases with appropriate exception handling

The test structure is clear and the coverage is excellent.

packages/cpf-fmt/src/cpf_fmt/__init__.py (1)

1-43: LGTM! Public API exports are well-organized.

The package properly exposes:

  • Formatter class and function
  • Options class with all default constants
  • Complete exception hierarchy

The __all__ list ensures explicit control over the public API surface.

packages/cpf-fmt/tests/cpf_formatter_class_test.py (1)

47-58: LGTM! Options property access test is well-structured.

The test properly verifies that the formatter's options attribute returns a CpfFormatterOptions instance with all default values correctly initialized.

packages/cpf-fmt/src/cpf_fmt/cpf_formatter_options.py (3)

37-61: LGTM! Initialization logic properly handles defaults and validation.

The __post_init__ method correctly:

  • Fills None values with appropriate defaults
  • Validates the hidden range
  • Ensures on_fail is callable

101-117: LGTM! Range validation is thorough and handles edge cases well.

The method properly validates boundaries and defensively auto-swaps start/end if reversed, which is good UX.


119-145: LGTM! Custom setattr provides proper validation.

The validation logic ensures:

  • Hidden range remains valid when start or end is updated individually
  • on_fail cannot be set to None or non-callable values after initialization

The implementation is complex but handles all edge cases correctly.

packages/cpf-fmt/tests/cpf_formatter_test_cases.py (3)

1-22: LGTM!

The abstract base class pattern is well-structured for sharing test cases across function and class-based implementations.


24-88: LGTM!

Comprehensive coverage of input normalization scenarios including various separator types, mixed characters, and whitespace handling.


153-179: LGTM!

Good coverage of error cases including out-of-bounds range values and invalid callback types.

packages/cpf-fmt/src/cpf_fmt/exceptions.py (4)

1-16: LGTM!

Well-structured exception with clear message formatting and appropriate attribute storage for debugging.


19-34: LGTM!

Consistent structure with the input type error class, providing useful context for debugging option validation failures.


37-55: LGTM!

Clear exception hierarchy with informative error messages that include the valid range and provided value.


58-79: LGTM!

Smart conditional formatting that provides additional context when the evaluated input differs from the actual input, helping users understand why their input was rejected.

packages/cpf-fmt/src/cpf_fmt/cpf_formatter.py (4)

1-33: LGTM!

Clean class initialization with __slots__ for memory efficiency and delegation to CpfFormatterOptions for configuration management.


75-84: LGTM!

The masking logic correctly handles the inclusive range with proper string slicing and concatenation.


86-99: LGTM!

Correct string slicing for the standard CPF format (XXX.XXX.XXX-XX) with configurable separators and optional HTML escaping.


101-104: LGTM!

Clean property access to the internal options manager.

juliolmuller and others added 2 commits December 8, 2025 11:27
Fix as per CodeRabbit review at #36 (comment).

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Fix as per CodeRabbit review at #36 (comment).
Copy link
Contributor

@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: 0

🧹 Nitpick comments (3)
packages/cpf-fmt/tests/cpf_formatter_test_cases.py (3)

24-88: Input-normalization tests are comprehensive; consider pytest parametrization

These tests nicely cover a wide range of messy input forms (dots, dashes, spaces, slashes, mixed characters) all converging to the canonical "123.456.789-10". To reduce duplication and make future additions cheaper, you could group many of these into a single @pytest.mark.parametrize over the input/expected pairs while keeping behavior identical.

-    def test_cpf_without_formatting_formats_to_dots_and_dash(self):
-        cpf = self.format("12345678910")
-
-        assert cpf == "123.456.789-10", f'Output: "{cpf}", Expected: "123.456.789-10"'
-
-    def test_cpf_with_dashes_formats_to_dots_and_dash(self):
-        cpf = self.format("123-456-789-10")
-
-        assert cpf == "123.456.789-10"
+    @pytest.mark.parametrize(
+        "raw",
+        [
+            "12345678910",
+            "123-456-789-10",
+            "123 456 789 10",
+            "12345678910 ",
+            " 12345678910",
+            "1.2.3.4.5.6.7.8.9.1.0",
+            "1-2-3-4-5-6-7-8-9-1-0",
+            "1 2 3 4 5 6 7 8 9 1 0",
+            "12345678910abc",
+            "123456789 dv 10",
+            "123/456/789/10",
+            "123 456 789 / 10",
+        ],
+    )
+    def test_cpf_normalization_formats_to_dots_and_dash(self, raw: str):
+        cpf = self.format(raw)
+
+        assert cpf == "123.456.789-10", f'Output: "{cpf}", Expected: "123.456.789-10"'

118-152: Hidden range and custom mask tests give good coverage; you might add explicit edge-boundary cases

The hidden-format tests cover default masking, start-only, end-only, combined ranges, reversed ranges, and custom hidden_key, which nicely constrains formatter behavior. If you want even stronger guarantees around the allowed [0, 10] index interval, consider adding explicit success cases for hidden_start=10 and hidden_end=0 to complement the error tests for out-of-range values.


153-179: on_fail and error-path tests look good; consider tightening callback typing

The on_fail behavior and non-callable error path are both well tested, and the range-error tests align with CpfFormatterHiddenRangeError’s documented bounded integer contract. To help static checkers, you could tighten the type of on_fail from a bare Callable | None to a more specific signature, e.g. Callable[[str], str] | None, matching how it’s used in test_cpf_formats_to_hidden_format_with_start_range and the referenced implementation. Based on relevant code snippets, this should better reflect the intended API.

-        escape: bool | None = None,
-        on_fail: Callable | None = None,
+        escape: bool | None = None,
+        on_fail: Callable[[str], str] | None = None,
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9f4476a and 32b132b.

📒 Files selected for processing (1)
  • packages/cpf-fmt/tests/cpf_formatter_test_cases.py (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
packages/cpf-fmt/tests/cpf_formatter_test_cases.py (4)
packages/cpf-fmt/src/cpf_fmt/cpf_fmt.py (1)
  • cpf_fmt (6-29)
packages/cpf-fmt/src/cpf_fmt/exceptions.py (1)
  • CpfFormatterHiddenRangeError (41-55)
packages/cpf-fmt/tests/cpf_formatter_function_test.py (1)
  • format (9-31)
packages/cpf-fmt/tests/cpf_formatter_class_test.py (1)
  • format (23-45)
🔇 Additional comments (2)
packages/cpf-fmt/tests/cpf_formatter_test_cases.py (2)

8-22: Abstract base test class and format signature look solid

The abstract CpfFormatterTestCases and its format(...) signature line up well with both the cpf_fmt function and the CpfFormatter.format method, making it easy to reuse the same cases across implementations without duplication. No issues here.


89-117: Delimiter and escaping behavior are well specified

The tests for dot_key, dash_key, and escape=True clearly pin down expected behavior (including HTML-escaping of & and <> into &amp; and &lt;&gt;), which should prevent regressions around delimiter customization and HTML-escaping semantics. No issues spotted.

@juliolmuller juliolmuller merged commit 086923e into main Dec 8, 2025
11 checks passed
@juliolmuller juliolmuller deleted the feat/cpf-fmt branch December 8, 2025 14:42
juliolmuller added a commit that referenced this pull request Dec 8, 2025
Fix as per CodeRabbit review at #36 (comment).

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
juliolmuller added a commit that referenced this pull request Dec 8, 2025
Fix as per CodeRabbit review at #36 (comment).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Create cpf-fmt

2 participants