chore: update Python compatibility and enhance test coverage#40
chore: update Python compatibility and enhance test coverage#40joel-pcg wants to merge 3 commits intoindexa-git:mainfrom
Conversation
- 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
There was a problem hiding this comment.
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_infocheck to importSelffromtyping(Python 3.11+) ortyping_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.
| # 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}" | ||
| ) |
There was a problem hiding this comment.
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}"
)| 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}") |
There was a problem hiding this comment.
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.
| 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')}" | ||
| ) |
There was a problem hiding this comment.
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.
| 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')}" | ||
| ) |
There was a problem hiding this comment.
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.
| "httpx[http2]>=0.28.1", | ||
| "pydantic>=2.11.5", | ||
| "pydantic-settings>=2.9.1", | ||
| "python-dotenv>=1.1.0", |
There was a problem hiding this comment.
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.
| "python-dotenv>=1.1.0", | |
| "python-dotenv>=1.1.0", | |
| "typing-extensions>=4.0.0", |
Add Python 3.10+ Support
Summary
Extended Python version support from
>=3.12to>=3.10, allowing users on Python 3.10 and 3.11 to use PyAzul.Changes Made
1. Core Code Changes
pyazul/core/config.py: ChangedSelfimport fromtypingtotyping_extensionsfor Python 3.10 compatibilitySelfwas introduced in Python 3.11'stypingmoduletyping_extensionsprovides backward compatibility for Python 3.102. Configuration Updates (
pyproject.toml)requires-pythonfrom>=3.12to>=3.10py310,py311,py312,py3133. 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_3dstest_token_sale_comparison_3ds_vs_non_3dstests/e2e/services/test_secure_integration.py(3 tests)test_secure_sale_direct_to_challengetest_secure_sale_challenge_after_methodtest_secure_sale_3ds_method_with_session_validationTests now correctly handle three 3DS response types:
redirect: true- 3DS Method/Challenge requiredvalue: {...}- Wrapped approval responseIsoCode: "00"- Direct frictionless approvalAll tests pass successfully on both Python 3.10 and 3.13.
Compatibility
Notes
typing_extensionsis already a transitive dependency via Pydantic