diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..2ad855c --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,61 @@ +name: Deploy Documentation + +on: + push: + branches: + - main + - staging + - develop + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: write + +jobs: + deploy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 # Fetch all history for mike versioning + + - name: Configure Git Credentials + run: | + git config user.name github-actions[bot] + git config user.email 41898282+github-actions[bot]@users.noreply.github.com + + - name: Set up Python + uses: actions/setup-python@v6 + with: + python-version: '3.x' + cache: 'pip' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[docs]" + + - name: Deploy documentation (main branch) + if: github.ref == 'refs/heads/main' + run: | + mike deploy --push --update-aliases latest stable + mike set-default --push latest + + - name: Deploy documentation (staging branch) + if: github.ref == 'refs/heads/staging' + run: | + mike deploy --push staging + + - name: Deploy documentation (develop branch) + if: github.ref == 'refs/heads/develop' + run: | + mike deploy --push dev + + - name: Deploy documentation (version tag) + if: startsWith(github.ref, 'refs/tags/v') + run: | + VERSION=${GITHUB_REF#refs/tags/v} + mike deploy --push --update-aliases $VERSION stable + mike set-default --push $VERSION diff --git a/docs/api-reference/client.md b/docs/api-reference/client.md new file mode 100644 index 0000000..f894f52 --- /dev/null +++ b/docs/api-reference/client.md @@ -0,0 +1,58 @@ +# Client API Reference + +Complete API reference for the `TMOClient` class. + +## TMOClient + +```python +class TMOClient: + def __init__( + self, + token: str, + database: str, + environment: Union[Environment, str] = Environment.US, + timeout: int = 30, + debug: bool = False + ) +``` + +### Parameters + +- **token** (`str`): Your API token from The Mortgage Office +- **database** (`str`): Your database name +- **environment** (`Union[Environment, str]`): API environment or custom URL (default: `Environment.US`) +- **timeout** (`int`): Request timeout in seconds (default: 30) +- **debug** (`bool`): Enable debug logging (default: False) + +### Shares Resource Attributes + +- **shares_pools**: `PoolsResource` - Shares pool operations +- **shares_partners**: `PartnersResource` - Shares partner operations +- **shares_distributions**: `DistributionsResource` - Shares distribution operations +- **shares_certificates**: `CertificatesResource` - Shares certificate operations +- **shares_history**: `HistoryResource` - Shares history operations + +### Capital Resource Attributes + +- **capital_pools**: `PoolsResource` - Capital pool operations +- **capital_partners**: `PartnersResource` - Capital partner operations +- **capital_distributions**: `DistributionsResource` - Capital distribution operations +- **capital_history**: `HistoryResource` - Capital history operations + +### Methods + +#### get(endpoint: str, params: Optional[Dict[str, Any]] = None) → Dict[str, Any] + +Make a GET request. + +#### post(endpoint: str, json: Optional[Dict[str, Any]] = None) → Dict[str, Any] + +Make a POST request. + +#### put(endpoint: str, json: Optional[Dict[str, Any]] = None) → Dict[str, Any] + +Make a PUT request. + +#### delete(endpoint: str) → Dict[str, Any] + +Make a DELETE request. diff --git a/docs/api-reference/exceptions.md b/docs/api-reference/exceptions.md new file mode 100644 index 0000000..5db0001 --- /dev/null +++ b/docs/api-reference/exceptions.md @@ -0,0 +1,123 @@ +# Exceptions API Reference + +## TMOException + +Base exception for all TMO API errors. + +```python +class TMOException(Exception): + def __init__( + self, + message: str, + error_number: Optional[int] = None + ) +``` + +**Attributes:** +- `message` (str): The error message +- `error_number` (Optional[int]): TMO API error number if available + +## AuthenticationError + +Raised for authentication failures (401/403). + +```python +class AuthenticationError(TMOException): + pass +``` + +**Usage:** +```python +from tmo_api import TMOClient, AuthenticationError + +try: + client = TMOClient(token="invalid", database="test") + pools = client.shares_pools.list_all() +except AuthenticationError as e: + print(f"Authentication failed: {e.message}") + if e.error_number: + print(f"Error number: {e.error_number}") +``` + +## APIError + +Raised when the API returns an error response. + +```python +class APIError(TMOException): + pass +``` + +**Usage:** +```python +from tmo_api import TMOClient, APIError + +try: + client = TMOClient(token="token", database="db") + pool = client.shares_pools.get_pool("INVALID") +except APIError as e: + print(f"API error: {e.message}") + if e.error_number: + print(f"Error number: {e.error_number}") +``` + +## ValidationError + +Raised for client-side validation errors before making API calls. + +```python +class ValidationError(TMOException): + pass +``` + +**Usage:** +```python +from tmo_api import TMOClient, ValidationError + +try: + client = TMOClient(token="token", database="db") + pool = client.shares_pools.get_pool("") # Empty account +except ValidationError as e: + print(f"Validation error: {e.message}") +``` + +## NetworkError + +Raised for network/connection errors (timeouts, connection failures). + +```python +class NetworkError(TMOException): + pass +``` + +**Usage:** +```python +from tmo_api import TMOClient, NetworkError + +try: + client = TMOClient(token="token", database="db", timeout=1) + pools = client.shares_pools.list_all() +except NetworkError as e: + print(f"Network error: {e.message}") +``` + +## Catching All Exceptions + +You can catch all TMO API exceptions using the base class: + +```python +import os +from tmo_api import TMOClient, TMOException + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +try: + pools = client.shares_pools.list_all() +except TMOException as e: + print(f"TMO API error: {e.message}") + if hasattr(e, 'error_number') and e.error_number: + print(f"Error number: {e.error_number}") +``` diff --git a/docs/api-reference/models.md b/docs/api-reference/models.md new file mode 100644 index 0000000..0634c95 --- /dev/null +++ b/docs/api-reference/models.md @@ -0,0 +1,73 @@ +# Models API Reference + +## BaseModel + +Base class for all data models. + +```python +class BaseModel: + rec_id: Optional[int] +``` + +## Pool + +Represents a mortgage pool. + +```python +class Pool(BaseModel): + Account: Optional[str] + Name: Optional[str] + InceptionDate: Optional[datetime] + LastEvaluation: Optional[datetime] + SysTimeStamp: Optional[datetime] + OtherAssets: List[OtherAsset] + OtherLiabilities: List[OtherLiability] +``` + +## OtherAsset + +Represents an asset in a pool. + +```python +class OtherAsset(BaseModel): + Description: Optional[str] + Value: Optional[float] + DateLastEvaluated: Optional[datetime] +``` + +## OtherLiability + +Represents a liability in a pool. + +```python +class OtherLiability(BaseModel): + Description: Optional[str] + Amount: Optional[float] + MaturityDate: Optional[datetime] + PaymentNextDue: Optional[datetime] +``` + +## Response Models + +### BaseResponse + +```python +class BaseResponse: + status: Optional[int] + message: Optional[str] + data: Any +``` + +### PoolResponse + +```python +class PoolResponse(BaseResponse): + pool: Optional[Pool] +``` + +### PoolsResponse + +```python +class PoolsResponse(BaseResponse): + pools: List[Pool] +``` diff --git a/docs/api-reference/resources.md b/docs/api-reference/resources.md new file mode 100644 index 0000000..7b82a56 --- /dev/null +++ b/docs/api-reference/resources.md @@ -0,0 +1,65 @@ +# Resources API Reference + +## PoolsResource + +```python +class PoolsResource: + def get_pool(self, account: str) -> Pool + def list_all(self) -> List[Pool] + def get_pool_partners(self, account: str) -> list + def get_pool_loans(self, account: str) -> list + def get_pool_bank_accounts(self, account: str) -> list + def get_pool_attachments(self, account: str) -> list +``` + +## PartnersResource + +```python +class PartnersResource: + def get_partner(self, account: str) -> dict + def get_partner_attachments(self, account: str) -> list + def list_all( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None + ) -> list +``` + +## DistributionsResource + +```python +class DistributionsResource: + def get_distribution(self, rec_id: str) -> dict + def list_all( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + pool_account: Optional[str] = None + ) -> list +``` + +## CertificatesResource + +```python +class CertificatesResource: + def get_certificates( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + partner_account: Optional[str] = None, + pool_account: Optional[str] = None + ) -> list +``` + +## HistoryResource + +```python +class HistoryResource: + def get_history( + self, + start_date: Optional[str] = None, + end_date: Optional[str] = None, + partner_account: Optional[str] = None, + pool_account: Optional[str] = None + ) -> list +``` diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 0000000..fc9067f --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,15 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +## [0.0.1] - 2024-11-06 + +### Added +- Initial release +- Support for Pools, Partners, Distributions, Certificates, and History resources +- Pool data models with date parsing +- Comprehensive test suite (92% coverage) +- Type hints with mypy support +- Multi-environment support (US, Canada, Australia) + +[0.0.1]: https://github.com/inntran/tmo-api-python/releases/tag/v0.0.1 diff --git a/docs/contributing/code-style.md b/docs/contributing/code-style.md new file mode 100644 index 0000000..2f3a165 --- /dev/null +++ b/docs/contributing/code-style.md @@ -0,0 +1,45 @@ +# Code Style + +## Formatting + +- Use **black** for code formatting (line length: 100) +- Use **isort** for import sorting +- Follow PEP 8 guidelines + +## Type Hints + +All functions should have type hints: + +```python +def get_pool(self, account: str) -> Pool: + pass +``` + +## Documentation + +Use Google-style docstrings: + +```python +def get_pool(self, account: str) -> Pool: + """Get pool details by account. + + Args: + account: The pool account identifier + + Returns: + Pool object with detailed information + + Raises: + ValidationError: If account is invalid + """ +``` + +## Commit Messages + +Follow conventional commits: + +- `feat:` New features +- `fix:` Bug fixes +- `docs:` Documentation changes +- `test:` Test additions/changes +- `refactor:` Code refactoring diff --git a/docs/contributing/development.md b/docs/contributing/development.md new file mode 100644 index 0000000..4f97c54 --- /dev/null +++ b/docs/contributing/development.md @@ -0,0 +1,52 @@ +# Development Setup + +## Prerequisites + +- Python 3.9 or higher +- Git +- pip + +## Setup + +Clone the repository: + +```bash +git clone https://github.com/inntran/tmo-api-python.git +cd tmo-api-python +``` + +Install development dependencies: + +```bash +pip install -e ".[dev]" +``` + +## Running Tests + +```bash +pytest tests/ -v +``` + +With coverage: + +```bash +pytest tests/ --cov=tmo_api --cov-report=term +``` + +## Code Quality + +Run all checks: + +```bash +# Format code +black src/tmo_api tests/ + +# Sort imports +isort src/tmo_api tests/ + +# Lint +flake8 src/tmo_api + +# Type check +mypy src/tmo_api +``` diff --git a/docs/contributing/testing.md b/docs/contributing/testing.md new file mode 100644 index 0000000..27ea2ec --- /dev/null +++ b/docs/contributing/testing.md @@ -0,0 +1,48 @@ +# Testing + +The SDK uses pytest for testing with 92% code coverage. + +## Running Tests + +```bash +# All tests +pytest + +# Specific test file +pytest tests/test_client.py + +# Specific test +pytest tests/test_client.py::TestClientInitialization::test_client_init_with_defaults + +# With verbose output +pytest -v + +# With coverage +pytest --cov=tmo_api --cov-report=term +``` + +## Writing Tests + +Tests use pytest fixtures and mocking: + +```python +import os +import pytest +from unittest.mock import patch, MagicMock +from tmo_api import TMOClient + +@pytest.fixture +def client(): + """Create a test client using environment variables.""" + return TMOClient( + token=os.environ.get("TMO_API_TOKEN", "test-token"), + database=os.environ.get("TMO_DATABASE", "test-db") + ) + +def test_example(client): + with patch('requests.Session.request') as mock_request: + mock_request.return_value.json.return_value = {"Status": 0, "Data": []} + mock_request.return_value.raise_for_status.return_value = None + result = client.shares_pools.list_all() + assert isinstance(result, list) +``` diff --git a/docs/getting-started/authentication.md b/docs/getting-started/authentication.md new file mode 100644 index 0000000..30b0555 --- /dev/null +++ b/docs/getting-started/authentication.md @@ -0,0 +1,234 @@ +# Authentication + +The TMO API uses token-based authentication with a database identifier. This guide explains how to obtain and use your credentials with the SDK. + +## Obtaining Credentials + +To use the TMO API, you need to obtain credentials from The Mortgage Office: + +1. Contact The Mortgage Office support +2. Request API access for your organization +3. Receive your API token and database name + +!!! warning "Keep Your Credentials Secure" + Your API token grants access to your TMO data. Never commit it to version control or share it publicly. + +## Using Credentials + +### Basic Authentication + +Pass your credentials when initializing the client: + +```python +from tmo_api import TMOClient + +client = TMOClient( + token="your-api-token", + database="your-database-name" +) +``` + +### Environment Variables + +For better security, store your credentials in environment variables: + +```bash +export TMO_API_TOKEN="your-api-token" +export TMO_DATABASE="your-database-name" +``` + +Then load them in your code: + +```python +import os +from tmo_api import TMOClient + +token = os.environ.get("TMO_API_TOKEN") +database = os.environ.get("TMO_DATABASE") + +client = TMOClient(token=token, database=database) +``` + +### Configuration File + +You can also store your configuration in a file: + +```python +import configparser +from tmo_api import TMOClient, Environment + +# Load configuration +config = configparser.ConfigParser() +config.read('config.ini') + +token = config['tmo']['token'] +database = config['tmo']['database'] +environment = config['tmo']['environment'] + +# Initialize client +client = TMOClient( + token=token, + database=database, + environment=Environment[environment.upper()] +) +``` + +Example `config.ini`: + +```ini +[tmo] +token = your-api-token +database = your-database-name +environment = US +``` + +!!! danger "Never Commit Secrets" + Add `config.ini` to your `.gitignore` to prevent accidentally committing your credentials. + +## Environments + +The TMO API has different endpoints for different regions: + +```python +from tmo_api import Environment + +# United States (default) +Environment.US + +# Canada +Environment.CANADA + +# Australia +Environment.AUSTRALIA +``` + +Specify the environment when creating the client: + +```python +from tmo_api import TMOClient, Environment + +client = TMOClient( + token="your-token", + database="your-database", + environment=Environment.CANADA +) +``` + +## Custom Base URL + +If you need to use a custom API endpoint (string instead of Environment enum): + +```python +client = TMOClient( + token="your-token", + database="your-database", + environment="https://custom.api.endpoint.com" +) +``` + +## Authentication Errors + +The SDK will raise an `AuthenticationError` if authentication fails: + +```python +from tmo_api import TMOClient, AuthenticationError + +try: + client = TMOClient(token="invalid-token", database="invalid-db") + pools = client.shares_pools.list_all() +except AuthenticationError as e: + print(f"Authentication failed: {e}") + if hasattr(e, 'error_number'): + print(f"Error number: {e.error_number}") +``` + +Common authentication errors: + +- **401 Unauthorized** - Invalid token or database name +- **403 Forbidden** - Token doesn't have required permissions + +## Best Practices + +### 1. Use Environment Variables + +```python +import os +from tmo_api import TMOClient + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) +``` + +### 2. Validate Credentials at Startup + +```python +def validate_credentials(): + try: + client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] + ) + # Make a simple API call to validate + client.shares_pools.list_all() + return True + except AuthenticationError: + return False + +if not validate_credentials(): + print("Error: Invalid credentials") + exit(1) +``` + +### 3. Use Separate Credentials for Environments + +```python +# Development +dev_client = TMOClient( + token=os.environ["TMO_DEV_TOKEN"], + database=os.environ["TMO_DEV_DATABASE"], + debug=True +) + +# Production +prod_client = TMOClient( + token=os.environ["TMO_PROD_TOKEN"], + database=os.environ["TMO_PROD_DATABASE"], + debug=False +) +``` + +## Troubleshooting + +### "Invalid Token" Error + +- Verify the token is correct +- Check that there are no extra spaces or newlines +- Ensure you're using the correct environment + +### "Invalid Database" Error + +- Verify the database name is correct +- Check for typos in the database name +- Contact TMO support to verify your database name + +### Environment Variable Not Found + +```python +import os +from tmo_api import TMOClient + +token = os.environ.get("TMO_API_TOKEN") +database = os.environ.get("TMO_DATABASE") + +if not token or not database: + raise ValueError("TMO_API_TOKEN and TMO_DATABASE environment variables must be set") + +client = TMOClient(token=token, database=database) +``` + +## Next Steps + +- [Client Configuration](../user-guide/client.md) - Advanced client options +- [Error Handling](../api-reference/exceptions.md) - Handle authentication errors diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md new file mode 100644 index 0000000..a30ab42 --- /dev/null +++ b/docs/getting-started/installation.md @@ -0,0 +1,79 @@ +# Installation + +This guide will help you install the TMO API Python SDK. + +## Requirements + +- Python 3.9 or higher +- pip (Python package installer) + +## Install from PyPI + +The recommended way to install the SDK is via pip: + +```bash +pip install tmo-api +``` + +## Install from Source + +If you want to install from source or contribute to the project: + +```bash +# Clone the repository +git clone https://github.com/inntran/tmo-api-python.git +cd tmo-api-python + +# Install in development mode +pip install -e ".[dev]" +``` + +## Verify Installation + +To verify that the SDK is installed correctly: + +```python +import tmo_api +print(tmo_api.__version__) +``` + +You should see the version number printed without any errors. + +## Optional Dependencies + +### Development Dependencies + +If you want to contribute to the project, install the development dependencies: + +```bash +pip install tmo-api[dev] +``` + +This includes: + +- pytest - Testing framework +- black - Code formatter +- flake8 - Linter +- isort - Import sorter +- mypy - Type checker + +### Documentation Dependencies + +To build the documentation locally: + +```bash +pip install tmo-api[docs] +``` + +This includes: + +- mkdocs - Documentation generator +- mkdocs-material - Material theme +- mike - Version management + +## Next Steps + +Now that you have the SDK installed, proceed to: + +- [Quick Start](quickstart.md) - Make your first API call +- [Authentication](authentication.md) - Learn about authentication diff --git a/docs/getting-started/quickstart.md b/docs/getting-started/quickstart.md new file mode 100644 index 0000000..32f4611 --- /dev/null +++ b/docs/getting-started/quickstart.md @@ -0,0 +1,192 @@ +# Quick Start + +This guide will help you make your first API call with the TMO API Python SDK. + +## Prerequisites + +Before you begin, make sure you have: + +1. Installed the SDK (see [Installation](installation.md)) +2. Obtained an API token and database name from The Mortgage Office +3. Know which environment to use (US, Canada, or Australia) + +## Basic Usage + +### Initialize the Client + +First, import and initialize the client using environment variables (recommended for CI/CD): + +```python +import os +from tmo_api import TMOClient, Environment + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + environment=Environment.US # or Environment.CANADA, Environment.AUSTRALIA +) +``` + +!!! tip "Environment Variables" + Using environment variables is recommended for production and CI/CD environments: + ```bash + export TMO_API_TOKEN="your-api-token" + export TMO_DATABASE="your-database-name" + ``` + +### Get a Pool + +Retrieve information about a specific mortgage pool: + +```python +# Get a shares pool by account number +pool = client.shares_pools.get_pool("POOL001") + +print(f"Pool Name: {pool.Name}") +print(f"Account: {pool.Account}") +print(f"Inception Date: {pool.InceptionDate}") +``` + +### List All Pools + +Get a list of all available pools: + +```python +# List all shares pools +pools = client.shares_pools.list_all() + +for pool in pools: + print(f"{pool.Account}: {pool.Name}") + +# For capital pools +capital_pools = client.capital_pools.list_all() +``` + +### Get Partner Information + +Retrieve partner account details: + +```python +# Get a partner by account number (shares) +partner = client.shares_partners.get_partner("PART001") + +print(f"Partner Name: {partner.get('Name')}") +print(f"Account: {partner.get('Account')}") +``` + +### Query Distributions + +Get distribution records with optional filtering: + +```python +# Get all distributions (shares) +distributions = client.shares_distributions.list_all() + +# Filter by date range +distributions = client.shares_distributions.list_all( + start_date="01/01/2024", + end_date="12/31/2024" +) + +# Filter by pool account +distributions = client.shares_distributions.list_all( + pool_account="POOL001" +) +``` + +## Pool Types + +The SDK supports both Shares and Capital pool types: + +```python +# Shares resources (most common) +client.shares_pools +client.shares_partners +client.shares_distributions +client.shares_certificates +client.shares_history + +# Capital resources +client.capital_pools +client.capital_partners +client.capital_distributions +client.capital_history +``` + +## Error Handling + +The SDK raises specific exceptions for different error types: + +```python +from tmo_api import TMOClient, AuthenticationError, APIError, ValidationError + +client = TMOClient(token="your-token", database="your-db") + +try: + pool = client.shares_pools.get_pool("POOL001") +except AuthenticationError as e: + print(f"Authentication failed: {e}") +except ValidationError as e: + print(f"Invalid input: {e}") +except APIError as e: + print(f"API error: {e}") +``` + +## Complete Example + +Here's a complete example that demonstrates various features: + +```python +import os +from tmo_api import TMOClient, Environment, TMOException + +def main(): + # Initialize the client with environment variables + client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + environment=Environment.US, + timeout=30, # Optional: custom timeout in seconds + debug=True # Optional: enable debug logging + ) + + try: + # Get all pools + print("Fetching all pools...") + pools = client.shares_pools.list_all() + print(f"Found {len(pools)} pools") + + # Get detailed information for the first pool + if pools: + first_pool = pools[0] + print(f"\nPool Details:") + print(f" Name: {first_pool.Name}") + print(f" Account: {first_pool.Account}") + + # Get partners for this pool + partners = client.shares_pools.get_pool_partners(first_pool.Account) + print(f" Partners: {len(partners)}") + + # Get distributions for this pool + distributions = client.shares_distributions.list_all( + pool_account=first_pool.Account + ) + print(f" Distributions: {len(distributions)}") + + except TMOException as e: + print(f"Error: {e}") + if hasattr(e, 'error_number'): + print(f"Error Number: {e.error_number}") + +if __name__ == "__main__": + main() +``` + +## Next Steps + +Now that you've made your first API call, explore more features: + +- [Authentication](authentication.md) - Learn about authentication +- [Client](../user-guide/client.md) - Client configuration options +- [Pools](../user-guide/pools.md) - Deep dive into pool operations +- [Error Handling](../api-reference/exceptions.md) - Comprehensive error handling guide diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..24a1b48 --- /dev/null +++ b/docs/index.md @@ -0,0 +1,76 @@ +# TMO API Python SDK + +Welcome to the **TMO API Python SDK** documentation. This SDK provides a clean, Pythonic interface for accessing [The Mortgage Office API](https://www.themortgageoffice.com/). + +## About This Project + +**TMO API Python SDK** is an independent, community-maintained wrapper for The Mortgage Office API. It provides a simple and intuitive way to interact with TMO's JSON-based web services. + +!!! warning "Independent Project" + This SDK is **not affiliated with or endorsed by Applied Business Software, Inc. (The Mortgage Office)**. + +## Features + +- 🚀 **Easy to use** - Simple, intuitive API design +- 🔒 **Type-safe** - Full type hints support with mypy +- 🌍 **Multi-region** - Support for US, Canada, and Australia environments +- 📦 **Comprehensive** - Complete coverage of TMO API endpoints +- ✅ **Well-tested** - 92% test coverage with 111+ tests +- 📚 **Well-documented** - Extensive documentation and examples + +## Supported Resources + +The SDK provides access to the following TMO API resources: + +- **Pools** - Access and manage mortgage pool information (Shares/Capital) +- **Partners** - Retrieve partner account details +- **Distributions** - Query distribution records +- **Certificates** - Access certificate information +- **History** - Retrieve account history + +## Quick Example + +```python +import os +from tmo_api import TMOClient, Environment + +# Initialize the client with environment variables +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + environment=Environment.US +) + +# Get a shares pool by account +pool = client.shares_pools.get_pool("POOL001") +print(f"Pool: {pool.Name}") + +# List all shares pools +pools = client.shares_pools.list_all() +print(f"Found {len(pools)} pools") +``` + +## Getting Started + +Ready to get started? Check out the following guides: + +- [Installation](getting-started/installation.md) - Install the SDK +- [Quick Start](getting-started/quickstart.md) - Your first API call +- [Authentication](getting-started/authentication.md) - How to authenticate + +## Support + +- 📖 [Documentation](https://inntran.github.io/tmo-api-python/) +- 🐛 [Issue Tracker](https://github.com/inntran/tmo-api-python/issues) +- 💻 [Source Code](https://github.com/inntran/tmo-api-python) + +## License + +This project is licensed under the **Apache License 2.0**. See the [LICENSE](https://github.com/inntran/tmo-api-python/blob/main/LICENSE) file for details. + +## Contact + +For sponsorship, commercial inquiries, or dedicated support: + +- 📧 Yinchuan Song - [songyinchuan@gmail.com](mailto:songyinchuan@gmail.com) +- 💼 GitHub - [https://github.com/inntran](https://github.com/inntran) diff --git a/docs/user-guide/certificates.md b/docs/user-guide/certificates.md new file mode 100644 index 0000000..ef73326 --- /dev/null +++ b/docs/user-guide/certificates.md @@ -0,0 +1,38 @@ +# Certificates + +The `CertificatesResource` provides methods for accessing certificate information. + +## Methods + +### get_certificates() + +Get certificates with optional filtering. + +```python +# All certificates +certificates = client.certificates.get_certificates() + +# Filter by date range +certificates = client.certificates.get_certificates( + start_date="01/01/2024", + end_date="12/31/2024" +) + +# Filter by partner account +certificates = client.certificates.get_certificates( + partner_account="PART001" +) + +# Filter by pool account +certificates = client.certificates.get_certificates( + pool_account="POOL001" +) + +# Combine filters +certificates = client.certificates.get_certificates( + start_date="01/01/2024", + end_date="12/31/2024", + partner_account="PART001", + pool_account="POOL001" +) +``` diff --git a/docs/user-guide/client.md b/docs/user-guide/client.md new file mode 100644 index 0000000..a1b5965 --- /dev/null +++ b/docs/user-guide/client.md @@ -0,0 +1,282 @@ +# Client + +The `TMOClient` class is the main entry point for interacting with The Mortgage Office API. It handles authentication, request management, and provides access to all resource endpoints. + +## Initialization + +### Basic Initialization (Recommended) + +Using environment variables is recommended for production and CI/CD: + +```python +import os +from tmo_api import TMOClient + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) +``` + +### With Environment + +```python +import os +from tmo_api import TMOClient, Environment + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + environment=Environment.US # US, CANADA, or AUSTRALIA +) +``` + +### With Custom Configuration + +```python +import os + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + environment="https://custom.endpoint.com", # Custom API endpoint + timeout=60, # Request timeout in seconds (default: 30) + debug=True # Enable debug logging (default: False) +) +``` + +## Configuration Options + +| Parameter | Type | Default | Description | +|-----------|------|---------|-------------| +| `token` | str | Required | Your TMO API token | +| `database` | str | Required | Your database name | +| `environment` | Environment \| str | `Environment.US` | API environment or custom URL | +| `timeout` | int | 30 | Request timeout in seconds | +| `debug` | bool | False | Enable debug logging | + +## Available Resources + +The client provides access to resources for both Shares and Capital pool types: + +### Shares Resources +```python +client.shares_pools # Pool operations +client.shares_partners # Partner operations +client.shares_distributions # Distribution operations +client.shares_certificates # Certificate operations +client.shares_history # History operations +``` + +### Capital Resources +```python +client.capital_pools # Pool operations +client.capital_partners # Partner operations +client.capital_distributions # Distribution operations +client.capital_history # History operations +``` + +## HTTP Methods + +The client provides low-level HTTP methods for direct API access: + +### GET Request + +```python +response = client.get("/LSS.svc/Shares/Pools") +``` + +### POST Request + +```python +data = {"field": "value"} +response = client.post("/LSS.svc/Shares/Pools", json=data) +``` + +### PUT Request + +```python +data = {"field": "updated_value"} +response = client.put("/LSS.svc/Shares/Pools/123", json=data) +``` + +### DELETE Request + +```python +response = client.delete("/LSS.svc/Shares/Pools/123") +``` + +## Debug Mode + +Enable debug mode to see detailed request/response information: + +```python +import os + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + debug=True +) + +# This will log: +# - Request method and URL +# - Request headers (with masked sensitive data) +# - Response status +# - Response body +pools = client.shares_pools.list_all() +``` + +## Error Handling + +The client automatically handles API errors and raises appropriate exceptions: + +```python +import os +from tmo_api import TMOClient, AuthenticationError, APIError, NetworkError + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +try: + pools = client.shares_pools.list_all() +except AuthenticationError as e: + # 401/403 errors + print(f"Authentication failed: {e}") +except NetworkError as e: + # Connection/timeout errors + print(f"Network error: {e}") +except APIError as e: + # Other API errors + print(f"API error: {e.message}") +``` + +## Session Management + +The client uses a persistent `requests.Session` for better performance: + +```python +import os + +# Session is automatically created and reused +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +# Make multiple requests efficiently +pools = client.shares_pools.list_all() +partners = client.shares_partners.list_all() +# Session is reused across requests +``` + +## Best Practices + +### 1. Reuse Client Instances + +```python +import os + +# Good: Create once, use many times +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) +pools = client.shares_pools.list_all() +partners = client.shares_partners.list_all() + +# Bad: Creating new client for each request +pools = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +).shares_pools.list_all() +partners = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +).shares_partners.list_all() +``` + +### 2. Use Environment Variables + +```python +import os + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) +``` + +### 3. Set Appropriate Timeouts + +```python +import os + +# For long-running operations +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + timeout=120 # 2 minutes +) + +# For quick operations +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"], + timeout=10 # 10 seconds +) +``` + +### 4. Handle Errors Gracefully + +```python +import os +from tmo_api import TMOClient, TMOException + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +try: + pools = client.shares_pools.list_all() +except TMOException as e: + # Log error and handle gracefully + logger.error(f"TMO API error: {e}") + pools = [] # Return empty list as fallback +``` + +## Thread Safety + +The client uses `requests.Session` which is not thread-safe. Create separate client instances for each thread: + +```python +import os +import threading + +def fetch_pools(): + # Create separate client for this thread + client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] + ) + return client.shares_pools.list_all() + +# Create threads with separate clients +threads = [] +for i in range(5): + t = threading.Thread(target=fetch_pools) + threads.append(t) + t.start() + +for t in threads: + t.join() +``` + +## Next Steps + +- [Pools](pools.md) - Working with mortgage pools +- [Partners](partners.md) - Managing partners +- [Distributions](distributions.md) - Querying distributions diff --git a/docs/user-guide/distributions.md b/docs/user-guide/distributions.md new file mode 100644 index 0000000..7980e9a --- /dev/null +++ b/docs/user-guide/distributions.md @@ -0,0 +1,79 @@ +# Distributions + +The `DistributionsResource` provides methods for querying distribution records. + +## Overview + +The SDK provides separate distribution resources for Shares and Capital pool types: + +```python +import os +from tmo_api import TMOClient + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +# Access shares distributions resource +shares_distributions = client.shares_distributions + +# Access capital distributions resource +capital_distributions = client.capital_distributions +``` + +## Methods + +### get_distribution() + +Get a specific distribution by record ID. + +**Parameters:** +- `rec_id` (str, required): The distribution record identifier + +**Returns:** `Dict[str, Any]` - Distribution data dictionary + +**Example:** +```python +distribution = client.shares_distributions.get_distribution("123") +print(f"Distribution ID: {distribution.get('rec_id')}") +print(f"Amount: {distribution.get('Amount')}") +``` + +### list_all() + +List all distributions with optional filtering. + +**Parameters:** +- `start_date` (str, optional): Start date in MM/DD/YYYY format +- `end_date` (str, optional): End date in MM/DD/YYYY format +- `pool_account` (str, optional): Filter by specific pool account + +**Returns:** `List[Any]` - List of distribution data dictionaries + +**Example:** +```python +# All distributions +distributions = client.shares_distributions.list_all() + +for dist in distributions: + print(f"Distribution: {dist.get('rec_id')} - Amount: {dist.get('Amount')}") + +# Filter by date range +distributions = client.shares_distributions.list_all( + start_date="01/01/2024", + end_date="12/31/2024" +) + +# Filter by pool account +distributions = client.shares_distributions.list_all( + pool_account="POOL001" +) + +# Combine filters +distributions = client.shares_distributions.list_all( + start_date="01/01/2024", + end_date="12/31/2024", + pool_account="POOL001" +) +``` diff --git a/docs/user-guide/history.md b/docs/user-guide/history.md new file mode 100644 index 0000000..2293274 --- /dev/null +++ b/docs/user-guide/history.md @@ -0,0 +1,38 @@ +# History + +The `HistoryResource` provides methods for retrieving account history. + +## Methods + +### get_history() + +Get account history with optional filtering. + +```python +# All history +history = client.history.get_history() + +# Filter by date range +history = client.history.get_history( + start_date="01/01/2024", + end_date="12/31/2024" +) + +# Filter by partner account +history = client.history.get_history( + partner_account="PART001" +) + +# Filter by pool account +history = client.history.get_history( + pool_account="POOL001" +) + +# Combine filters +history = client.history.get_history( + start_date="01/01/2024", + end_date="12/31/2024", + partner_account="PART001", + pool_account="POOL001" +) +``` diff --git a/docs/user-guide/partners.md b/docs/user-guide/partners.md new file mode 100644 index 0000000..95c39db --- /dev/null +++ b/docs/user-guide/partners.md @@ -0,0 +1,83 @@ +# Partners + +The `PartnersResource` provides methods for accessing partner account information. + +## Overview + +The SDK provides separate partner resources for Shares and Capital pool types: + +```python +import os +from tmo_api import TMOClient + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +# Access shares partners resource +shares_partners = client.shares_partners + +# Access capital partners resource +capital_partners = client.capital_partners +``` + +## Methods + +### get_partner() + +Get detailed information about a specific partner. Returns a dictionary containing partner data. + +**Parameters:** +- `account` (str, required): The partner account identifier + +**Returns:** `Dict[str, Any]` - Partner data dictionary + +**Example:** +```python +partner = client.shares_partners.get_partner("PART001") +print(f"Partner: {partner.get('Name')}") +print(f"Account: {partner.get('Account')}") +``` + +### get_partner_attachments() + +Get attachments for a partner. + +**Parameters:** +- `account` (str, required): The partner account identifier + +**Returns:** `List[Any]` - List of partner attachments + +**Example:** +```python +attachments = client.shares_partners.get_partner_attachments("PART001") + +for attachment in attachments: + print(f"File: {attachment.get('FileName')}") +``` + +### list_all() + +List all partners with optional date filtering. + +**Parameters:** +- `start_date` (str, optional): Start date in MM/DD/YYYY format +- `end_date` (str, optional): End date in MM/DD/YYYY format + +**Returns:** `List[Any]` - List of partner data dictionaries + +**Example:** +```python +# All partners +partners = client.shares_partners.list_all() + +for partner in partners: + print(f"{partner.get('Account')}: {partner.get('Name')}") + +# Filter by date range +partners = client.shares_partners.list_all( + start_date="01/01/2024", + end_date="12/31/2024" +) +``` diff --git a/docs/user-guide/pools.md b/docs/user-guide/pools.md new file mode 100644 index 0000000..ae8bf09 --- /dev/null +++ b/docs/user-guide/pools.md @@ -0,0 +1,265 @@ +# Pools + +The `PoolsResource` provides methods for accessing and managing mortgage pool information. + +## Overview + +Pools represent mortgage pools in The Mortgage Office system. The SDK supports both Shares and Capital pool types. + +## Initialization + +The pools resource is automatically initialized when you create a client: + +```python +import os +from tmo_api import TMOClient + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +# Access shares pools resource +shares_pools = client.shares_pools + +# Access capital pools resource +capital_pools = client.capital_pools +``` + +## Pool Types + +```python +from tmo_api.resources.pools import PoolType + +# Shares pools (default) +PoolType.SHARES + +# Capital pools +PoolType.CAPITAL +``` + +## Methods + +### get_pool() + +Get detailed information about a specific pool. + +**Parameters:** +- `account` (str, required): The pool account identifier + +**Returns:** `Pool` object + +**Example:** +```python +pool = client.shares_pools.get_pool("POOL001") + +print(f"Pool Name: {pool.Name}") +print(f"Account: {pool.Account}") +print(f"Inception Date: {pool.InceptionDate}") + +# Access nested objects +if pool.OtherAssets: + for asset in pool.OtherAssets: + print(f"Asset: {asset.Description}, Value: {asset.Value}") +``` + +### list_all() + +Get a list of all available pools. + +**Returns:** `List[Pool]` + +**Example:** +```python +pools = client.shares_pools.list_all() + +for pool in pools: + print(f"{pool.Account}: {pool.Name}") +``` + +### get_pool_partners() + +Get partners associated with a pool. + +**Parameters:** +- `account` (str, required): The pool account identifier + +**Returns:** `list` of partner data + +**Example:** +```python +partners = client.shares_pools.get_pool_partners("POOL001") + +for partner in partners: + print(f"Partner: {partner.get('Name')}") +``` + +### get_pool_loans() + +Get loans associated with a pool. + +**Parameters:** +- `account` (str, required): The pool account identifier + +**Returns:** `list` of loan data + +**Example:** +```python +loans = client.shares_pools.get_pool_loans("POOL001") + +for loan in loans: + print(f"Loan: {loan.get('LoanNumber')}") +``` + +### get_pool_bank_accounts() + +Get bank accounts associated with a pool. + +**Parameters:** +- `account` (str, required): The pool account identifier + +**Returns:** `list` of bank account data + +**Example:** +```python +bank_accounts = client.shares_pools.get_pool_bank_accounts("POOL001") + +for account in bank_accounts: + print(f"Bank: {account.get('BankName')}") +``` + +### get_pool_attachments() + +Get attachments associated with a pool. + +**Parameters:** +- `account` (str, required): The pool account identifier + +**Returns:** `list` of attachment data + +**Example:** +```python +attachments = client.shares_pools.get_pool_attachments("POOL001") + +for attachment in attachments: + print(f"File: {attachment.get('FileName')}") +``` + +## Pool Model + +The `Pool` model represents a mortgage pool with the following key attributes: + +```python +pool = client.shares_pools.get_pool("POOL001") + +# Basic information +pool.rec_id # Record ID +pool.Account # Account number +pool.Name # Pool name + +# Date fields (automatically parsed to datetime) +pool.InceptionDate # datetime object +pool.LastEvaluation # datetime object +pool.SysTimeStamp # datetime object + +# Nested objects +pool.OtherAssets # List[OtherAsset] +pool.OtherLiabilities # List[OtherLiability] + +# Access all fields dynamically +for key, value in pool.__dict__.items(): + print(f"{key}: {value}") +``` + +## Error Handling + +```python +import os +from tmo_api import TMOClient, ValidationError, APIError + +client = TMOClient( + token=os.environ["TMO_API_TOKEN"], + database=os.environ["TMO_DATABASE"] +) + +try: + pool = client.shares_pools.get_pool("INVALID") +except ValidationError as e: + print(f"Invalid input: {e}") +except APIError as e: + print(f"API error: {e}") +``` + +## Common Use Cases + +### 1. List All Pools with Details + +```python +pools = client.shares_pools.list_all() + +for pool in pools: + print(f"\nPool: {pool.Account}") + print(f" Name: {pool.Name}") + if pool.InceptionDate: + print(f" Inception: {pool.InceptionDate.strftime('%Y-%m-%d')}") +``` + +### 2. Get Complete Pool Information + +```python +account = "POOL001" + +# Get basic pool info +pool = client.shares_pools.get_pool(account) + +# Get related data +partners = client.shares_pools.get_pool_partners(account) +loans = client.shares_pools.get_pool_loans(account) +bank_accounts = client.shares_pools.get_pool_bank_accounts(account) + +print(f"Pool: {pool.Name}") +print(f"Partners: {len(partners)}") +print(f"Loans: {len(loans)}") +print(f"Bank Accounts: {len(bank_accounts)}") +``` + +### 3. Filter Pools by Criteria + +```python +pools = client.shares_pools.list_all() + +# Filter by inception date +from datetime import datetime + +recent_pools = [ + p for p in pools + if p.InceptionDate and p.InceptionDate.year >= 2024 +] + +print(f"Found {len(recent_pools)} pools from 2024") +``` + +### 4. Export Pool Data + +```python +import csv + +pools = client.shares_pools.list_all() + +with open('pools.csv', 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow(['Account', 'Name', 'Inception Date']) + + for pool in pools: + writer.writerow([ + pool.Account, + pool.Name, + pool.InceptionDate.strftime('%Y-%m-%d') if pool.InceptionDate else '' + ]) +``` + +## Next Steps + +- [Partners](partners.md) - Working with partners +- [Distributions](distributions.md) - Querying distributions +- [Models API Reference](../api-reference/models.md) - Pool model details diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 0000000..992aabf --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,96 @@ +site_name: TMO API Python SDK +site_description: Python SDK for The Mortgage Office API +site_author: Yinchuan Song +site_url: https://inntran.github.io/tmo-api-python/ + +repo_name: inntran/tmo-api-python +repo_url: https://github.com/inntran/tmo-api-python + +theme: + name: material + palette: + # Palette toggle for light mode + - media: "(prefers-color-scheme: light)" + scheme: default + primary: indigo + accent: indigo + toggle: + icon: material/brightness-7 + name: Switch to dark mode + # Palette toggle for dark mode + - media: "(prefers-color-scheme: dark)" + scheme: slate + primary: indigo + accent: indigo + toggle: + icon: material/brightness-4 + name: Switch to light mode + features: + - navigation.instant + - navigation.tracking + - navigation.tabs + - navigation.sections + - navigation.expand + - navigation.top + - search.suggest + - search.highlight + - content.code.copy + - content.code.annotate + +markdown_extensions: + - pymdownx.highlight: + anchor_linenums: true + line_spans: __span + pygments_lang_class: true + - pymdownx.inlinehilite + - pymdownx.snippets + - pymdownx.superfences + - pymdownx.tabbed: + alternate_style: true + - admonition + - pymdownx.details + - attr_list + - md_in_html + - tables + - toc: + permalink: true + +plugins: + - search + - mike: + version_selector: true + css_dir: css + javascript_dir: js + canonical_version: latest + +extra: + version: + provider: mike + default: latest + social: + - icon: fontawesome/brands/github + link: https://github.com/inntran/tmo-api-python + +nav: + - Home: index.md + - Getting Started: + - Installation: getting-started/installation.md + - Quick Start: getting-started/quickstart.md + - Authentication: getting-started/authentication.md + - User Guide: + - Client: user-guide/client.md + - Pools: user-guide/pools.md + - Partners: user-guide/partners.md + - Distributions: user-guide/distributions.md + - Certificates: user-guide/certificates.md + - History: user-guide/history.md + - API Reference: + - Client: api-reference/client.md + - Models: api-reference/models.md + - Resources: api-reference/resources.md + - Exceptions: api-reference/exceptions.md + - Contributing: + - Development Setup: contributing/development.md + - Testing: contributing/testing.md + - Code Style: contributing/code-style.md + - Changelog: changelog.md diff --git a/pyproject.toml b/pyproject.toml index 063ee85..97b6bf9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -44,6 +44,11 @@ dev = [ "mypy>=1.0.0", "types-requests>=2.31.0", ] +docs = [ + "mkdocs>=1.6.0", + "mkdocs-material>=9.6.0", + "mike>=2.1.0", +] [build-system] requires = ["hatchling"]