odecloud-python 0.2.0
Official Python SDK for the OdeCloud API
Requires Python: >=3.8
Keywords: api, consulting, erp, odecloud, sdk, time-tracking

# OdeCloud Python SDK

Official Python client for the [OdeCloud API](https://docs.odecloud.com).

## Installation

```bash
pip install odecloud-python
```

## Quick Start

```python
from odecloud import OdeCloud

# Initialize the client
client = OdeCloud(api_key="ode_live_your_api_key")

# List your time entries
entries = client.time_entries.list()
for entry in entries:
    print(f"{entry.task_title}: {entry.total_hours}h")

# Get your profile
profile = client.profile.get()
print(f"Hello, {profile.full_name}!")
```

## Async Support

```python
import asyncio
from odecloud import AsyncOdeCloud

async def main():
    async with AsyncOdeCloud(api_key="ode_live_your_api_key") as client:
        entries = await client.time_entries.list()
        for entry in entries:
            print(f"{entry.task_title}: {entry.total_hours}h")

asyncio.run(main())
```

## Resources

### Time Entries

```python
# List time entries with filters
entries = client.time_entries.list(
    page=1,
    page_size=20,
    project_id="project-123",
    billable=True,
    start_date="2024-01-01",
    end_date="2024-01-31"
)

# Auto-paginate through all entries
for entry in client.time_entries.list_all():
    print(entry.id)

# Get a specific entry
entry = client.time_entries.get("entry-123")

# Create a time entry
entry = client.time_entries.create(
    task_id="task-123",
    times=[{"startedAt": 1704067200000, "endedAt": 1704070800000}],
    billable=True,
    notes="Worked on feature X"
)

# Update a time entry
entry = client.time_entries.update(
    "entry-123",
    notes="Updated notes",
    billable=False
)

# Delete a time entry
client.time_entries.delete("entry-123")

# Get time summary
summary = client.time_entries.summary(
    start_date="2024-01-01",
    end_date="2024-01-31",
    project_id="project-123"
)
print(f"Total: {summary.total_time_hours}h")
print(f"Billable: {summary.billable_time_hours}h")
```

### Projects

```python
# List projects
projects = client.projects.list(search="ERP")

# Auto-paginate through all projects
for project in client.projects.list_all():
    print(f"{project.title}: {project.total_time_hours}h")

# Get project details with members
project = client.projects.get("project-123")
for member in project.members:
    print(f"  - {member.full_name}")

# List tasks for a project
tasks = client.projects.list_tasks("project-123", status="in_progress")
for task in tasks:
    print(f"  {task.title}: {task.current_time_hours}h")
```

### Profile

```python
# Get your profile
profile = client.profile.get()
print(f"Name: {profile.full_name}")
print(f"Email: {profile.email}")
print(f"Tagline: {profile.tagline}")

# Update your profile
updated = client.profile.update(
    tagline="Senior ERP Consultant | NetSuite Expert",
    location="San Francisco, CA",
    social_links={
        "linkedin": "https://linkedin.com/in/yourname",
        "github": "https://github.com/yourname"
    }
)
```

## Error Handling

```python
from odecloud import OdeCloud, AuthenticationError, RateLimitError, NotFoundError

client = OdeCloud(api_key="ode_live_your_api_key")

try:
    entry = client.time_entries.get("invalid-id")
except NotFoundError:
    print("Entry not found")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited. Retry after {e.retry_after} seconds")
```

## Configuration

```python
client = OdeCloud(
    api_key="ode_live_your_api_key",
    base_url="https://server.odecloud.app/api/v1/public",  # Optional
    timeout=30.0,  # Request timeout in seconds
    max_retries=3,  # Max retries for rate-limited requests
)
```

## API Scopes

Your API key needs the appropriate scopes to access different resources:

| Resource | Required Scope |
|----------|----------------|
| `time_entries.list()`, `time_entries.get()`, `time_entries.summary()` | `time:read` |
| `time_entries.create()`, `time_entries.update()`, `time_entries.delete()` | `time:write` |
| `projects.list()`, `projects.get()`, `projects.list_tasks()` | `projects:read` |
| `profile.get()` | `profile:read` |
| `profile.update()` | `profile:write` |

## Publishing to PyPI

### Prerequisites

1. Create a PyPI account at https://pypi.org/account/register/
2. Generate an API token at https://pypi.org/manage/account/#api-tokens
3. Install build tools:

```bash
pip install build twine
```

### Build and Publish

```bash
# Navigate to the SDK directory
cd odecloud-python

# Build the package
python -m build

# Upload to PyPI (you'll be prompted for your API token)
python -m twine upload dist/*
```

### Using API Token

You can configure twine to use your API token:

```bash
# Option 1: Environment variable
export TWINE_USERNAME=__token__
export TWINE_PASSWORD=pypi-your-api-token

# Option 2: .pypirc file (~/.pypirc)
cat > ~/.pypirc << EOF
[pypi]
username = __token__
password = pypi-your-api-token
EOF
```

### Test on TestPyPI First (Recommended)

```bash
# Upload to TestPyPI
python -m twine upload --repository testpypi dist/*

# Install from TestPyPI to verify
pip install --index-url https://test.pypi.org/simple/ odecloud-python
```

### Version Management

Update the version in `pyproject.toml` before each release:

```toml
[project]
version = "1.0.1"  # Increment this
```

### GitHub Actions (Optional)

Add `.github/workflows/publish.yml` for automated publishing:

```yaml
name: Publish to PyPI

on:
  release:
    types: [published]

jobs:
  publish:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v5
        with:
          python-version: '3.11'
      - name: Install dependencies
        run: pip install build twine
      - name: Build package
        run: python -m build
      - name: Publish to PyPI
        env:
          TWINE_USERNAME: __token__
          TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
        run: python -m twine upload dist/*
```

## License

MIT