Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
75f6db3
chore: update version to 2.24.0 in pyproject.toml and uv.lock
btoron Mar 4, 2026
af4b1a2
feat(metadata): add 22 async write methods (#138)
btoron Mar 4, 2026
b4d1c15
feat(metadata): update model_dump calls to include mode parameter and…
btoron Mar 4, 2026
cd345e8
feat(statistics): implement Statistics API GET operations (#141)
btoron Mar 4, 2026
f5f6f3f
fix(statistics): fix PATCH serialization and response model for live API
btoron Mar 4, 2026
5dcfb4d
feat(core): add async resource file property methods (CO057G/U/D) (#140)
btoron Mar 4, 2026
a9903a2
test(core): fix live resource file property test with proper fixture …
btoron Mar 4, 2026
32477c9
Merge branch '140-async-core-api-resource-properties-write-operations…
btoron Mar 4, 2026
735bffe
feat(core): add async get_multiday_segments method (CO003G) (#139)
btoron Mar 4, 2026
a96dd7d
fix(core): use dedicated MultidaySegmentListResponse without totalRes…
btoron Mar 4, 2026
6e38a17
Merge branch '139-async-core-api-miscellaneous-get-operations' into r…
btoron Mar 4, 2026
31242b6
feat(auth): implement async get_token via v2 OAuth endpoint (AU002P) …
btoron Mar 4, 2026
67c10be
fix(tests): standardize token_type casing to lowercase in OAuth tests
btoron Mar 4, 2026
d44edef
feat(auth): implement Bearer token auth for async client (#146)
btoron Mar 4, 2026
a59896d
feat(metadata): implement async capacity area sub-resources & misc GE…
btoron Mar 4, 2026
82f06fe
feat(tests): add cross-API validation for capacity area and resource …
btoron Mar 4, 2026
09c8587
feat(resources): update ResourceWorkzoneAssignment fields for enhance…
btoron Mar 4, 2026
f2fc151
fix(resources): add required params to get_calendars, get_resource_as…
btoron Mar 4, 2026
d1a949a
feat: merge fix for async resource methods required params
btoron Mar 4, 2026
c53f6f8
feat(models): audit and tighten metadata models with extra=allow (#149)
btoron Mar 4, 2026
4d42e46
fix(tests): update assertions for totalResults to allow non-negative …
btoron Mar 4, 2026
fe94155
fix(tests): skip tests requiring dedicated daily extract instance wit…
btoron Mar 4, 2026
fe572ef
fix(tests): update test_get_booking_statuses to use tomorrow's date
btoron Mar 4, 2026
b40d1b4
Add test execution summary report for version 2.24.0
btoron Mar 4, 2026
c65fdb1
feat: add ShiftUpdate model and update create_or_replace_shift method…
btoron Mar 4, 2026
5ff41ab
fix: update workskill methods to include model parameters and improve…
btoron Mar 4, 2026
05d0e6c
fix: update link template creation to use simultaneous type and UUID …
btoron Mar 4, 2026
daa023d
feat: merge issue #149 - audit and tighten pydantic models with extra…
btoron Mar 4, 2026
86a568f
fix: update .gitignore to include docs/qa.md and tests/audit_results
btoron Mar 4, 2026
51ca865
fix: update link template test to assert label is in request body, no…
btoron Mar 4, 2026
b016873
feat: enhance exception handling and response validation in API inter…
btoron Mar 4, 2026
925a97b
feat: add GitHub Actions workflow for automated release tagging
btoron Mar 4, 2026
566a6b0
test: fix offline test suite by adding uses_real_data markers and moc…
btoron Mar 4, 2026
62264f4
test: add uses_local_data marker to exclude saved response tests from CI
btoron Mar 4, 2026
28bee6f
test: fix 51 mocked tests to use mock_instance instead of async_instance
btoron Mar 4, 2026
fd1f94f
test: fix CI failures by adding missing uses_real_data markers and de…
btoron Mar 4, 2026
082ed45
feat: add ruff as a dependency and configure linting options
btoron Mar 4, 2026
a02532e
chore: apply ruff formatting to entire codebase
btoron Mar 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
name: Release

on:
pull_request:
types: [closed]

permissions:
contents: write

jobs:
release:
if: >
github.event.pull_request.merged == true &&
(contains(github.event.pull_request.labels.*.name, 'release:patch') ||
contains(github.event.pull_request.labels.*.name, 'release:minor') ||
contains(github.event.pull_request.labels.*.name, 'release:major'))
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Extract version
id: version
run: |
VERSION=$(grep '^version = ' pyproject.toml | sed 's/version = "\(.*\)"/\1/')
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "Release version: $VERSION"

- name: Create and push tag
run: |
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
TAG="v${{ steps.version.outputs.version }}"
git tag -a "$TAG" -m "Release $TAG"
git push origin "$TAG"
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ jobs:
run: uv sync --all-extras --dev

- name: Run tests (mocked only)
run: uv run pytest -m "not uses_real_data" -n auto
run: uv run pytest -m "not uses_real_data and not uses_local_data" -n auto

- name: Check code quality
run: |
Expand Down
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,5 @@ _*.json
tests/saved_responses
plans
tmp
docs/qa.md
docs/qa.md
tests/audit_results
113 changes: 90 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,7 @@ uv run pytest tests/async/test_async_workzones.py
delete_resource_preferences(self, activity_id) [Async]
get_capacity_categories(self, activity_id) [Async]
get_submitted_forms(self, activity_id, offset=0, limit=100) [Async]
get_multiday_segments(self, activity_id) [Async]
get_all_activities(self, root=None, date_from=date.today()-timedelta(days=7), date_to=date.today()+timedelta(days=7), activity_fields=["activityId", "activityType", "date", "resourceId", "status"], additional_fields=None, initial_offset=0, include_non_scheduled=False, limit=5000)


Expand Down Expand Up @@ -246,6 +247,9 @@ uv run pytest tests/async/test_async_workzones.py
delete_resource_workzone(self, resource_id, workzone_item_id) [Async]
delete_resource_workschedule(self, resource_id, schedule_item_id) [Async]
update_resource_location(self, resource_id, location_id, data) [Async]
get_resource_file_property(self, resource_id, property_label, media_type) [Async]
set_resource_file_property(self, resource_id, property_label, content, filename, content_type) [Async]
delete_resource_file_property(self, resource_id, property_label) [Async]

### Core / Inventories (Standalone)
create_inventory(self, data) [Async]
Expand Down Expand Up @@ -283,29 +287,94 @@ uv run pytest tests/async/test_async_workzones.py

### Metadata / Activity Type Groups
get_activity_type_groups (self, expand="parent", offset=0, limit=100, response_type=OBJ_RESPONSE)
get_activity_type_group (self,label, response_type=OBJ_RESPONSE)
get_activity_type_group (self,label, response_type=OBJ_RESPONSE)
create_or_replace_activity_type_group(self, data: ActivityTypeGroup) [Async]

### Metadata / Activity Types
get_activity_types(self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_activity_type (self, label, response_type=OBJ_RESPONSE)
create_or_replace_activity_type(self, data: ActivityType) [Async]

### Metadata / Applications
get_applications(self, response_type=OBJ_RESPONSE)
get_application(self, label: str, response_type=OBJ_RESPONSE)
get_application_api_accesses(self, label: str, response_type=OBJ_RESPONSE)
get_application_api_access(self, label: str, accessId: str, response_type=OBJ_RESPONSE)
create_or_replace_application(self, data: Application) [Async]
update_application_api_access(self, label: str, api_label: str, data: dict) [Async]
generate_application_client_secret(self, label: str) [Async]

### Metadata / Capacity
get_capacity_areas(self, expandParent: bool = False, fields: list[str] = ["label"], activeOnly: bool = False, areasOnly: bool = False, response_type=OBJ_RESPONSE)
get_capacity_area(self, label: str, response_type=OBJ_RESPONSE)
get_capacity_area_capacity_categories(self, label: str) [Async]
get_capacity_area_workzones(self, label: str) [Async]
get_capacity_area_workzones_v1(self, label: str) [Async]
get_capacity_area_time_slots(self, label: str) [Async]
get_capacity_area_time_intervals(self, label: str) [Async]
get_capacity_area_organizations(self, label: str) [Async]
get_capacity_area_children(self, label: str, status=None, fields=None, expand=None, type=None) [Async]
get_capacity_categories(self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_capacity_category(self, label: str, response_type=OBJ_RESPONSE)
create_or_replace_capacity_category(self, data: CapacityCategory) [Async]
delete_capacity_category(self, label: str) [Async]

### Metadata / Forms
get_forms(self, offset=0, limit=100) [Async]
get_form(self, label: str) [Async]
create_or_replace_form(self, data: Form) [Async]
delete_form(self, label: str) [Async]

### Metadata / Inventory
get_inventory_types(self, response_type=OBJ_RESPONSE)
get_inventory_type(self, label: str, response_type=OBJ_RESPONSE)
create_or_replace_inventory_type(self, data: InventoryType) [Async]

### Metadata / Link Templates
get_link_templates(self, offset=0, limit=100) [Async]
get_link_template(self, label: str) [Async]
create_link_template(self, data: LinkTemplate) [Async]
update_link_template(self, data: LinkTemplate) [Async]

### Metadata / Map Layers
get_map_layers(self, offset=0, limit=100) [Async]
get_map_layer(self, label: str) [Async]
create_or_replace_map_layer(self, data: MapLayer) [Async]
create_map_layer(self, data: MapLayer) [Async]
populate_map_layers(self, data: bytes | Path) [Async]
get_populate_map_layers_status(self, download_id: int) [Async]

### Metadata / Plugins
import_plugin(self, plugin: str)
import_plugin_file(self, plugin: Path)
install_plugin(self, plugin_label: str) [Async]

### Metadata / Properties
get_properties(self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_property(self, label: str, response_type=OBJ_RESPONSE)
create_or_replace_property(self, property: Property, response_type=OBJ_RESPONSE)
update_property(self, property: Property) [Async]
get_enumeration_values(self, label: str, offset=0, limit=100, response_type=OBJ_RESPONSE)
create_or_update_enumeration_value(self, label: str, value: Tuple[EnumerationValue, ...], response_type=OBJ_RESPONSE)

### Metadata / Resource Types
get_resource_types(self, response_type=OBJ_RESPONSE)

### Metadata / Routing Profiles
get_routing_profiles(self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_routing_profile_plans(self, profile_label: str, offset=0, limit=100, response_type=OBJ_RESPONSE)
export_routing_plan(self, profile_label: str, plan_label: str, response_type=OBJ_RESPONSE)
export_plan_file(self, profile_label: str, plan_label: str) -> bytes
import_routing_plan(self, profile_label: str, plan_data: bytes, response_type=OBJ_RESPONSE)
force_import_routing_plan(self, profile_label: str, plan_data: bytes, response_type=OBJ_RESPONSE)
start_routing_plan(self, profile_label: str, plan_label: str, resource_external_id: str, date: str, response_type=OBJ_RESPONSE)

### Metadata / Shifts
get_shifts(self, offset=0, limit=100) [Async]
get_shift(self, label: str) [Async]
create_or_replace_shift(self, data: Shift) [Async]
delete_shift(self, label: str) [Async]

### Metadata / Workskills
get_workskills (self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_workskill(self, label: str, response_type=OBJ_RESPONSE)
Expand All @@ -318,33 +387,16 @@ uv run pytest tests/async/test_async_workzones.py
create_or_update_workskill_group(self, group: WorkSkillGroup, response_type=OBJ_RESPONSE)
delete_workskill_group(self, label: str, response_type=OBJ_RESPONSE)

### Metadata / Plugins
import_plugin(self, plugin: str)
import_plugin_file(self, plugin: Path)

### Metadata / Resource Types
get_resource_types(self, response_type=OBJ_RESPONSE)

### Metadata / Workzones [Sync & Async]
get_workzones(self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_workzone(self, label: str, response_type=OBJ_RESPONSE)
create_workzone(self, workzone: Workzone, response_type=OBJ_RESPONSE) # Async only
replace_workzone(self, workzone: Workzone, auto_resolve_conflicts: bool = False, response_type=OBJ_RESPONSE)

### Metadata / Routing Profiles
get_routing_profiles(self, offset=0, limit=100, response_type=OBJ_RESPONSE)
get_routing_profile_plans(self, profile_label: str, offset=0, limit=100, response_type=OBJ_RESPONSE)
export_routing_plan(self, profile_label: str, plan_label: str, response_type=OBJ_RESPONSE)
export_plan_file(self, profile_label: str, plan_label: str) -> bytes
import_routing_plan(self, profile_label: str, plan_data: bytes, response_type=OBJ_RESPONSE)
force_import_routing_plan(self, profile_label: str, plan_data: bytes, response_type=OBJ_RESPONSE)
start_routing_plan(self, profile_label: str, plan_label: str, resource_external_id: str, date: str, response_type=OBJ_RESPONSE)

### Metadata / Applications
get_applications(self, response_type=OBJ_RESPONSE)
get_application(self, label: str, response_type=OBJ_RESPONSE)
get_application_api_accesses(self, label: str, response_type=OBJ_RESPONSE)
get_application_api_access(self, label: str, accessId: str, response_type=OBJ_RESPONSE)
replace_workzones(self, data: list[Workzone]) [Async]
update_workzones(self, data: list[Workzone]) [Async]
populate_workzone_shapes(self, data: bytes | Path) [Async]
get_populate_workzone_shapes_status(self, download_id: int) [Async]
get_workzone_key(self) [Async]

### Metadata / Organizations
get_organizations(self, response_type=OBJ_RESPONSE)
Expand All @@ -364,6 +416,21 @@ uv run pytest tests/async/test_async_workzones.py
show_booking_grid(self, data) [Async]
get_booking_fields_dependencies(self, areas=None) [Async]

### Statistics / Activity Duration Stats
get_activity_duration_stats(self, resource_id=None, include_children=None, akey=None, offset=0, limit=100) [Async]
update_activity_duration_stats(self, data) [Async]

### Statistics / Activity Travel Stats
get_activity_travel_stats(self, region=None, tkey=None, fkey=None, key_id=None, offset=0, limit=100) [Async]
update_activity_travel_stats(self, data) [Async]

### Statistics / Airline Distance Based Travel
get_airline_distance_based_travel(self, level=None, key=None, distance=None, key_id=None, offset=0, limit=100) [Async]
update_airline_distance_based_travel(self, data) [Async]

### Auth / OAuth2
get_token(self, request: OFSOAuthRequest = OFSOAuthRequest()) [Async]

## Usage Examples

### Capacity API
Expand Down
Loading