Skip to content

Releases: btoron/pyOFSC

v2.24.1 - Logging & Code Quality

05 Mar 00:06
9d5200d

Choose a tag to compare

$(cat <<'EOF'

pyOFSC v2.24.1 - Logging & Code Quality

Release Date: March 4, 2026


🎯 New Features

Request/Response Logging (#110)

Optional httpx event hooks for automatic API call tracing on the async client:

import logging
logging.basicConfig(level=logging.DEBUG)

async with AsyncOFSC(clientID="...", secret="...", companyName="...", enable_logging=True) as client:
    workzones = await client.metadata.get_workzones()
    # DEBUG: Request: GET https://company.fs.ocs.oraclecloud.com/rest/ofscMetadata/v1/workZones
    # DEBUG: Response: GET ...  200
  • Logs under ofsc.async_client logger
  • HTTP errors (4xx/5xx) logged at WARNING level
  • Disabled by default with zero overhead

🐛 Bug Fixes

baseUrl validation in async modules (#150)

core/_base.py and oauth.py could return None from baseUrl, causing cryptic urljoin(None, ...) errors. Now raises ValueError("Base URL is not configured") matching the pattern in capacity.py and statistics.py.

Security: OAuth token leak removed

Removed print() statement in ofsc/models/_base.py that printed the OAuth bearer token to stdout.


🔧 Improvements

Named loggers across library code

Replaced root logger (logging.debug(...)) with named loggers (logger = logging.getLogger(__name__)) in:

  • ofsc/common.py
  • ofsc/core.py
  • ofsc/models/_base.py
  • ofsc/async_client/__init__.py

Pagination logging downgraded to DEBUG

5 INFO-level log calls in get_all_activities and get_all_properties pagination loops downgraded to DEBUG.

Removed leftover print statements

Removed 2 debug print() calls in ofsc/core.py (create_resource_location, get_all_activities).

README improvements

Updated README with clearer usage examples for both sync and async clients.


📊 Test Coverage

  • 447 tests passing (6 new logging tests)
  • Zero pydantic deprecation warnings

Files Changed

File Changes
ofsc/async_client/__init__.py enable_logging param, event hooks, named logger
ofsc/async_client/core/_base.py baseUrl None validation
ofsc/async_client/oauth.py baseUrl None validation
ofsc/common.py Named logger
ofsc/core.py Named logger, remove prints, DEBUG pagination
ofsc/models/_base.py Named logger, remove token print
tests/async/test_async_ofsc.py 6 new logging tests
README.md Updated usage examples and logging docs
EOF
)

v2.24.0 - Async Core & Write Operations

04 Mar 23:13
f03ac19

Choose a tag to compare

pyOFSC v2.24.0 - Async Core & Write Operations

Release Date: March 4, 2026
Highlights: Full async Core API, Async Metadata write operations, Async Capacity API, Statistics API, OAuth Bearer token auth, Pydantic model audit


🎯 Major Features

1. Async Core API - Full Implementation

Complete async implementation of the Core API covering all major resource types:

Activities

  • get_activities() - List activities with comprehensive filtering (GetActivitiesParams model)
  • get_activity() - Get single activity details
  • create_activity() - Create new activity
  • update_activity() - Update activity fields
  • delete_activity() - Delete activity
  • move_activity() - Move activity between resources

Resources (GET + Write/Delete)

  • get_resources() - List resources with filters
  • get_resource() - Get resource details
  • create_or_replace_resource() - Create/replace resource (ResourceCreate model)
  • update_resource() - Partial update resource
  • delete_resource() - Delete resource
  • get_resource_workzones() / update_resource_workzones() - Workzone assignments
  • get_resource_workskills() / update_resource_workskills() - Workskill assignments
  • get_resource_availability() - Resource availability
  • get_calendars() / get_calendar() - Calendar management
  • get_resource_assistants() / get_resource_plans() - Assistant and plan management
  • get_position_history() - Position tracking
  • Inventory CRUD: get_inventories(), create_inventory(), update_inventory(), delete_inventory(), move_inventory()
  • File properties: get_resource_file_property(), set_resource_file_property(), delete_resource_file_property()

Users

  • get_users() - List users
  • get_user() - Get user details
  • create_or_update_user() - Create/update user (User/UserCreate models)
  • delete_user() - Delete user
  • get_collaboration_groups() / get_collaboration_group() - Group management

Other Core Operations

  • get_events() - Event stream with filters
  • get_subscriptions() / create_subscription() / delete_subscription() - Event subscriptions
  • get_daily_extract() - Bulk data extraction
  • get_multiday_segments() - Multiday segment retrieval (#139)
  • File properties (CO057G/U/D): get_resource_file_property(), set_resource_file_property(), delete_resource_file_property() (#140)

2. Async Metadata Write Operations (#138)

22 new async write methods covering the full metadata management lifecycle:

  • Activity Types: create_or_replace_activity_type(), delete_activity_type(), create_or_replace_activity_type_group(), delete_activity_type_group()
  • Workzones: create_or_replace_workzone(), delete_workzone()
  • Workskills: create_or_replace_work_skill(), delete_work_skill(), create_or_replace_work_skill_group(), delete_work_skill_group()
  • Capacity Areas: create_or_replace_capacity_area(), delete_capacity_area(), plus 8 sub-resource write methods (#137)
  • Capacity Categories: create_or_replace_capacity_category(), delete_capacity_category()
  • Time Slots: create_or_replace_time_slot(), delete_time_slot()
  • Non-Working Reasons: create_or_replace_non_working_reason(), delete_non_working_reason()
  • Shifts: create_or_replace_shift() (with ShiftUpdate model), delete_shift()
  • Link Templates: create_link_template(), delete_link_template()
  • Organizations: create_or_replace_organization(), delete_organization()

3. Async Capacity API (#128)

Complete async implementation with 10 methods:

  • get_available_capacity() - Query available capacity
  • get_quota() - Get quota information
  • Plus 8 additional capacity query methods
  • Fixed OAuth token caching bug affecting capacity requests
  • Added determineAreaByWorkZone parameter support

4. Statistics API - GET Operations (#141)

New async Statistics API module:

  • get_statistics() - Retrieve statistics data
  • PATCH serialization support with correct response model
  • Full Pydantic model validation (ofsc/models/statistics.py)

5. OAuth Bearer Token Auth for Async Client (#146)

  • get_token() - Async OAuth token retrieval via v2 endpoint (AU002P)
  • Bearer token authentication mode for async client
  • Immutable OFSOAuthRequest model
  • Token caching fix for async requests

6. Pydantic Model Audit (#149)

Comprehensive audit and tightening of all Pydantic models:

  • Added extra="allow" to models accepting arbitrary OFSC fields
  • Improved validation consistency across all model classes
  • Updated model_dump() calls to include mode parameter for correct serialization
  • Enhanced ResourceWorkzoneAssignment fields for additional metadata
  • Fixed MultidaySegmentListResponse (dedicated model without totalResults)

🏗️ Architecture Improvements

Async Core as Package with Mixins

The ofsc/async_client/core/ was refactored into a proper Python package:

  • core/__init__.py - Composes AsyncOFSCore from mixins
  • core/_base.py - Base activities, events, daily extract, subscriptions
  • core/users.py - AsyncOFSCoreUsersMixin with user management
  • core/resources.py - AsyncOFSCoreResourcesMixin with full resource operations
  • core/inventories.py - Standalone inventory CRUD

Model Files Organization

Models extracted into dedicated files:

  • ofsc/models/_base.py - Base types (OFSResponseList, etc.)
  • ofsc/models/resources.py - 35+ resource models
  • ofsc/models/users.py - User and collaboration group models
  • ofsc/models/metadata.py - Metadata models (430+ lines)
  • ofsc/models/statistics.py - Statistics API models
  • ofsc/models/capacity.py - Capacity API models

🔧 Code Quality

Ruff Integration

  • Added ruff as a project dependency with full configuration
  • Applied ruff formatting to the entire codebase
  • Configured linting rules in pyproject.toml

CI/CD Improvements

  • Added GitHub Actions workflow for automated release tagging (.github/workflows/release.yml)
  • Fixed 51 mocked tests to use mock_instance instead of async_instance
  • Added uses_local_data marker to separate saved-response tests from CI
  • Added uses_real_data markers to properly exclude live tests from CI
  • Parallel test execution support (#130)

Exception Handling

  • Enhanced exception handling and response validation across all API interactions
  • Improved error propagation for network and HTTP errors

🧪 Testing

New Test Files

  • tests/async/test_async_metadata_write.py - 772 lines, write operation tests
  • tests/async/test_async_metadata_roundtrip.py - 714 lines, round-trip validation
  • tests/async/test_metadata_model_audit.py - 682 lines, model audit tests
  • tests/async/test_async_statistics.py - 1,030 lines, Statistics API tests
  • tests/async/test_async_oauth.py - 189 lines, OAuth tests
  • tests/async/test_async_populate_status.py - 152 lines, populate status tests
  • tests/REQUIREMENTS_TEST_MAPPING.md - Requirement-to-test mapping
  • tests/TEST_EXECUTION_SUMMARY.md - Execution summary report

Test Coverage

  • Full mocked test suite runnable without API credentials
  • Live integration tests with @pytest.mark.uses_real_data
  • Cross-API validation tests for capacity areas and resource workzones

🐛 Bug Fixes

  • OAuth token caching - Fixed bug in async client token refresh
  • MultidaySegmentListResponse - Dedicated model without totalResults
  • Link template tests - Fixed to assert label in request body, not URL
  • Link template creation - Updated to use simultaneous type and UUID for labels
  • Workskill methods - Added missing model parameters and improved response handling
  • Resource methods - Added required params to get_calendars(), get_resource_assistants(), get_resource_plans()
  • Daily extract tests - Skip tests requiring dedicated instance credentials
  • Token type casing - Standardized to lowercase in OAuth tests

📦 Dependency Updates

Package From To
cryptography 44.0.1 46.0.5
requests 2.32.3 2.32.4
urllib3 2.2.3 2.6.3

📊 Statistics

Metric Count
Issues Resolved #117, #118, #120, #121, #122, #124, #125, #126, #128, #130, #137, #138, #139, #140, #141, #146, #149
API Methods Added 60+
Models Created/Updated 30+
Test Files Created 8
Net Lines Added +9,460

🔄 Migration Guide

Breaking Changes

None - All changes are backward compatible.

New Features Usage

from ofsc import AsyncOFSC

async with AsyncOFSC(
    clientID="your_client_id",
    companyName="your_company",
    secret="your_secret"
) as client:
    # Core API - Activities
    activities = await client.core.get_activities(date="2026-03-04")
    
    # Core API - Resources (full CRUD)
    resources = await client.core.get_resources()
    await client.core.create_or_replace_resource(resource_data)
    inventories = await client.core.get_inventories("resource_id")
    
    # Core API - Users
    users = await client.core.get_users()
    await client.core.create_or_update_user(user_data)
    
    # Metadata Write Operations
    await client.metadata.create_or_replace_workzone(workzone)
    await client.metadata.delete_workzone("ZONE_LABEL")
    
    # Capacity API
    capacity = await client.capacity.get_available_capacity(...)
    
    # Statistics API
    stats = await client.statistics.get_statistics(...)
    
    # OAuth - Bearer Token
    token = await client.oauth2.get_token()

🙏 Contributors

  • Borja Toron (@btoron) - Primary development
  • Claude Sonnet 4.5/4.6 - AI pair programming assistance

🔗 Links

Read more

v2.21.0 - Async Metadata API Phase 2

30 Dec 00:19
bcfe49d

Choose a tag to compare

pyOFSC v2.21.0 - Async Metadata API Phase 2

Release Date: December 30, 2025
Highlights: 9 new async API modules, 31 methods, comprehensive type safety improvements


🎯 Major Features

Async Metadata API - Phase 2 Implementation

This release completes Phase 2 of the async metadata API implementation, adding async support for 9 critical OFSC metadata modules.

1. Applications API

  • get_applications() - List all applications
  • get_application(label) - Get application details
  • get_application_api_accesses(label) - List API accesses
  • get_application_api_access(label, accessId) - Get specific API access

2. Capacity Areas API

  • get_capacity_areas() - List all capacity areas
  • get_capacity_area(label) - Get capacity area details

3. Capacity Categories API

  • get_capacity_categories(offset, limit) - List with pagination
  • get_capacity_category(label) - Get category details

4. Forms API

  • get_forms(offset, limit) - List forms with pagination
  • get_form(label) - Get form details

5. Map Layers API

  • get_map_layers(offset, limit) - List map layers with pagination
  • get_map_layer(label) - Get map layer details

6. Plugins API

  • import_plugin_file(file_path) - Import plugin from XML file
  • import_plugin(plugin_xml) - Import plugin from XML string

7. Routing Profiles API (Full CRUD)

  • get_routing_profiles(offset, limit) - List routing profiles
  • get_routing_plans(profile_label, offset, limit) - List plans
  • export_routing_plan(profile_label, plan_label) - Export plan
  • import_routing_plan(profile_label, plan_label, data) - Import plan
  • replace_routing_plan(profile_label, plan_label, data) - Replace plan
  • create_routing_plan(profile_label, plan_label, data) - Create plan
  • delete_routing_plan(profile_label, plan_label) - Delete plan

8. Shifts API

  • get_shifts(offset, limit) - List shifts with pagination
  • get_shift(label) - Get shift details

9. Work Skills API (Full CRUD)

  • get_work_skills(offset, limit) - List work skills
  • get_work_skill(label) - Get work skill details
  • create_or_replace_work_skill(workskill) - Create/update work skill
  • delete_work_skill(label) - Delete work skill
  • get_work_skill_conditions() - List all conditions
  • get_work_skill_groups(offset, limit) - List groups
  • get_work_skill_group(label) - Get group details
  • create_or_replace_work_skill_group(group) - Create/update group
  • delete_work_skill_group(label) - Delete group

🔧 Code Quality Improvements

Type Safety Enhancements

  • Replaced List[] with list[] - 70 occurrences updated for modern Python typing
  • Added pyright to dev dependencies - Static type checking support
  • Fixed all pyright errors - Reduced from 13 errors to 0
  • Added type: ignore annotations - 7 RootModel classes properly annotated

Model Improvements

  • Fixed duplicate Link class - Renamed to DailyExtractLink
  • Made Application fields Optional - Proper type safety
  • Fixed baseUrl property - Correct type annotation
  • Removed invalid __next__() methods - From response list classes
  • Fixed enum type mismatch - In Recurrence model
  • Added None checks - For optional member access and iterables
  • Fixed Property.translations - Changed to Optional[TranslationList]

Documentation

  • PEP 287 docstrings - All async metadata methods converted to reStructuredText format
  • New scripts/README.md - 504 lines documenting utility scripts
  • Updated ENDPOINTS.md - Current implementation status for all 243 endpoints

Code Organization

  • Reorganized models.py - Application and Routing Profile models in alphabetical regions
  • Deleted empty regions - 4 regions removed for cleaner code
  • Consistent naming - Fixed Workskill/WorkskillGroup inconsistencies

🧪 Testing

New Test Coverage

  • 9 new test files with 3,340 lines
  • 100+ test cases covering all new functionality
  • 50+ saved API responses for validation testing
  • All tests passing: 266/266 ✅

Test Structure

  • Live tests with @pytest.mark.uses_real_data
  • Model validation tests
  • Saved response validation tests
  • Error handling tests (404, authentication, etc.)
  • Comprehensive CRUD operation tests

Coverage Metrics

  • Overall: 84% (exceeds 80% target)
  • async_client/metadata.py: 95%
  • models.py: 87%
  • exceptions.py: 100%

🐛 Bug Fixes

Critical Fixes

  1. RoutingPlanData Validation - Made fields optional for varied API responses
  2. Optional Member Access - Added None checks before calling methods
  3. Import Naming - Fixed WorkskillConditionList typo
  4. Type Annotations - Standardized across all models

Minor Fixes

  • Updated capacity area validation test label
  • Updated Status enum usage in CapacityArea and Workzone
  • Corrected endpoint statuses in documentation
  • Removed deprecated routing profiles capture script

📦 New Dependencies

Development

  • pyright>=1.1.390,<2 - Static type checker for Python

📊 Statistics

Metric Count
Issues Resolved 9 (#92, #93, #94, #95, #99, #102, #105, #106, #108)
API Methods Added 31
Models Created/Updated 18
Test Files Created 9
Test Cases Added 100+
Saved Responses 50+
Documentation Lines 1,050+
Net Lines Added +5,253

🔄 Migration Guide

Breaking Changes

None - All changes are backward compatible.

New Features Usage

from ofsc import AsyncOFSC

async with AsyncOFSC(
    clientID="your_client_id",
    companyName="your_company",
    secret="your_secret"
) as client:
    # Applications API
    apps = await client.metadata.get_applications()
    app = await client.metadata.get_application("app_label")
    
    # Routing Profiles API
    profiles = await client.metadata.get_routing_profiles()
    plans = await client.metadata.get_routing_plans("profile_label")
    plan_data = await client.metadata.export_routing_plan(
        "profile_label", "plan_label"
    )
    
    # Work Skills API (CRUD)
    skills = await client.metadata.get_work_skills()
    skill = await client.metadata.get_work_skill("skill_label")
    await client.metadata.create_or_replace_work_skill(workskill)
    await client.metadata.delete_work_skill("skill_label")
    
    # All other new APIs follow the same pattern
    forms = await client.metadata.get_forms()
    shifts = await client.metadata.get_shifts()
    capacity_areas = await client.metadata.get_capacity_areas()

Type Checking Support

# Models now fully support modern Python typing
from ofsc.models import (
    Application,
    RoutingProfileList,
    Workskill,
    Form
)

# Use with mypy or pyright for static type checking
apps: list[Application] = []  # Modern syntax supported

📝 Documentation

New Documentation

  • scripts/README.md - Comprehensive guide for utility scripts
    • capture_api_responses.py - How to capture API responses
    • update_endpoints_doc.py - How to update endpoint documentation
    • Usage examples, best practices, troubleshooting

Updated Documentation

  • ENDPOINTS.md - Current implementation status (89 sync, 53 async endpoints)
  • All async metadata docstrings - Now in PEP 287 reStructuredText format

🚀 What's Next

Phase 3 (Upcoming)

  • Remaining metadata endpoints (see issue #109)
  • Additional CRUD operations
  • Batch operations support

Future Releases

  • Async Core API implementation
  • Async Capacity API implementation
  • OAuth token-based authentication for async client
  • Enhanced error handling and retries
  • Rate limiting support

🙏 Contributors

  • Borja Toron (@btoron) - Primary development
  • Claude Sonnet 4.5 - AI pair programming assistance

📋 Full Changelog

Features

  • feat: implement async routing profiles API methods and add tests (86db72c)
  • feat: implement async methods for capacity areas and add comprehensive tests (3448768)
  • feat: implement async methods for capacity categories and add comprehensive tests (3dc52d1)
  • feat: implement async methods for map layers and add comprehensive tests (aafcf98)
  • feat: implement async methods for forms and add comprehensive tests (503377d)
  • feat: implement async methods for shifts and add comprehensive tests (9671b40)
  • feat: implement async methods for work skills and conditions with comprehensive tests (f87e963)
  • feat: Add async applications and plugins API implementation (5db8cf1)
  • feat: Add comprehensive README for utility scripts (5ead113)
  • feat: add endpoints for work skills in API capture script (52bdbbc)
  • feat: add live and model tests for CRUD operations (a7fd98b)
  • feat: Update RoutingPlanData model for optional fields (f48d935)
  • feat: Refactor metadata models for applications and routing profiles (5383307)
  • feat: Add pyright to development dependencies (b9ad391)
  • feat: update status fields to use Status enum (f2a77d0)

Bug Fixes

  • fix: correct naming inconsistencies for Workskill and WorkskillGroup (7018dfa)
  • fix: Correct typo in import statement for WorkskillConditionList (6e64aaa)
  • fix: Update endpoint statuses in documentation (5557984)
  • test: update expected label for capacity area validation (ff18bed)

Refactoring

  • refactor: Replace List with built-in list type (202049d)
  • refactor: Simplify model classes and update type hints (de8086f)
  • refactor: Docstrings to use reStructuredText (35440fd)
  • feat: remove deprecated routing profiles capture script (e2f569e)

Version Updates

  • chore: Update version to 2.21.0 (01dfffe)

🔗 Links

Read more

Async Client

23 Dec 20:56
d28b50f

Choose a tag to compare

What's Changed

  • 86 add asyncofsc class with httpx async support by @btoron in #89

Full Changelog: v2.18.1...v2.19

v2.18.1

12 Dec 05:14
310e517

Choose a tag to compare

What's Changed

Full Changelog: v2.18...v2.18.1

v2.18

12 Dec 04:51
df5fe35

Choose a tag to compare

What's Changed

  • 83 add replace workzone method to metadata api by @btoron in #84

Full Changelog: v2.17...v2.18

v2.17

28 Oct 13:45
ffece3d

Choose a tag to compare

What's Changed

  • 78 add models and routes for route profiles and plan management by @btoron in #79
  • feat: Add get_resources method by @btoron in #82

Full Changelog: v2.15...v2.17

v2.15

02 Oct 00:56
7fa6cc9

Choose a tag to compare

What's Changed

Full Changelog: v2.14.1...v2.15

v2.14.1

25 Jun 20:52
caf8ed5

Choose a tag to compare

What's Changed

  • Update capacity and model handling: enhance fields type and version b… by @btoron in #72

Full Changelog: v2.14...v2.14.1

v2.14

25 Jun 17:17
44dfa54

Choose a tag to compare

Summary

This release adds comprehensive capacity management functionality to pyOFSC, including:

  • New OFSCapacity module for capacity and quota operations
  • Support for both getAvailableCapacity and getQuota endpoints
  • Flexible parameter handling with CsvList support
  • Generic internal converter function using type inspection

Major Changes

1. New Capacity Module (ofsc/capacity.py)

  • getAvailableCapacity(): Get available capacity with individual parameters
    • Supports list, CSV string, and CsvList inputs for areas, dates, categories
    • Consistent with getQuota signature pattern
  • getQuota(): Get quota information with flexible parameters
    • Boolean parameters automatically converted to lowercase strings
    • All string array parameters support multiple input formats
  • _convert_model_to_api_params(): Internal generic converter using inspection
    • Automatically detects CsvList and boolean fields
    • No hardcoded field names - works with any Pydantic model
    • DRY implementation shared by both public methods

2. Enhanced Models (ofsc/models.py)

  • CsvList: Utility model for comma-separated string lists
    • from_list() and to_list() conversion methods
    • Automatic conversion in field validators
  • CapacityRequest: Updated to use CsvList fields with validators
  • GetQuotaRequest: Request model with comprehensive parameters
  • GetQuotaResponse and QuotaAreaItem: Response models for quota data
  • All models support flexible input formats (list[str], str, CsvList)

3. Module Integration

  • Capacity module integrated as top-level API alongside core and metadata
  • Access via ofsc_instance.capacity.getAvailableCapacity() and ofsc_instance.capacity.getQuota()

4. Comprehensive Test Suite

  • 30+ mocked tests covering all functionality
  • 19+ integration tests for real server validation
  • Real server responses captured for consistent mocking
  • Tests for all input formats and edge cases
  • Performance tests included

API Examples

# Get available capacity
response = ofsc.capacity.getAvailableCapacity(
    dates=["2025-06-25", "2025-06-26"],
    areas=["FLUSA", "CAUSA"],
    categories=["EST", "RES"]
)

# Get quota information  
response = ofsc.capacity.getQuota(
    dates="2025-06-25",
    areas=["FLUSA", "CAUSA"],
    aggregateResults=True,
    categoryLevel=False
)

Breaking Changes

- getAvailableCapacity signature changed from accepting CapacityRequest object to individual parameters
- This aligns with the getQuota pattern for consistency

Testing

- All tests passing (30 mocked, 19 integration)
- Tested with real OFSC server using FLUSA/CAUSA areas
- Performance validated with week-long date ranges