Skip to content

Conversation

@joel-pcg
Copy link
Contributor

Add Python 3.10+ Support

Summary

Extended Python version support from >=3.12 to >=3.10, allowing users on Python 3.10 and 3.11 to use PyAzul.

Changes Made

1. Core Code Changes

  • pyazul/core/config.py: Changed Self import from typing to typing_extensions for Python 3.10 compatibility
    • Self was introduced in Python 3.11's typing module
    • typing_extensions provides backward compatibility for Python 3.10

2. Configuration Updates (pyproject.toml)

  • Updated requires-python from >=3.12 to >=3.10
  • Added Python version classifiers: 3.10, 3.11, 3.12, 3.13
  • Updated Black formatter target versions to include py310, py311, py312, py313

3. Test Fixes

Fixed 5 e2e tests to properly handle frictionless 3DS approval responses:

  • tests/e2e/services/test_datavault_integration.py (2 tests)
    • test_create_sale_datavault_3ds
    • test_token_sale_comparison_3ds_vs_non_3ds
  • tests/e2e/services/test_secure_integration.py (3 tests)
    • test_secure_sale_direct_to_challenge
    • test_secure_sale_challenge_after_method
    • test_secure_sale_3ds_method_with_session_validation

Tests now correctly handle three 3DS response types:

  1. redirect: true - 3DS Method/Challenge required
  2. value: {...} - Wrapped approval response
  3. Top-level IsoCode: "00" - Direct frictionless approval

All tests pass successfully on both Python 3.10 and 3.13.

Compatibility

  • Supported Python versions: 3.10, 3.11, 3.12, 3.13
  • All dependencies support Python 3.10+
  • No breaking changes - works on all versions
  • Backward compatible - code that works on 3.10 works on newer versions

Notes

  • Python 3.10 EOL: October 2026
  • typing_extensions is already a transitive dependency via Pydantic
  • No new dependencies added

- Added support for Python versions 3.10 to 3.13 in pyproject.toml.
- Adjusted the required Python version to 3.10.
- Updated Black's target version to include Python 3.10, 3.11, 3.12, and 3.13.
- Refined test assertions for direct approval scenarios in 3DS flows to improve clarity and coverage.
- Use sys.version_info to conditionally import Self
- Python 3.11+ uses native typing.Self
- Python 3.10 falls back to typing_extensions.Self
- Follows pythonic pattern for version-specific imports
- Move conditional Self import after third-party imports to fix pylint
- Reformat test files with Black to fix line length issues
- Resolves PYTHON_PYLINT, PYTHON_BLACK, PYTHON_FLAKE8, PYTHON_PYINK linter errors
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR extends Python version support from >=3.12 to >=3.10, enabling PyAzul to work with Python 3.10 and 3.11. It includes backward-compatible changes to handle the Self typing annotation and updates end-to-end tests to properly handle frictionless 3DS approval responses.

  • Uses conditional imports with sys.version_info check to import Self from typing (Python 3.11+) or typing_extensions (Python 3.10)
  • Updates test suite to handle three types of 3DS responses: redirect with HTML, wrapped approval responses, and top-level frictionless approvals
  • Extends Python version classifiers and Black formatter target versions to include Python 3.10-3.13

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.

File Description
pyazul/core/config.py Adds conditional import logic for Self type annotation to support Python 3.10 via typing_extensions
pyproject.toml Updates Python version requirement to >=3.10, adds version classifiers for 3.10-3.13, and updates Black target versions
tests/e2e/services/test_secure_integration.py Enhances three test functions to handle frictionless 3DS approval responses in addition to redirect scenarios
tests/e2e/services/test_datavault_integration.py Adds handling for top-level frictionless approval responses in two test functions

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

You can also share your feedback on Copilot code review for a chance to win a $100 gift card. Take the survey.

