sap-odata-connector-testcov 1.0.0
A powerful Python library for connecting to SAP OData services
License: MIT
Requires Python: >=3.8
Keywords: sap, odata, connector, api, rest, data, integration, etl

# SAP OData Connector

A powerful, production-ready Python library for connecting to SAP OData services with advanced features like automatic pagination, relationship expansion, filtering, and more.

## Features

- **Easy to Use**: Simple, intuitive API for querying SAP OData services
- **Automatic Pagination**: Handles server-side pagination automatically
- **Relationship Expansion**: Support for `$expand` to fetch related entities
- **Advanced Filtering**: Full OData query capabilities (`$filter`, `$select`, `$orderby`)
- **Async Support**: Built on asyncio for high performance
- **Clean Logging**: Professional logging without emojis, with detailed execution summaries
- **Automatic File Saving**: Query results automatically saved with unique filenames
- **Type Safety**: Full type hints and Pydantic models
- **BigQuery Integration**: Optional BigQuery storage support

## Installation

```bash
pip install sap-odata-connector-tescov
```

**Important**: The package name is `sap-odata-connector-tescov`, but you import it as `odc`:

```python
from odc.connector import SAPODataConnector  # ✅ Correct
# NOT: from sap-odata-connector-tescov import ...  ❌ Wrong
```

### Optional Dependencies

For BigQuery support:
```bash
pip install sap-odata-connector[bigquery]
```

For development:
```bash
pip install sap-odata-connector[dev]
```

## Quick Start

```python
import asyncio
from odc.connector import SAPODataConnector
from odc.config.models import ClientConfig

async def main():
    # Configure the connector
    config = ClientConfig(
        service_url="https://your-sap-server.com/sap/opu/odata/sap/SERVICE_NAME/",
        username="your_username",
        password="your_password",
        output_directory="./output"
    )
    
    # Create connector instance
    connector = SAPODataConnector(config)
    
    try:
        # Initialize
        await connector.initialize()
        
        # Fetch all products
        products = await connector.get_data(entity_name="Products")
        print(f"Retrieved {products['execution_stats']['records_processed']} products")
        
        # Fetch with filtering
        filtered = await connector.get_data(
            entity_name="Products",
            filter_condition="Price gt 100",
            select_fields="Id,Name,Price"
        )
        
        # Fetch with relationship expansion (JOIN)
        products_with_suppliers = await connector.get_data(
            entity_name="Products",
            expand_relations="Supplier"
        )
        
    finally:
        await connector.cleanup()

# Run
asyncio.run(main())
```

## Advanced Usage

### Filtering

```python
# Simple filter
result = await connector.get_data(
    entity_name="Orders",
    filter_condition="Status eq 'Completed'"
)

# Complex filter with date
from datetime import datetime, timedelta
six_months_ago = (datetime.now() - timedelta(days=180)).strftime('%Y-%m-%dT%H:%M:%S')
result = await connector.get_data(
    entity_name="Orders",
    filter_condition=f"OrderDate ge datetime'{six_months_ago}'",
    order_by="OrderDate desc"
)
```

### Relationship Expansion (Joins)

```python
# Expand single relationship
products_with_category = await connector.get_data(
    entity_name="Products",
    expand_relations="Category"
)

# Expand multiple relationships
orders_with_details = await connector.get_data(
    entity_name="Orders",
    expand_relations="Customer,OrderDetails"
)
```

### Field Selection

```python
# Select specific fields
result = await connector.get_data(
    entity_name="Products",
    select_fields="Id,Name,Price,Category"
)
```

### Ordering

```python
# Order by single field
result = await connector.get_data(
    entity_name="Products",
    order_by="Price desc"
)

# Order by multiple fields
result = await connector.get_data(
    entity_name="Products",
    order_by="Category asc, Price desc"
)
```

## Output Files

Query results are automatically saved to JSON files with unique names based on the query parameters:

- `20251006_144807_products.json` - Simple query
- `20251006_144807_products_expand_Supplier.json` - With expansion
- `20251006_144807_products_filter_Price_greater_100.json` - With filter
- `20251006_144807_products_select_5fields.json` - With field selection

## Configuration

### Basic Configuration

```python
config = ClientConfig(
    service_url="https://your-sap-server.com/sap/opu/odata/sap/SERVICE_NAME/",
    username="your_username",
    password="your_password",
    output_directory="./output"
)
```

### Advanced Configuration

```python
config = ClientConfig(
    service_url="https://your-sap-server.com/sap/opu/odata/sap/SERVICE_NAME/",
    username="your_username",
    password="your_password",
    output_directory="./output",
    use_https=True,
    max_workers=10,
    batch_size=100,
    total_records_limit=10000
)
```

## Logging

The connector provides clean, professional logging:

```
12:47:56 - odc.connector - INFO - START: Querying for Products with automatic pagination
12:47:56 - odc.connector - INFO - Paginated query completed: 2 records in 1.23s (1 requests)
12:47:56 - odc.connector - INFO - Detailed stats: Requests made: 1, Total records: 2, Duration: 1.23s
12:47:56 - odc.connector - INFO - Query results saved to: ./output/query_results/20251006_124756_products.json
```

## API Reference

### `SAPODataConnector.get_data()`

Main method for querying data.

**Parameters:**
- `entity_name` (str): Name of the entity to query
- `filter_condition` (str, optional): OData filter expression
- `select_fields` (str, optional): Comma-separated list of fields
- `expand_relations` (str, optional): Comma-separated list of relationships to expand
- `order_by` (str, optional): OData orderby expression
- `record_limit` (int, optional): Maximum number of records to fetch

**Returns:**
- Dictionary with `execution_stats` and `data`

## Requirements

- Python >= 3.8
- aiohttp >= 3.8.0
- orjson >= 3.9.0
- structlog >= 23.1.0
- pydantic >= 2.0.0

## License

MIT License - see LICENSE file for details

## Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

## Support

For issues and questions, please open an issue on GitHub.

## Author

Bhuvan Chandra Mothe

## Changelog

### Version 1.0.0 (2025-10-06)
- Initial release
- Core OData querying functionality
- Automatic pagination support
- Relationship expansion
- Advanced filtering and ordering
- Automatic file saving with unique names
- Clean professional logging