Metadata-Version: 2.5
Name: ofsc
Version: 2.27.2
Summary: Python wrapper for Oracle Field Service API
Project-URL: Repository, https://github.com/btoron/pyOFSC
Author-email: Borja Toron <borja.toron@gmail.com>
License-Expression: MIT
License-File: LICENSE.txt
Requires-Python: >=3.13
Requires-Dist: cachetools>=5.5.0
Requires-Dist: httpx[http2]<1,>=0.28.1
Requires-Dist: pydantic-settings<3,>=2.6.1
Requires-Dist: pydantic<3,>=2.12.5
Requires-Dist: requests<3,>=2.32.4
Description-Content-Type: text/markdown

## pyOFSC

**Oracle Fusion Field Service** (formerly Oracle Field Service Cloud, formerly ETAdirect) is a cloud-based field service management platform for scheduling, dispatching, and managing mobile workforces.

**pyOFSC** is a Python wrapper for its REST API, providing both synchronous and asynchronous clients with Pydantic model-based validation. See the [official Oracle Fusion Field Service documentation](https://docs.oracle.com/en/cloud/saas/field-service/index.html) for API details.

## Async Client

Starting with version 2.19, pyOFSC includes an async client (`AsyncOFSC`) that provides asynchronous API access using `httpx` and Python's `async`/`await` patterns.

**Implementation Status**: The async client is being implemented progressively. Currently available async methods are marked with `[Sync & Async]` tags in [docs/ENDPOINTS.md](docs/ENDPOINTS.md).

### Usage Example
```python
from ofsc.async_client import AsyncOFSC
async with AsyncOFSC(clientID="...", secret="...", companyName="...") as client:
    workzones = await client.metadata.get_workzones()
```

### Key Features
- **Async/Await Support**: Full async/await pattern support for non-blocking I/O
- **Same Models**: Reuses all existing Pydantic models from the sync version
- **Context Manager**: Must be used as an async context manager to properly manage HTTP client lifecycle
- **Simplified API**: Async methods always return Pydantic models (no `response_type` parameter)
- **Request/Response Logging**: Optional httpx event hooks for automatic API call tracing
- **Async Generators**: Lazy pagination helpers that yield individual items across all pages (e.g. `get_all_workzones`, `get_all_resources`) `[Async]`

### Streaming Daily Extract Downloads

Download large extracts incrementally with `core.get_daily_extract_file_stream`:

```python
async with AsyncOFSC(clientID="...", secret="...", companyName="...") as client:
    async with client.core.get_daily_extract_file_stream(
        date="2024-01-15", filename="extract.zip", chunk_size=8192
    ) as stream:
        with open("extract.zip", "wb") as output:
            async for chunk in stream:
                output.write(chunk)
```

The async context keeps the response open while chunks are consumed and closes it
on completion, early exit, errors, or cancellation. `chunk_size` must be positive.
Successful downloads are not buffered in full; HTTP error bodies are read to
preserve the same typed API exceptions as the buffered download method.

### Enabling Request/Response Logging

Pass `enable_logging=True` to automatically log all HTTP requests and responses via Python's standard logging:

```python
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 https://company.fs.ocs.oraclecloud.com/rest/ofscMetadata/v1/workZones 200
```

Logs are emitted under the `ofsc.async_client` logger. HTTP errors (4xx/5xx) are also logged at WARNING level. Disabled by default with zero overhead.

### HTTP Transport Configuration

Pass an `HTTPClientConfig` to tune transport behavior — most commonly to cap concurrency against the OFSC tenant, but also to set timeouts, retries, a proxy, and other knobs. All fields are optional; defaults preserve historical behavior. The surface is library-neutral (only scalars) so the underlying HTTP library can change without breaking your code.

```python
from ofsc.async_client import AsyncOFSC, HTTPClientConfig

async with AsyncOFSC(
    clientID="...",
    secret="...",
    companyName="...",
    http_config=HTTPClientConfig(
        max_concurrency=20,   # cap in-flight connections to the tenant
        timeout=30.0,         # seconds, per request
        max_retries=2,        # connection-level retries
    ),
) as client:
    workzones = await client.metadata.get_workzones()
```

Available fields: `max_concurrency`, `timeout`, `max_retries`, `proxy`, `verify_ssl`, `http2`, `follow_redirects`, `trust_env`.

## Models

All API entities use Pydantic v2 models. See `ofsc/models/` for available models.

## Testing

pyOFSC includes a comprehensive test suite with 500+ tests. Tests run in parallel by default using pytest-xdist for 10x faster execution.

### Running Tests

```bash
# Run all tests (parallel for safe tests, sequential for serial tests)
uv run pytest

# Run tests with specific number of workers
uv run pytest -n 4 -m "not serial"

# Run all tests sequentially (disable parallel execution)
uv run pytest -n 0

# Run only serial tests (sequential execution)
uv run pytest -m serial -n 0

# Run only mocked tests (no API credentials needed)
uv run pytest -m "not uses_real_data"

# Run specific test file
uv run pytest tests/async/test_async_workzones.py
```

**Note:** By default, tests marked with `@pytest.mark.serial` are excluded from parallel execution to prevent conflicts when modifying shared API state. To run all tests including serial ones, use: `uv run pytest -m "" -n auto && uv run pytest -m serial -n 0`

### Test Requirements

- **Mocked tests**: No special requirements, use saved API responses
- **Live tests** (marked with `@pytest.mark.uses_real_data`): Require API credentials in `.env` file:
  ```
  OFSC_CLIENT_ID=your_client_id
  OFSC_COMPANY=your_company
  OFSC_CLIENT_SECRET=your_secret
  ```

### Test Markers

- `@pytest.mark.uses_real_data` - Tests that require API credentials
- `@pytest.mark.serial` - Tests that must run sequentially (automatically excluded from parallel execution)
- `@pytest.mark.slow` - Slow-running tests
- `@pytest.mark.integration` - Integration tests

## Implemented Functions

**197 async endpoints** (81% coverage) and **89 sync endpoints** (37% coverage) across Core, Metadata, Capacity, Statistics, and Auth modules.

See [docs/ENDPOINTS.md](docs/ENDPOINTS.md) for the full implementation status table.

### Async core operations

- `core.search_activities(params)` returns an `ActivityListResponse` and accepts
  the [search API's query parameters](https://docs.oracle.com/en/cloud/saas/field-service/cxfsc/op-rest-ofsccore-v1-activities-custom-actions-search-get.html),
  including `searchInField`, `searchForValue`, date filters, offset and limit.
- `core.move_activity(activity_id, data)` sends the action object and returns
  `None` for a successful [204 move response](https://docs.oracle.com/en/cloud/saas/field-service/cxfsc/op-rest-ofsccore-v1-activities-activityid-custom-actions-move-post.html).
- `core.bulk_update(BulkUpdateRequest(...))` returns a `BulkUpdateResponse`.
  Explicit nulls are preserved, including `activityId=None` for creation; unset
  optional fields are omitted. Inspect each result's errors, operations and
  warnings: [bulk updates](https://docs.oracle.com/en/cloud/saas/field-service/cxfsc/op-rest-ofsccore-v1-activities-custom-actions-bulkupdate-post.html)
  can partially fail within a successful HTTP response.

`core.get_all_activities(...)` and `core.get_all_properties(...)` collect all
pages into typed `OFSResponseList` containers. Activity dates default to seven
days before/after the current day; provide both bounds for another range.
`activity_fields` and `additional_fields` control which fields are fetched.
Offsets advance by the number of returned items, and an empty page claiming
more data raises `OFSCApiError`. Both helpers hold the complete result in memory;
use `core.get_activities` or `metadata.get_properties` for individual pages.

## Usage Examples

**Sync:**
```python
from ofsc import OFSC
instance = OFSC(clientID="...", secret="...", companyName="...")
workzones = instance.metadata.get_workzones()
```

**Async:**
```python
from ofsc.async_client import AsyncOFSC
async with AsyncOFSC(clientID="...", secret="...", companyName="...") as client:
    workzones = await client.metadata.get_workzones()
```

See the [examples/](examples/) directory for comprehensive sync and async usage examples.

### Streaming Daily Extract Downloads

The synchronous client can download large extracts without buffering the full
successful response in memory. Use the response as a context manager and choose
the chunk size through Requests' `iter_content` method:

```python
with instance.core.get_daily_extract_file_stream(
    date="2026-01-15", filename="extract.zip", timeout=(5, 60)
) as stream:
    with open("extract.zip", "wb") as destination:
        for chunk in stream.iter_content(chunk_size=8192):
            if chunk:
                destination.write(chunk)
```

The response closes on context exit, including after an early break or an
exception. The optional timeout specifies connect and read timeouts; omitting
it preserves Requests' default. With the default `auto_raise=True`, OFSC JSON
errors raise `OFSAPIException`, and other unsuccessful responses raise
`requests.HTTPError`. If `auto_raise=False`, check `stream.status_code` before
writing the body. `get_daily_extract_file` continues to return buffered bytes.

## Test History

OFS REST API Version | PyOFSC
------------ | -------------
20C| 1.7
21A| 1.8, 1.8,1, 1.9
21D| 1.15
22B| 1.16, 1.17
22D| 1.18
24C| 2.0
25B| 2.12
26A| 2.24.0

## Future Deprecation Notice - OFSC 3.0

**Important**: Starting with Oracle Fusion Field Service 3.0, the synchronous client (`OFSC`) will be deprecated in favor of the async client (`AsyncOFSC`).

### Migration Path
- The async client (`AsyncOFSC`) is the recommended approach for all new development
- OFSC 3.0 will provide a **compatibility wrapper** to allow existing synchronous code to continue working without modifications
- The compatibility wrapper will internally use the async client with synchronous adapters
- We recommend gradually migrating to the async client to take advantage of better performance and scalability

### Timeline
- **OFSC 2.x**: Both sync and async clients fully supported
- **OFSC 3.0**: Sync client deprecated, compatibility wrapper provided
- **OFSC 4.0**: Sync client may be removed (compatibility wrapper will remain for at least one major version)