Comment on lines +322 to +334
# Check if redirect (challenge/method) or direct approval
if not initial_response_dict.get("redirect"):
# Handle frictionless approval case
if initial_response_dict.get("IsoCode") == "00":
print("Transaction approved frictionlessly (no redirect).")
assert initial_response_dict.get("ResponseMessage") == "APROBADA"
pytest.skip(
"Test expects redirect, but transaction was approved frictionlessly"
)
else:
pytest.fail(
f"Expected redirect for 3DS Method, got: {initial_response_dict}"
)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

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

This frictionless approval handling code is duplicated across multiple test functions (lines 322-334, 420-429 in this file, and similar patterns in test_datavault_integration.py). Consider extracting this into a shared helper function to improve maintainability and reduce code duplication.

Example:

def handle_frictionless_approval_or_fail(response_dict, expected_redirect_type="redirect"):
    """Handle frictionless approval or fail with appropriate message."""
    if not response_dict.get("redirect"):
        if response_dict.get("IsoCode") == "00":
            print("Transaction approved frictionlessly (no redirect).")
            assert response_dict.get("ResponseMessage") == "APROBADA"
            pytest.skip(
                f"Test expects {expected_redirect_type}, but transaction was approved frictionlessly"
            )
        else:
            pytest.fail(
                f"Expected {expected_redirect_type}, got: {response_dict}"
            )

Copilot uses AI. Check for mistakes.
Comment on lines +420 to +429
if not initial_response_dict.get("redirect"):
# Handle frictionless approval case
if initial_response_dict.get("IsoCode") == "00":
print("Transaction approved frictionlessly (no redirect).")
assert initial_response_dict.get("ResponseMessage") == "APROBADA"
pytest.skip(
"Test expects 3DS method redirect, but transaction was approved frictionlessly"
)
else:
pytest.fail(f"Expected 3DS method redirect, got: {initial_response_dict}")
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

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

This frictionless approval handling code is duplicated across multiple test functions (lines 420-429 here, lines 322-334 earlier in this file, and similar patterns in test_datavault_integration.py). Consider extracting this into a shared helper function to improve maintainability and reduce code duplication.

Copilot uses AI. Check for mistakes.
Comment on lines +185 to +192
elif result.get("IsoCode") == "00":
# Direct approval (frictionless) - top-level response
assert (
result.get("ResponseMessage") == "APROBADA"
), f"3DS token sale failed: {result}"
print(
f"3DS token sale approved directly (top-level): {result.get('AuthorizationCode')}"
)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

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

This frictionless approval handling code (checking for top-level IsoCode == "00") is duplicated across multiple test functions in both this file and test_secure_integration.py. Consider extracting the common response handling logic into a shared helper function to improve maintainability.

Copilot uses AI. Check for mistakes.
Comment on lines +270 to +277
elif three_ds_result.get("IsoCode") == "00":
# Direct approval at top level
assert (
three_ds_result.get("ResponseMessage") == "APROBADA"
), f"3DS failed: {three_ds_result}"
print(
f"3DS token sale approved (top-level): {three_ds_result.get('AuthorizationCode')}"
)
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

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

This top-level frictionless approval handling is duplicated in this file (also at lines 185-192) and in test_secure_integration.py. Consider extracting this common pattern into a shared helper function.

Copilot uses AI. Check for mistakes.
"httpx[http2]>=0.28.1",
"pydantic>=2.11.5",
"pydantic-settings>=2.9.1",
"python-dotenv>=1.1.0",
Copy link

Copilot AI Nov 26, 2025

Choose a reason for hiding this comment

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

While typing_extensions is currently available as a transitive dependency through pydantic, it's being used directly in the code but not explicitly declared in pyproject.toml. Consider adding typing-extensions>=4.0.0 to the dependencies list to ensure it remains available even if Pydantic's dependencies change in the future. This makes the dependency relationship explicit and prevents potential breakage.

Suggested change
"python-dotenv>=1.1.0",
"python-dotenv>=1.1.0",
"typing-extensions>=4.0.0",

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

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.

1 participant