mirror of
https://github.com/Second-Hand-Friends/kleinanzeigen-bot.git
synced 2026-03-12 10:31:50 +01:00
fix: JSON API Pagination for >25 Ads (#797)
## ℹ️ Description *Provide a concise summary of the changes introduced in this pull request.* - Link to the related issue(s): Closes #789 (completes the fix started in #793) - **Motivation**: Fix JSON API pagination for accounts with >25 ads. Aligns pagination logic with weidi’s approach (starts at page 1), while hardening error handling and tests. Based on https://github.com/weidi/kleinanzeigen-bot/pull/1. ## 📋 Changes Summary - Added pagination helper to fetch all published ads and use it in delete/extend/publish/update flows - Added robust handling for malformed JSON payloads and unexpected ads types (with translated warnings) - Improved sell_directly extraction with pagination, bounds checks, and shared coercion helper - Added/updated tests for pagination and edge cases; updated assertions to pytest.fail style ### ⚙️ Type of Change Select the type(s) of change(s) included in this pull request: - [x] 🐞 Bug fix (non-breaking change which fixes an issue) - [ ] ✨ New feature (adds new functionality without breaking existing usage) - [ ] 💥 Breaking change (changes that might break existing user setups, scripts, or configurations) ## ✅ Checklist Before requesting a review, confirm the following: - [x] I have reviewed my changes to ensure they meet the project's standards. - [x] I have tested my changes and ensured that all tests pass (`pdm run test:cov:unified`). - [x] I have formatted the code (`pdm run format`). - [x] I have verified that linting passes (`pdm run lint`). - [x] I have updated documentation where necessary. By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Reliable multi-page fetching for published ads and buy-now eligibility checks. * **Bug Fixes** * Safer pagination with per-page JSON handling, limits and improved termination diagnostics; ensures pageNum is used when needed. * **Tests** * New comprehensive pagination tests and updates to existing tests to reflect multi-page behavior. * **Chores** * Added a utility to safely coerce page numbers; minor utility signature cleanup. <sub>✏️ Tip: You can customize this high-level summary in your review settings.</sub> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
@@ -16,12 +16,55 @@ from . import i18n
|
||||
T = TypeVar("T")
|
||||
|
||||
|
||||
def coerce_page_number(value:Any) -> int | None:
|
||||
"""Safely coerce a value to int or return None if conversion fails.
|
||||
|
||||
Whole-number floats are accepted; non-integer floats are rejected.
|
||||
|
||||
Args:
|
||||
value: Value to coerce to int (can be int, str, float, or any type)
|
||||
|
||||
Returns:
|
||||
int if value can be safely coerced, None otherwise
|
||||
|
||||
Examples:
|
||||
>>> coerce_page_number(1)
|
||||
1
|
||||
>>> coerce_page_number("2")
|
||||
2
|
||||
>>> coerce_page_number(3.0)
|
||||
3
|
||||
>>> coerce_page_number(3.5) is None
|
||||
True
|
||||
>>> coerce_page_number(True) is None # Not 1!
|
||||
True
|
||||
>>> coerce_page_number(None) is None
|
||||
True
|
||||
>>> coerce_page_number("invalid") is None
|
||||
True
|
||||
>>> coerce_page_number([1, 2, 3]) is None
|
||||
True
|
||||
"""
|
||||
if value is None:
|
||||
return None
|
||||
if isinstance(value, bool):
|
||||
return None
|
||||
if isinstance(value, float):
|
||||
if value.is_integer():
|
||||
return int(value)
|
||||
return None
|
||||
try:
|
||||
return int(value)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def ensure(
|
||||
condition:Any | bool | Callable[[], bool], # noqa: FBT001 Boolean-typed positional argument in function definition
|
||||
error_message:str,
|
||||
timeout:float = 5,
|
||||
poll_frequency:float = 0.5
|
||||
) -> None:
|
||||
condition:Any | bool | Callable[[], bool], # noqa: FBT001 Boolean-typed positional argument in function definition
|
||||
error_message:str,
|
||||
timeout:float = 5,
|
||||
poll_frequency:float = 0.5,
|
||||
) -> None:
|
||||
"""
|
||||
Ensure a condition is true, retrying until timeout.
|
||||
|
||||
@@ -152,12 +195,7 @@ def parse_decimal(number:float | int | str) -> decimal.Decimal:
|
||||
raise decimal.DecimalException(f"Invalid number format: {number}") from ex
|
||||
|
||||
|
||||
def parse_datetime(
|
||||
date:datetime | str | None,
|
||||
*,
|
||||
add_timezone_if_missing:bool = True,
|
||||
use_local_timezone:bool = True
|
||||
) -> datetime | None:
|
||||
def parse_datetime(date:datetime | str | None, *, add_timezone_if_missing:bool = True, use_local_timezone:bool = True) -> datetime | None:
|
||||
"""
|
||||
Parses a datetime object or ISO-formatted string.
|
||||
|
||||
@@ -184,10 +222,7 @@ def parse_datetime(
|
||||
dt = date if isinstance(date, datetime) else datetime.fromisoformat(date)
|
||||
|
||||
if dt.tzinfo is None and add_timezone_if_missing:
|
||||
dt = (
|
||||
dt.astimezone() if use_local_timezone
|
||||
else dt.replace(tzinfo = timezone.utc)
|
||||
)
|
||||
dt = dt.astimezone() if use_local_timezone else dt.replace(tzinfo = timezone.utc)
|
||||
|
||||
return dt
|
||||
|
||||
|
||||
Reference in New Issue
Block a user