From 5ec3b00b04c04375505deac5534d81a64964acdc Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Sun, 26 Oct 2025 16:22:02 +0100 Subject: [PATCH 1/6] fix: Add missing CLI optional dependencies and entry point Add the missing [cli] optional dependencies (click>=8.0.0, rich>=13.0.0) and [project.scripts] entry point configuration to pyproject.toml. This fixes the inconsistency between the documented CLI installation method in ADVANCED-USEAGE.MD and the actual package configuration. The CLI module was already implemented but could not be properly installed using 'pip install "nocodb-simple-client[cli]"' as documented, because the required dependencies and console script entry point were missing from the package configuration. Changes: - Add [project.optional-dependencies.cli] with click and rich - Add [project.scripts] with nocodb entry point - Enables proper installation: pip install "nocodb-simple-client[cli]" --- pyproject.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 44ead1d..643bc81 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -85,6 +85,11 @@ dev = [ "docker>=6.0.0", "pillow>=10.0.0", ] +cli = [ + # Command-line interface dependencies + "click>=8.0.0", + "rich>=13.0.0", +] docs = [ "mkdocs>=1.4.0", "mkdocs-material>=8.5.0", @@ -92,6 +97,9 @@ docs = [ "mkdocstrings[python]>=0.19.0", ] +[project.scripts] +nocodb = "nocodb_simple_client.cli:main" + [project.urls] Homepage = "https://github.com/bauer-group/LIB-NocoDB_SimpleClient" Documentation = "https://github.com/bauer-group/LIB-NocoDB_SimpleClient#readme" From aeb68dfcd169c1ace7e135dbb88c13d0bc1231f8 Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Sun, 26 Oct 2025 16:37:06 +0100 Subject: [PATCH 2/6] fix: Reorganize optional dependencies and add tomllib support Reorganize optional dependencies into proper runtime feature groups and fix TOML loading to use stdlib tomllib for Python 3.11+. Changes to pyproject.toml: - Add [async] optional dependencies (aiohttp, aiofiles) - Add [config] optional dependencies (PyYAML, tomli) - Add [dotenv] optional dependencies (python-dotenv) - Reorganize [cli] to runtime section - Remove runtime features from [dev] dependencies - Group dependencies by purpose (runtime vs development) Changes to config.py: - Update TOML loading to use stdlib tomllib for Python 3.11+ - Fall back to tomli for Python < 3.11 - Improve error message to indicate stdlib availability These changes fix several issues: 1. Config file support (YAML/TOML) now properly declared as optional 2. Async client dependencies separated from dev dependencies 3. Users can install only needed features (e.g., pip install ".[async]") 4. Better Python 3.11+ support using stdlib tomllib 5. Clearer separation between runtime and development dependencies --- pyproject.toml | 31 +++++++++++++++++++++--------- src/nocodb_simple_client/config.py | 13 ++++++++++--- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 643bc81..c443673 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,28 @@ dependencies = [ ] [project.optional-dependencies] +# Runtime optional features +cli = [ + # Command-line interface dependencies + "click>=8.0.0", + "rich>=13.0.0", +] +async = [ + # Async client dependencies + "aiohttp>=3.8.0", + "aiofiles>=0.8.0", +] +config = [ + # Configuration file format support (YAML/TOML) + "PyYAML>=6.0.0", + "tomli>=2.0.0; python_version<'3.11'", +] +dotenv = [ + # .env file support for configuration + "python-dotenv>=1.0.0", +] + +# Development dependencies dev = [ # Testing "pytest>=7.0.0", @@ -73,23 +95,14 @@ dev = [ # Development Tools "pre-commit>=2.20.0", "tox>=4.0.0", - "tomli>=2.0.0; python_version<'3.11'", "types-requests>=2.31.0", "types-PyYAML>=6.0.0", "types-aiofiles>=0.8.0", - "python-dotenv>=1.0.0", - "aiohttp>=3.8.0", - "aiofiles>=0.8.0", # Integration Testing "docker>=6.0.0", "pillow>=10.0.0", ] -cli = [ - # Command-line interface dependencies - "click>=8.0.0", - "rich>=13.0.0", -] docs = [ "mkdocs>=1.4.0", "mkdocs-material>=8.5.0", diff --git a/src/nocodb_simple_client/config.py b/src/nocodb_simple_client/config.py index cafc56f..f742e83 100644 --- a/src/nocodb_simple_client/config.py +++ b/src/nocodb_simple_client/config.py @@ -140,12 +140,19 @@ def from_file(cls, config_path: Path) -> "NocoDBConfig": raise ValueError("PyYAML is required to load YAML configuration files") from e elif suffix == ".toml": try: - import tomli + # Use tomllib (stdlib) for Python 3.11+, otherwise tomli + try: + import tomllib + except ImportError: + import tomli as tomllib # type: ignore[no-redef] with open(config_path, "rb") as f: - data = tomli.load(f) + data = tomllib.load(f) except ImportError as e: - raise ValueError("tomli is required to load TOML configuration files") from e + raise ValueError( + "tomli is required to load TOML configuration files " + "(pre-installed in Python 3.11+)" + ) from e else: raise ValueError(f"Unsupported configuration file format: {suffix}") From c362d36b45edfdc241b5b0606b9be03f3f536810 Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Sun, 26 Oct 2025 17:00:16 +0100 Subject: [PATCH 3/6] docs: Add documentation for optional dependencies installation Update documentation to include all new optional dependency groups introduced in the previous commits. This ensures users know how to install the exact features they need. Changes to README.template.MD: - Add "Optional Features" section with installation examples - Document all available extras: cli, async, config, dotenv, dev, docs - Include descriptions of what each extra provides - Show how to combine multiple extras Changes to ADVANCED-USEAGE.MD: - Add installation instructions for async support - Add installation instructions for config file support - Document both YAML/TOML support and .env support - Use consistent formatting for dependency sections This documentation matches the package structure changes made in previous commits where runtime features were properly separated into optional dependency groups. --- docs/ADVANCED-USEAGE.MD | 19 ++++++++++++++++++- docs/README.template.MD | 35 +++++++++++++++++++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/docs/ADVANCED-USEAGE.MD b/docs/ADVANCED-USEAGE.MD index 31cfc75..41c007b 100644 --- a/docs/ADVANCED-USEAGE.MD +++ b/docs/ADVANCED-USEAGE.MD @@ -44,13 +44,23 @@ Optional environment variables: ### File-based Configuration +**Required Dependencies:** + +```bash +# Install with configuration file support (YAML/TOML) +pip install "nocodb-simple-client[config]" + +# For .env file support +pip install "nocodb-simple-client[dotenv]" +``` + Load configuration from JSON, YAML, or TOML files: ```python from pathlib import Path from nocodb_simple_client.config import load_config -# Load from file +# Load from file (supports .json, .yaml, .yml, .toml) config = load_config(config_path=Path("config.yaml")) ``` @@ -93,6 +103,13 @@ config.setup_logging() ## Async Support +**Required Dependencies:** + +```bash +# Install with async dependencies +pip install "nocodb-simple-client[async]" +``` + For high-performance applications, use the async client: ```python diff --git a/docs/README.template.MD b/docs/README.template.MD index a4aac1e..7be7089 100644 --- a/docs/README.template.MD +++ b/docs/README.template.MD @@ -46,6 +46,8 @@ A simple and powerful Python client for interacting with [NocoDB](https://nocodb ### Installation +#### Basic Installation + Install from PyPI using pip: ```bash @@ -65,6 +67,39 @@ pip install git+{{REPO_URL}}.git@v{{VERSION}} pip install git+{{REPO_URL}}.git@main ``` +#### Optional Features + +Install with optional features based on your needs: + +```bash +# Command-line interface support +pip install "nocodb-simple-client[cli]" + +# Async client support +pip install "nocodb-simple-client[async]" + +# Configuration file support (YAML/TOML) +pip install "nocodb-simple-client[config]" + +# .env file support +pip install "nocodb-simple-client[dotenv]" + +# Multiple features +pip install "nocodb-simple-client[cli,async,config]" + +# All features for development +pip install "nocodb-simple-client[dev]" +``` + +**Available extras:** + +- `cli` - Command-line interface with rich output (requires `click`, `rich`) +- `async` - Async client support (requires `aiohttp`, `aiofiles`) +- `config` - YAML/TOML configuration file support (requires `PyYAML`, `tomli` for Python <3.11) +- `dotenv` - .env file support for configuration (requires `python-dotenv`) +- `dev` - All development dependencies (testing, linting, etc.) +- `docs` - Documentation generation dependencies + ### Basic Usage ```python From 43ce8cd704337322261e61c580557a12ed49919e Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Sun, 26 Oct 2025 17:06:28 +0100 Subject: [PATCH 4/6] fix: Add python-dotenv back to dev dependencies Re-add python-dotenv to [dev] dependencies as it is required by the test fixtures in conftest.py. While dotenv is optional for end users (via [dotenv] extra), it is mandatory for running tests. The test suite uses python-dotenv in conftest.py to load .env files for test configuration, making it a development dependency rather than just a runtime optional feature. Fixes test failure: ImportError while loading conftest.py tests/conftest.py:12: from dotenv import load_dotenv This ensures CI/CD tests can run successfully while keeping dotenv optional for library users who don't need .env file support. --- pyproject.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/pyproject.toml b/pyproject.toml index c443673..0da0f7b 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -98,6 +98,7 @@ dev = [ "types-requests>=2.31.0", "types-PyYAML>=6.0.0", "types-aiofiles>=0.8.0", + "python-dotenv>=1.0.0", # Integration Testing "docker>=6.0.0", From 582ab4322b96488df3cfce907aaba74ec115b023 Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Sun, 26 Oct 2025 17:25:27 +0100 Subject: [PATCH 5/6] ci: Add optional dependencies installation validation Add a new CI job to validate that all optional dependency groups can be installed correctly and their imports work as expected. Changes: - Add validate-extras job with matrix testing across Python 3.11-3.13 - Test all optional extras: cli, async, config, dotenv, dev - Verify packages install correctly - Verify imports work after installation - Test tomllib (stdlib) vs tomli for Python version compatibility Job execution order: 1. unit-tests (fast, no external dependencies) 2. validate-extras (validates all install options) 3. integration-test (needs NocoDB instance) 4. performance-test (optional, triggered by label) This ensures that users can reliably install any combination of optional features without import errors or missing dependencies. Matrix testing covers: - 3 Python versions (3.11, 3.12, 3.13) - 5 optional extras (cli, async, config, dotenv, dev) - Total: 15 validation scenarios per workflow run --- .github/workflows/feature-test.yml | 67 +++++++++++++++++++++++++++++- 1 file changed, 66 insertions(+), 1 deletion(-) diff --git a/.github/workflows/feature-test.yml b/.github/workflows/feature-test.yml index 9c75d31..8e26d18 100644 --- a/.github/workflows/feature-test.yml +++ b/.github/workflows/feature-test.yml @@ -48,11 +48,76 @@ jobs: env: PYTHONPATH: ${{ github.workspace }}/src + # ๐Ÿ“ฆ Validate optional dependencies installation + validate-extras: + name: ๐Ÿ“ฆ Validate Optional Dependencies + runs-on: ubuntu-latest + needs: unit-tests + strategy: + matrix: + python-version: ["3.11", "3.12", "3.13"] + extra: ["cli", "async", "config", "dotenv", "dev"] + + steps: + - name: ๐Ÿ“ฅ Checkout code + uses: actions/checkout@v4 + + - name: ๐Ÿ Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + with: + python-version: ${{ matrix.python-version }} + + - name: ๐Ÿ“ฆ Install base package + run: | + python -m pip install --upgrade pip + pip install -e . + + - name: ๐Ÿ“ฆ Install optional extra [${{ matrix.extra }}] + run: | + pip install -e ".[${{ matrix.extra }}]" + + - name: โœ… Verify installation + run: | + echo "=== Installed packages ===" + pip list | grep -E "(nocodb|click|rich|aiohttp|aiofiles|pyyaml|tomli|python-dotenv)" || true + + echo "=== Testing imports ===" + python -c "from nocodb_simple_client import NocoDBClient; print('โœ“ Base client imports')" + + # Test extra-specific imports + case "${{ matrix.extra }}" in + cli) + python -c "import click; import rich; print('โœ“ CLI dependencies available')" + python -c "from nocodb_simple_client.cli import main; print('โœ“ CLI module imports')" + ;; + async) + python -c "import aiohttp; import aiofiles; print('โœ“ Async dependencies available')" + python -c "from nocodb_simple_client.async_client import AsyncNocoDBClient; print('โœ“ Async client imports')" + ;; + config) + python -c "import yaml; print('โœ“ PyYAML available')" + if [ "${{ matrix.python-version }}" != "3.11" ] && [ "${{ matrix.python-version }}" != "3.12" ] && [ "${{ matrix.python-version }}" != "3.13" ]; then + python -c "import tomli; print('โœ“ tomli available')" + else + python -c "import tomllib; print('โœ“ tomllib available (stdlib)')" + fi + python -c "from nocodb_simple_client.config import NocoDBConfig; from pathlib import Path; print('โœ“ Config module imports')" + ;; + dotenv) + python -c "import dotenv; print('โœ“ python-dotenv available')" + ;; + dev) + python -c "import pytest; import ruff; import mypy; print('โœ“ Dev dependencies available')" + ;; + esac + + echo "โœ… Extra '${{ matrix.extra }}' validated successfully!" + # ๐Ÿ”— Integration tests with Python-managed NocoDB instance integration-test: name: ๐Ÿ”— Integration Tests (Python-Managed) runs-on: ubuntu-latest - needs: unit-tests + needs: validate-extras steps: - name: ๐Ÿ“ฅ Checkout code From 43608fa846f8e101f6b1e7a6686b92b75a01d9ef Mon Sep 17 00:00:00 2001 From: Karl Bauer Date: Wed, 28 Jan 2026 20:54:33 +0100 Subject: [PATCH 6/6] docs: Add examples for async client, config file handling, and dotenv usage --- docs/README.template.MD | 12 +- examples/README.md | 75 ++++++ examples/async_example.py | 274 +++++++++++++++++++++ examples/config_file_example.py | 402 +++++++++++++++++++++++++++++++ examples/dotenv_example.py | 414 ++++++++++++++++++++++++++++++++ 5 files changed, 1176 insertions(+), 1 deletion(-) create mode 100644 examples/async_example.py create mode 100644 examples/config_file_example.py create mode 100644 examples/dotenv_example.py diff --git a/docs/README.template.MD b/docs/README.template.MD index 7be7089..aa89f4f 100644 --- a/docs/README.template.MD +++ b/docs/README.template.MD @@ -1038,7 +1038,9 @@ except Exception as e: ## ๐Ÿงช Examples -Check out the [`examples/`](examples/) directory for comprehensive examples: +Check out the [`examples/`](examples/) directory for comprehensive examples. See the [Examples README](examples/README.md) for detailed documentation. + +### Core Examples - **[Basic Usage](examples/basic_usage.py)**: CRUD operations and fundamentals - **[API Version Support](examples/api_version_example.py)**: Using v2 and v3 APIs with automatic conversion @@ -1052,6 +1054,14 @@ Check out the [`examples/`](examples/) directory for comprehensive examples: - **[Link Management](examples/link_examples.py)**: Managing relationships between tables - **[Configuration](examples/config_examples.py)**: Different configuration methods +### Optional Dependencies Examples + +These examples demonstrate the optional dependency groups: + +- **[Async Client](examples/async_example.py)**: High-performance concurrent operations (`[async]`) +- **[Config Files](examples/config_file_example.py)**: YAML/TOML configuration loading (`[config]`) +- **[Environment Files](examples/dotenv_example.py)**: Secure .env file management (`[dotenv]`) + ## ๐Ÿ“‹ Requirements - Python 3.8 or higher diff --git a/examples/README.md b/examples/README.md index a066a15..3459ce9 100644 --- a/examples/README.md +++ b/examples/README.md @@ -61,6 +61,81 @@ This directory contains comprehensive examples demonstrating how to use the Noco **Best for**: Production applications requiring robust error handling +--- + +## Optional Dependencies Examples + +These examples demonstrate how to use the optional dependency groups. Each group provides additional functionality that can be installed separately based on your needs. + +### 5. Async Client (`async_example.py`) + +**Installation**: `pip install "nocodb-simple-client[async]"` + +**Purpose**: High-performance concurrent operations using async/await + +**What you'll learn**: + +- Setting up and using `AsyncNocoDBClient` +- Basic async CRUD operations with context managers +- Parallel bulk operations with automatic concurrency limiting +- Custom semaphore-based concurrency control +- Async error handling patterns +- Running multiple queries in parallel with `asyncio.gather()` + +**Best for**: High-throughput applications, batch processing, real-time data pipelines + +**Dependencies installed**: `aiohttp`, `aiofiles` + +### 6. Configuration Files (`config_file_example.py`) + +**Installation**: `pip install "nocodb-simple-client[config]"` + +**Purpose**: Load configuration from YAML and TOML files + +**What you'll learn**: + +- Loading configuration from YAML files +- Loading configuration from TOML files +- JSON configuration (no extra dependencies required) +- Managing environment-specific configurations (dev/staging/prod) +- Graceful fallback patterns when dependencies are missing +- Python 3.11+ `tomllib` vs `tomli` for older versions + +**Best for**: Applications with complex configuration needs, multi-environment deployments + +**Dependencies installed**: `PyYAML`, `tomli` (Python < 3.11 only) + +### 7. Environment Files (`dotenv_example.py`) + +**Installation**: `pip install "nocodb-simple-client[dotenv]"` + +**Purpose**: Secure secrets management with .env files + +**What you'll learn**: + +- Basic `.env` file loading with `load_dotenv()` +- Managing multiple environment files (`.env.development`, `.env.production`) +- Secrets management best practices and gitignore patterns +- Reading `.env` without modifying `os.environ` using `dotenv_values()` +- Combining `.env` files (secrets) with config files (settings) +- Docker and cloud platform compatibility + +**Best for**: 12-factor apps, Docker deployments, secure credential management + +**Dependencies installed**: `python-dotenv` + +### Quick Reference: Optional Extras + +| Extra | Command | Use Case | +| ---------- | ----------------------------------------------------------- | --------------------------- | +| `[async]` | `pip install "nocodb-simple-client[async]"` | Async/concurrent operations | +| `[config]` | `pip install "nocodb-simple-client[config]"` | YAML/TOML config files | +| `[dotenv]` | `pip install "nocodb-simple-client[dotenv]"` | .env file support | +| `[cli]` | `pip install "nocodb-simple-client[cli]"` | Command-line interface | +| Combined | `pip install "nocodb-simple-client[async,config,dotenv]"` | Multiple features | + +--- + ## Configuration Before running the examples, you'll need to configure: diff --git a/examples/async_example.py b/examples/async_example.py new file mode 100644 index 0000000..aa5124d --- /dev/null +++ b/examples/async_example.py @@ -0,0 +1,274 @@ +""" +Async client examples for NocoDB Simple Client. + +This example demonstrates how to use the async client for high-performance +concurrent operations with NocoDB. + +INSTALLATION: + pip install "nocodb-simple-client[async]" + +This installs the required dependencies: + - aiohttp: Async HTTP client + - aiofiles: Async file operations +""" + +import asyncio +import importlib.util +import sys + +# ============================================================================= +# DEPENDENCY CHECK +# ============================================================================= +# Check if async dependencies are available before importing + +ASYNC_AVAILABLE = ( + importlib.util.find_spec("aiohttp") is not None + and importlib.util.find_spec("aiofiles") is not None +) + +if not ASYNC_AVAILABLE: + print("=" * 60) + print("ERROR: Async dependencies not installed!") + print("=" * 60) + print() + print("To use the async client, install the [async] extra:") + print() + print(' pip install "nocodb-simple-client[async]"') + print() + print("This will install: aiohttp, aiofiles") + print("=" * 60) + sys.exit(1) + +# Now we can safely import the async client +from nocodb_simple_client import NocoDBConfig # noqa: E402 +from nocodb_simple_client.async_client import ( # noqa: E402 + AsyncNocoDBClient, + AsyncNocoDBTable, +) +from nocodb_simple_client.exceptions import ( # noqa: E402 + AuthenticationException, + NocoDBException, + RateLimitException, + RecordNotFoundException, +) + +# ============================================================================= +# CONFIGURATION +# ============================================================================= +NOCODB_BASE_URL = "https://your-nocodb-instance.com" +API_TOKEN = "your-api-token-here" +TABLE_ID = "your-table-id-here" + + +# ============================================================================= +# EXAMPLE 1: Basic Async Operations +# ============================================================================= +async def example_basic_operations(): + """Demonstrate basic async CRUD operations.""" + print("\n" + "=" * 60) + print("Example 1: Basic Async Operations") + print("=" * 60) + + config = NocoDBConfig( + base_url=NOCODB_BASE_URL, + api_token=API_TOKEN, + timeout=30.0, + ) + + # Use async context manager for automatic session management + async with AsyncNocoDBClient(config) as client: + table = AsyncNocoDBTable(client, TABLE_ID) + + # Insert a record + record_id = await table.insert_record( + { + "Name": "Async User", + "Email": "async@example.com", + } + ) + print(f"Inserted record: {record_id}") + + # Get the record + record = await table.get_record(record_id) + print(f"Retrieved record: {record}") + + # Update the record + await table.update_record({"Id": record_id, "Name": "Updated Async User"}) + print(f"Updated record: {record_id}") + + # Delete the record + await table.delete_record(record_id) + print(f"Deleted record: {record_id}") + + print("Basic async operations completed!") + + +# ============================================================================= +# EXAMPLE 2: Parallel Bulk Operations +# ============================================================================= +async def example_bulk_operations(): + """Demonstrate high-performance bulk operations with concurrency control.""" + print("\n" + "=" * 60) + print("Example 2: Parallel Bulk Operations") + print("=" * 60) + + config = NocoDBConfig( + base_url=NOCODB_BASE_URL, + api_token=API_TOKEN, + pool_connections=20, # Increase for bulk operations + pool_maxsize=50, + ) + + async with AsyncNocoDBClient(config) as client: + table = AsyncNocoDBTable(client, TABLE_ID) + + # Prepare records for bulk insert + records = [{"Name": f"User {i}", "Email": f"user{i}@example.com"} for i in range(100)] + + # Bulk insert with automatic concurrency limiting (10 concurrent requests) + print(f"Inserting {len(records)} records in parallel...") + inserted_ids = await table.bulk_insert_records(records) + print(f"Inserted {len(inserted_ids)} records") + + # Bulk update + updates = [{"Id": id, "Name": f"Updated User {i}"} for i, id in enumerate(inserted_ids)] + print(f"Updating {len(updates)} records in parallel...") + await table.bulk_update_records(updates) + print("Bulk update completed") + + print("Bulk operations completed!") + + +# ============================================================================= +# EXAMPLE 3: Custom Concurrency Control +# ============================================================================= +async def example_custom_concurrency(): + """Demonstrate custom concurrency control with semaphores.""" + print("\n" + "=" * 60) + print("Example 3: Custom Concurrency Control") + print("=" * 60) + + config = NocoDBConfig( + base_url=NOCODB_BASE_URL, + api_token=API_TOKEN, + ) + + async with AsyncNocoDBClient(config) as client: + table = AsyncNocoDBTable(client, TABLE_ID) + + # Custom concurrency limit + max_concurrent = 5 + semaphore = asyncio.Semaphore(max_concurrent) + + async def process_with_limit(record_data: dict) -> int | str: + """Insert a record with concurrency limiting.""" + async with semaphore: + return await table.insert_record(record_data) + + # Create tasks + records = [{"Name": f"Concurrent {i}"} for i in range(20)] + tasks = [process_with_limit(record) for record in records] + + # Execute with controlled concurrency + print(f"Processing {len(records)} records with max {max_concurrent} concurrent...") + results = await asyncio.gather(*tasks) + print(f"Processed {len(results)} records") + + print("Custom concurrency example completed!") + + +# ============================================================================= +# EXAMPLE 4: Error Handling in Async Context +# ============================================================================= +async def example_error_handling(): + """Demonstrate proper error handling in async operations.""" + print("\n" + "=" * 60) + print("Example 4: Async Error Handling") + print("=" * 60) + + config = NocoDBConfig( + base_url=NOCODB_BASE_URL, + api_token=API_TOKEN, + ) + + async with AsyncNocoDBClient(config) as client: + table = AsyncNocoDBTable(client, TABLE_ID) + + try: + # Try to get a non-existent record + _record = await table.get_record(999999) # noqa: F841 + except RecordNotFoundException: + print("Record not found (expected)") + except AuthenticationException: + print("Authentication failed - check your API token") + except RateLimitException as e: + print(f"Rate limited - retry after {e.retry_after} seconds") + except NocoDBException as e: + print(f"NocoDB error: {e.message}") + + print("Error handling example completed!") + + +# ============================================================================= +# EXAMPLE 5: Parallel Queries +# ============================================================================= +async def example_parallel_queries(): + """Demonstrate running multiple queries in parallel.""" + print("\n" + "=" * 60) + print("Example 5: Parallel Queries") + print("=" * 60) + + config = NocoDBConfig( + base_url=NOCODB_BASE_URL, + api_token=API_TOKEN, + ) + + async with AsyncNocoDBClient(config) as client: + table = AsyncNocoDBTable(client, TABLE_ID) + + # Run multiple queries in parallel + results = await asyncio.gather( + table.get_records(limit=10, sort="Name"), + table.get_records(limit=10, sort="-CreatedAt"), + table.count_records(), + table.count_records(where="(Status,eq,Active)"), + ) + + sorted_by_name, recent_records, total_count, active_count = results + + print(f"Records sorted by name: {len(sorted_by_name)}") + print(f"Recent records: {len(recent_records)}") + print(f"Total count: {total_count}") + print(f"Active count: {active_count}") + + print("Parallel queries completed!") + + +# ============================================================================= +# MAIN ENTRY POINT +# ============================================================================= +async def main(): + """Run all async examples.""" + print("\n" + "=" * 60) + print("NOCODB SIMPLE CLIENT - ASYNC EXAMPLES") + print("=" * 60) + print() + print("These examples demonstrate the [async] optional dependency group.") + print() + print("Installation: pip install 'nocodb-simple-client[async]'") + print() + + # Uncomment the examples you want to run: + # await example_basic_operations() + # await example_bulk_operations() + # await example_custom_concurrency() + # await example_error_handling() + # await example_parallel_queries() + + print("\n" + "=" * 60) + print("Uncomment the examples in main() to run them.") + print("=" * 60) + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/examples/config_file_example.py b/examples/config_file_example.py new file mode 100644 index 0000000..3daec3b --- /dev/null +++ b/examples/config_file_example.py @@ -0,0 +1,402 @@ +""" +Configuration file examples for NocoDB Simple Client. + +This example demonstrates how to load configuration from YAML and TOML files +using the [config] optional dependency group. + +INSTALLATION: + pip install "nocodb-simple-client[config]" + +This installs the required dependencies: + - PyYAML: YAML file support + - tomli: TOML file support (Python < 3.11 only; Python 3.11+ uses stdlib tomllib) +""" + +import importlib.util +import sys +from pathlib import Path +from tempfile import TemporaryDirectory + +from nocodb_simple_client.config import NocoDBConfig + +# ============================================================================= +# DEPENDENCY CHECK +# ============================================================================= + +# Check for YAML support (PyYAML) +YAML_AVAILABLE = importlib.util.find_spec("yaml") is not None + +# Check for TOML support (stdlib tomllib for 3.11+ or tomli for older versions) +TOML_AVAILABLE = ( + importlib.util.find_spec("tomllib") is not None or importlib.util.find_spec("tomli") is not None +) + +if not YAML_AVAILABLE and not TOML_AVAILABLE: + print("=" * 60) + print("ERROR: Config file dependencies not installed!") + print("=" * 60) + print() + print("To use YAML/TOML configuration files, install the [config] extra:") + print() + print(' pip install "nocodb-simple-client[config]"') + print() + print("This will install: PyYAML, tomli (for Python < 3.11)") + print() + print("Note: Python 3.11+ includes tomllib in the standard library.") + print("=" * 60) + sys.exit(1) + + +# ============================================================================= +# EXAMPLE 1: YAML Configuration +# ============================================================================= +def example_yaml_config(): + """Demonstrate loading configuration from a YAML file.""" + print("\n" + "=" * 60) + print("Example 1: YAML Configuration") + print("=" * 60) + + if not YAML_AVAILABLE: + print("YAML support not available. Install PyYAML:") + print(' pip install "nocodb-simple-client[config]"') + return + + # Create a sample YAML configuration file + yaml_content = """ +# NocoDB Configuration +# Save this as: nocodb-config.yaml + +base_url: "https://your-nocodb-instance.com" +api_token: "your-api-token-here" + +# Connection settings +timeout: 60.0 +max_retries: 3 +backoff_factor: 0.5 + +# Connection pooling +pool_connections: 20 +pool_maxsize: 50 + +# Security +verify_ssl: true + +# Debugging +debug: false +log_level: "INFO" + +# Custom headers (optional) +extra_headers: + X-Request-Source: "my-application" + X-Client-Version: "1.0.0" +""" + + with TemporaryDirectory() as tmpdir: + config_path = Path(tmpdir) / "nocodb-config.yaml" + config_path.write_text(yaml_content) + + print(f"Created sample config: {config_path}") + print() + print("YAML Content:") + print("-" * 40) + print(yaml_content.strip()) + print("-" * 40) + print() + + # Load configuration from YAML + config = NocoDBConfig.from_file(config_path) + + print("Loaded configuration:") + print(f" Base URL: {config.base_url}") + print(f" Timeout: {config.timeout}s") + print(f" Max Retries: {config.max_retries}") + print(f" Pool Size: {config.pool_connections}/{config.pool_maxsize}") + print(f" SSL Verify: {config.verify_ssl}") + print(f" Debug: {config.debug}") + + print("\nYAML configuration example completed!") + + +# ============================================================================= +# EXAMPLE 2: TOML Configuration +# ============================================================================= +def example_toml_config(): + """Demonstrate loading configuration from a TOML file.""" + print("\n" + "=" * 60) + print("Example 2: TOML Configuration") + print("=" * 60) + + if not TOML_AVAILABLE: + print("TOML support not available.") + print("For Python < 3.11, install tomli:") + print(' pip install "nocodb-simple-client[config]"') + print() + print("Python 3.11+ includes tomllib in the standard library.") + return + + # Detect which TOML library is being used + if importlib.util.find_spec("tomllib") is not None: + toml_source = "tomllib (stdlib, Python 3.11+)" + else: + toml_source = "tomli (third-party)" + + print(f"Using: {toml_source}") + print() + + # Create a sample TOML configuration file + toml_content = """ +# NocoDB Configuration +# Save this as: nocodb-config.toml + +base_url = "https://your-nocodb-instance.com" +api_token = "your-api-token-here" + +# Connection settings +timeout = 60.0 +max_retries = 3 +backoff_factor = 0.5 + +# Connection pooling +pool_connections = 20 +pool_maxsize = 50 + +# Security +verify_ssl = true + +# Debugging +debug = false +log_level = "INFO" + +# Custom headers (optional) +[extra_headers] +X-Request-Source = "my-application" +X-Client-Version = "1.0.0" +""" + + with TemporaryDirectory() as tmpdir: + config_path = Path(tmpdir) / "nocodb-config.toml" + config_path.write_text(toml_content) + + print(f"Created sample config: {config_path}") + print() + print("TOML Content:") + print("-" * 40) + print(toml_content.strip()) + print("-" * 40) + print() + + # Load configuration from TOML + config = NocoDBConfig.from_file(config_path) + + print("Loaded configuration:") + print(f" Base URL: {config.base_url}") + print(f" Timeout: {config.timeout}s") + print(f" Max Retries: {config.max_retries}") + print(f" Pool Size: {config.pool_connections}/{config.pool_maxsize}") + + print("\nTOML configuration example completed!") + + +# ============================================================================= +# EXAMPLE 3: JSON Configuration (Built-in, no extra dependencies) +# ============================================================================= +def example_json_config(): + """Demonstrate loading configuration from a JSON file (no extra dependencies).""" + print("\n" + "=" * 60) + print("Example 3: JSON Configuration (Built-in)") + print("=" * 60) + + # JSON is always available (stdlib) + json_content = """{ + "base_url": "https://your-nocodb-instance.com", + "api_token": "your-api-token-here", + "timeout": 60.0, + "max_retries": 3, + "backoff_factor": 0.5, + "pool_connections": 20, + "pool_maxsize": 50, + "verify_ssl": true, + "debug": false, + "log_level": "INFO", + "extra_headers": { + "X-Request-Source": "my-application" + } +}""" + + with TemporaryDirectory() as tmpdir: + config_path = Path(tmpdir) / "nocodb-config.json" + config_path.write_text(json_content) + + print("Note: JSON support requires NO extra dependencies!") + print() + print("JSON Content:") + print("-" * 40) + print(json_content) + print("-" * 40) + print() + + # Load configuration from JSON + config = NocoDBConfig.from_file(config_path) + + print("Loaded configuration:") + print(f" Base URL: {config.base_url}") + print(f" Timeout: {config.timeout}s") + + print("\nJSON configuration example completed!") + + +# ============================================================================= +# EXAMPLE 4: Environment-Specific Configurations +# ============================================================================= +def example_environment_configs(): + """Demonstrate managing multiple environment configurations.""" + print("\n" + "=" * 60) + print("Example 4: Environment-Specific Configurations") + print("=" * 60) + + if not YAML_AVAILABLE: + print("YAML support required for this example.") + return + + # Development configuration + dev_config = """ +base_url: "http://localhost:8080" +api_token: "dev-token" +timeout: 10.0 +max_retries: 1 +verify_ssl: false +debug: true +log_level: "DEBUG" +""" + + # Staging configuration + staging_config = """ +base_url: "https://staging.nocodb.example.com" +api_token: "staging-token" +timeout: 30.0 +max_retries: 3 +verify_ssl: true +debug: false +log_level: "INFO" +""" + + # Production configuration + prod_config = """ +base_url: "https://nocodb.example.com" +api_token: "prod-token" +timeout: 120.0 +max_retries: 5 +backoff_factor: 1.0 +pool_connections: 50 +pool_maxsize: 100 +verify_ssl: true +debug: false +log_level: "WARNING" +""" + + with TemporaryDirectory() as tmpdir: + configs = { + "development": dev_config, + "staging": staging_config, + "production": prod_config, + } + + for env_name, content in configs.items(): + config_path = Path(tmpdir) / f"config.{env_name}.yaml" + config_path.write_text(content) + + config = NocoDBConfig.from_file(config_path) + print(f"\n{env_name.upper()}:") + print(f" URL: {config.base_url}") + print(f" Timeout: {config.timeout}s") + print(f" Debug: {config.debug}") + print(f" Log Level: {config.log_level}") + + print("\nEnvironment configurations example completed!") + + +# ============================================================================= +# EXAMPLE 5: Graceful Fallback Pattern +# ============================================================================= +def example_graceful_fallback(): + """Demonstrate graceful fallback when config dependencies are missing.""" + print("\n" + "=" * 60) + print("Example 5: Graceful Fallback Pattern") + print("=" * 60) + + print( + """ +This pattern allows your application to work with or without +the [config] extra installed: + + from pathlib import Path + from nocodb_simple_client.config import NocoDBConfig + + def load_configuration(config_path: Path | None = None): + '''Load config with graceful fallback.''' + + # Try file-based configuration first + if config_path and config_path.exists(): + suffix = config_path.suffix.lower() + + if suffix == '.json': + # JSON always works (stdlib) + return NocoDBConfig.from_file(config_path) + + elif suffix in ['.yaml', '.yml']: + try: + return NocoDBConfig.from_file(config_path) + except ValueError as e: + if 'PyYAML' in str(e): + print("YAML not available, falling back to env") + else: + raise + + elif suffix == '.toml': + try: + return NocoDBConfig.from_file(config_path) + except ValueError as e: + if 'tomli' in str(e): + print("TOML not available, falling back to env") + else: + raise + + # Fallback to environment variables + return NocoDBConfig.from_env() +""" + ) + + print("\nGraceful fallback pattern demonstrated!") + + +# ============================================================================= +# MAIN ENTRY POINT +# ============================================================================= +def main(): + """Run all configuration file examples.""" + print("\n" + "=" * 60) + print("NOCODB SIMPLE CLIENT - CONFIG FILE EXAMPLES") + print("=" * 60) + print() + print("These examples demonstrate the [config] optional dependency group.") + print() + print("Installation: pip install 'nocodb-simple-client[config]'") + print() + print("Dependency status:") + print(f" YAML support (PyYAML): {'Available' if YAML_AVAILABLE else 'Not installed'}") + print(f" TOML support: {'Available' if TOML_AVAILABLE else 'Not installed'}") + + # Run examples + example_json_config() # Always works (no extra deps) + example_yaml_config() # Requires PyYAML + example_toml_config() # Requires tomli (< 3.11) or stdlib tomllib (3.11+) + example_environment_configs() + example_graceful_fallback() + + print("\n" + "=" * 60) + print("All configuration file examples completed!") + print("=" * 60) + + +if __name__ == "__main__": + main() diff --git a/examples/dotenv_example.py b/examples/dotenv_example.py new file mode 100644 index 0000000..1721a09 --- /dev/null +++ b/examples/dotenv_example.py @@ -0,0 +1,414 @@ +""" +Environment file (.env) examples for NocoDB Simple Client. + +This example demonstrates how to use .env files for configuration +using the [dotenv] optional dependency group. + +INSTALLATION: + pip install "nocodb-simple-client[dotenv]" + +This installs the required dependency: + - python-dotenv: .env file support + +WHY USE .env FILES? + - Keep secrets out of source code + - Easy environment switching (dev/staging/prod) + - Standard practice for 12-factor apps + - Works with Docker and cloud platforms +""" + +import importlib.util +import os +import sys +from pathlib import Path +from tempfile import TemporaryDirectory + +from nocodb_simple_client.config import NocoDBConfig + +# ============================================================================= +# DEPENDENCY CHECK +# ============================================================================= + +DOTENV_AVAILABLE = importlib.util.find_spec("dotenv") is not None + +if not DOTENV_AVAILABLE: + print("=" * 60) + print("ERROR: python-dotenv not installed!") + print("=" * 60) + print() + print("To use .env file support, install the [dotenv] extra:") + print() + print(' pip install "nocodb-simple-client[dotenv]"') + print() + print("This will install: python-dotenv") + print("=" * 60) + sys.exit(1) + +# Import after check +from dotenv import dotenv_values, load_dotenv # noqa: E402 + + +# ============================================================================= +# EXAMPLE 1: Basic .env File Usage +# ============================================================================= +def example_basic_dotenv(): + """Demonstrate basic .env file loading.""" + print("\n" + "=" * 60) + print("Example 1: Basic .env File Usage") + print("=" * 60) + + # Sample .env file content + env_content = """ +# NocoDB Configuration +# Save this as: .env + +NOCODB_BASE_URL=https://your-nocodb-instance.com +NOCODB_API_TOKEN=your-api-token-here +NOCODB_TIMEOUT=60.0 +NOCODB_MAX_RETRIES=3 +NOCODB_DEBUG=false +""" + + with TemporaryDirectory() as tmpdir: + env_path = Path(tmpdir) / ".env" + env_path.write_text(env_content) + + print(".env Content:") + print("-" * 40) + print(env_content.strip()) + print("-" * 40) + print() + + # Load .env file into environment + load_dotenv(env_path) + + # Now NocoDBConfig.from_env() can read these values + config = NocoDBConfig.from_env() + + print("Loaded configuration:") + print(f" Base URL: {config.base_url}") + print(f" Timeout: {config.timeout}s") + print(f" Max Retries: {config.max_retries}") + print(f" Debug: {config.debug}") + + print("\nBasic .env example completed!") + + +# ============================================================================= +# EXAMPLE 2: Multiple Environment Files +# ============================================================================= +def example_multiple_environments(): + """Demonstrate using different .env files for different environments.""" + print("\n" + "=" * 60) + print("Example 2: Multiple Environment Files") + print("=" * 60) + + # Development environment + dev_env = """ +NOCODB_BASE_URL=http://localhost:8080 +NOCODB_API_TOKEN=dev-token-12345 +NOCODB_TIMEOUT=10.0 +NOCODB_DEBUG=true +NOCODB_LOG_LEVEL=DEBUG +""" + + # Staging environment + staging_env = """ +NOCODB_BASE_URL=https://staging.nocodb.example.com +NOCODB_API_TOKEN=staging-token-67890 +NOCODB_TIMEOUT=30.0 +NOCODB_DEBUG=false +NOCODB_LOG_LEVEL=INFO +""" + + # Production environment + prod_env = """ +NOCODB_BASE_URL=https://nocodb.example.com +NOCODB_API_TOKEN=prod-token-secure +NOCODB_TIMEOUT=120.0 +NOCODB_DEBUG=false +NOCODB_LOG_LEVEL=WARNING +NOCODB_VERIFY_SSL=true +""" + + with TemporaryDirectory() as tmpdir: + envs = { + ".env.development": dev_env, + ".env.staging": staging_env, + ".env.production": prod_env, + } + + for filename, content in envs.items(): + env_path = Path(tmpdir) / filename + env_path.write_text(content) + + print("File structure:") + print(" .env.development <- Local development") + print(" .env.staging <- Staging environment") + print(" .env.production <- Production environment") + print() + + # Load based on APP_ENV environment variable + app_env = os.getenv("APP_ENV", "development") + env_file = Path(tmpdir) / f".env.{app_env}" + + print(f"Loading: .env.{app_env}") + load_dotenv(env_file, override=True) + + config = NocoDBConfig.from_env() + print(f" Base URL: {config.base_url}") + print(f" Debug: {config.debug}") + + print("\nMultiple environments example completed!") + + +# ============================================================================= +# EXAMPLE 3: .env with Secrets Management +# ============================================================================= +def example_secrets_management(): + """Demonstrate secure secrets handling with .env files.""" + print("\n" + "=" * 60) + print("Example 3: Secrets Management Best Practices") + print("=" * 60) + + print( + """ +BEST PRACTICES FOR .env FILES: + +1. NEVER commit .env files to version control: + + # .gitignore + .env + .env.* + !.env.example + +2. Create an .env.example template (safe to commit): + + # .env.example + NOCODB_BASE_URL=https://your-instance.com + NOCODB_API_TOKEN= + NOCODB_TIMEOUT=60.0 + +3. Use different files for different environments: + + .env <- Local overrides (gitignored) + .env.development <- Development defaults + .env.production <- Production settings + .env.example <- Template (committed) + +4. Load with precedence (local overrides defaults): + + from dotenv import load_dotenv + + # Load base configuration + load_dotenv('.env.development') + + # Override with local settings + load_dotenv('.env', override=True) + +5. Validate required variables: + + import os + + required = ['NOCODB_BASE_URL', 'NOCODB_API_TOKEN'] + missing = [var for var in required if not os.getenv(var)] + + if missing: + raise ValueError(f"Missing required env vars: {missing}") +""" + ) + + print("Secrets management best practices shown!") + + +# ============================================================================= +# EXAMPLE 4: Reading .env Without Modifying Environment +# ============================================================================= +def example_dotenv_values(): + """Demonstrate reading .env without polluting the environment.""" + print("\n" + "=" * 60) + print("Example 4: Read .env Without Modifying os.environ") + print("=" * 60) + + env_content = """ +NOCODB_BASE_URL=https://isolated-example.com +NOCODB_API_TOKEN=isolated-token +NOCODB_TIMEOUT=45.0 +""" + + with TemporaryDirectory() as tmpdir: + env_path = Path(tmpdir) / ".env" + env_path.write_text(env_content) + + # Read values without modifying os.environ + config_dict = dotenv_values(env_path) + + print("Read without modifying environment:") + for key, value in config_dict.items(): + print(f" {key}={value}") + + # Verify os.environ was not modified + print() + print("os.environ['NOCODB_BASE_URL'] =", os.getenv("NOCODB_BASE_URL", "(not set)")) + + # Create config manually from dotenv_values + config = NocoDBConfig( + base_url=config_dict.get("NOCODB_BASE_URL", ""), + api_token=config_dict.get("NOCODB_API_TOKEN", ""), + timeout=float(config_dict.get("NOCODB_TIMEOUT", "30.0")), + ) + print() + print(f"Created config with timeout: {config.timeout}s") + + print("\ndotenv_values example completed!") + + +# ============================================================================= +# EXAMPLE 5: Combining .env with Config Files +# ============================================================================= +def example_combined_config(): + """Demonstrate combining .env for secrets with config files for settings.""" + print("\n" + "=" * 60) + print("Example 5: Combining .env with Config Files") + print("=" * 60) + + print( + """ +RECOMMENDED PATTERN: Separate secrets from configuration + +1. .env file (gitignored) - Contains ONLY secrets: + + NOCODB_API_TOKEN=secret-token-here + NOCODB_PROTECTION_AUTH=protection-secret + +2. config.yaml (committed) - Contains non-secret settings: + + base_url: "https://nocodb.example.com" + timeout: 60.0 + max_retries: 3 + pool_connections: 20 + +3. Load both in your application: + + from dotenv import load_dotenv + from nocodb_simple_client.config import NocoDBConfig + import os + + # Load secrets into environment + load_dotenv() + + # Load settings from config file + config = NocoDBConfig.from_file(Path('config.yaml')) + + # Override with environment secrets + config.api_token = os.getenv('NOCODB_API_TOKEN') + +This approach: + - Keeps secrets out of config files + - Allows config files to be version controlled + - Works seamlessly with CI/CD pipelines + - Supports Docker secrets and cloud KMS +""" + ) + + print("Combined configuration pattern shown!") + + +# ============================================================================= +# EXAMPLE 6: Docker and Cloud Compatibility +# ============================================================================= +def example_docker_cloud(): + """Demonstrate .env usage with Docker and cloud platforms.""" + print("\n" + "=" * 60) + print("Example 6: Docker and Cloud Compatibility") + print("=" * 60) + + print( + """ +DOCKER USAGE: + +1. docker-compose.yml: + + services: + app: + image: my-app + env_file: + - .env + environment: + - NOCODB_BASE_URL=https://nocodb.example.com + +2. Docker run: + + docker run --env-file .env my-app + +3. In your Python code: + + # No need to call load_dotenv() - Docker already loaded vars + config = NocoDBConfig.from_env() + + +CLOUD PLATFORMS: + +AWS ECS/Lambda: + - Use AWS Secrets Manager or Parameter Store + - Set environment variables in task definition + +Google Cloud Run: + - Use Secret Manager + - Set environment variables in service config + +Azure: + - Use Azure Key Vault + - Set in App Service configuration + +Kubernetes: + - Use ConfigMaps for settings + - Use Secrets for tokens + - Mount as environment variables + + +LOCAL DEVELOPMENT with dotenv: + + from dotenv import load_dotenv + + # Only load .env in development + if os.getenv('ENVIRONMENT') != 'production': + load_dotenv() + + config = NocoDBConfig.from_env() +""" + ) + + print("Docker and cloud patterns shown!") + + +# ============================================================================= +# MAIN ENTRY POINT +# ============================================================================= +def main(): + """Run all dotenv examples.""" + print("\n" + "=" * 60) + print("NOCODB SIMPLE CLIENT - DOTENV EXAMPLES") + print("=" * 60) + print() + print("These examples demonstrate the [dotenv] optional dependency group.") + print() + print("Installation: pip install 'nocodb-simple-client[dotenv]'") + print() + print(f"python-dotenv available: {DOTENV_AVAILABLE}") + + # Run examples + example_basic_dotenv() + example_multiple_environments() + example_secrets_management() + example_dotenv_values() + example_combined_config() + example_docker_cloud() + + print("\n" + "=" * 60) + print("All dotenv examples completed!") + print("=" * 60) + + +if __name__ == "__main__": + main()