Dedup with precision=datetime crashed with TypeError: redminelib returns
naive created_on/updated_on while the cutoff from _compute_dedup_cutoff
is aware UTC. Normalize at a single point: _parse_datetime now always
returns an aware datetime (naive treated as UTC), matching its docstring.
The dedup cutoff is normalized the same way, and any residual comparison
TypeError is wrapped in RedmineAPIError instead of leaking raw.
Also fix --commit saving last_used.to as naive local time; it now stores
aware UTC (datetime.now(timezone.utc)) so the next run computes a correct
aware cutoff.
Closes#58
The trap mocked issue.filter to return only issue 2, so result == [2]
would pass even with a wrong fix that suppresses TypeError but keeps all
entries. Now the API mock returns both issues; only correct filtering
of the pre-cutoff time entry keeps the result at [2].
Refs #67, #58
- tests/test_cli.py: fix 26 fetch_issues_with_spent_time mocks to return
3-element tuples (issue, hours, activities) matching the real signature
- pyproject.toml: add [tool.pytest.ini_options] with testpaths and
pythonpath so bare pytest works
- tests/test_client.py: add pagination test (>100 time entries arriving
in pages, hours aggregated across all pages)
- tests/test_formatters.py: add structural tests — XLSX full header row
and grand total row via openpyxl, HTML thead with all columns and
tbody row count
- add strict xfail trap-tests for known bugs: #58 (naive created_on vs
aware dedup cutoff TypeError) and #59 (parse_date_range rejects
datetime range from dynamic+datetime period)
Closes#67
#43 — Имя файла и пути по умолчанию:
- resolve_output_path() в yaml_config.py: резолвит --output с учётом
output.dir, filename_template, default_format из YAML-конфига
- Bare format (xlsx/odt/...) → путь по шаблону
- Без расширения → автодописывается default_format (.xlsx)
- Config.get_output_dir/filename/default_format()
#47 — datetime precision и дедупликация:
- period.last_used.from/to в YAML-конфиге и AppConfig
- period.precision: date|datetime
- _compute_dedup_cutoff() в cli.py: при precision=datetime вычисляет
cutoff из period.last_used.to
- _parse_datetime() + AND-логика дедупликации в client.py:
запись исключается если created_on И updated_on < cutoff
- Пропуск issues без часов после дедупликации
Closes#43Closes#47
- Load time entry activity names from Redmine enumeration.
- Aggregate hours per issue and per activity in fetch_issues_with_spent_time.
- Add --by-activity CLI flag and propagate it through report builder,
summary, and all formatters (console, CSV, HTML, JSON, ODT).
- Keep backward-compatible 2-tuple input in build_grouped_report.
- Bump version to 1.8.0.
Closes#41
- Add user_id parameter to fetch_issues_with_spent_time().
- Support numeric ID, login, or full name resolution.
- Reject ambiguous names and unknown users with clear messages.
- Add CLI flags: --user-id, --user-login, --user-name.
- Only allow one user flag at a time.
- Add ResourceNotFoundError handling.
- Update README with usage examples.
- Add tests for user resolution and CLI flags.
Closes#40
- Introduce RedmineAPIError with human-friendly messages.
- Distinguish AuthError, ForbiddenError, HTTP status codes, timeouts
and connection errors in client.py.
- Update CLI to print the readable message instead of generic
"Redmine API error: ...".
- Log original exception with traceback when --debug is enabled.
- Add tests for all error paths and CLI output.
Closes#39
python-redmine exposes as a context manager, while the
actual requests.Session lives in . Mounting the
retry adapter on the wrong object caused:
'function' object has no attribute 'mount'
on startup. Update the related test to mock the real session.
- #24: Mount HTTPAdapter with Retry(total=3, backoff_factor=0.5) for
HTTP 429/500/502/503/504 on the Redmine session, set 30s timeout
- #21: Split large issue_id lists into chunks of 100 to avoid exceeding
URL length limits on reverse proxies
Closes#24, #21