togglr-sdk


Nametogglr-sdk JSON
Version 1.1.0 PyPI version JSON
download
home_pagehttps://github.com/togglr-project/togglr-sdk-python
SummarySDK for Togglr feature flags management system
upload_time2025-10-18 06:32:17
maintainerNone
docs_urlNone
authorRoman Chudov
requires_python>=3.8
licenseNone
keywords sdk togglr feature-flag
VCS
bugtrack_url
requirements httpx pydantic typing-extensions cachetools
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # Togglr Python SDK

Python SDK for working with Togglr - feature flag management system.

## Installation

```bash
pip install togglr-sdk
```

## Quick Start

```python
import togglr
from togglr import RequestContext

# Create client with default configuration
client = togglr.new_client(
    api_key="your-api-key-here",
    base_url="http://localhost:8090",
    timeout=1.0,
    cache={"enabled": True, "max_size": 1000, "ttl_seconds": 10}
)

# Use context manager for proper resource cleanup
with client:
    # Create request context
    context = RequestContext.new() \
        .with_user_id("user123") \
        .with_country("US") \
        .with_device_type("mobile") \
        .with_os("iOS") \
        .with_os_version("15.0") \
        .with_browser("Safari") \
        .with_language("en-US")
    
    # Evaluate feature flag
    try:
        value, enabled, found = client.evaluate("new_ui", context)
        if found:
            print(f"Feature enabled: {enabled}, value: {value}")
        else:
            print("Feature not found")
    except togglr.TogglrError as e:
        print(f"Error evaluating feature: {e}")
    
    # Simple enabled check
    try:
        is_enabled = client.is_enabled("new_ui", context)
        print(f"Feature is enabled: {is_enabled}")
    except togglr.FeatureNotFoundError:
        print("Feature not found")
    except togglr.TogglrError as e:
        print(f"Error checking feature: {e}")
    
    # With default value
    is_enabled = client.is_enabled_or_default("new_ui", context, default=False)
    print(f"Feature enabled (with default): {is_enabled}")
```

## Configuration

### Creating a client

```python
# With default settings
client = togglr.new_client("api-key")

# With custom configuration
client = togglr.new_client(
    api_key="api-key",
    base_url="https://api.togglr.com",
    timeout=2.0,
    retries=3,
    cache={"enabled": True, "max_size": 1000, "ttl_seconds": 10},
    insecure=True  # Skip SSL verification for self-signed certificates
)
```

### TLS/SSL Configuration

The SDK supports comprehensive TLS/SSL configuration for secure connections:

```python
# Using with_ methods
config = ClientConfig.default("api-key") \
    .with_base_url("https://api.togglr.com") \
    .with_ssl_ca_cert("/path/to/ca-certificate.pem") \
    .with_client_cert("/path/to/client-cert.pem", "/path/to/client-key.pem") \
    .with_tls_server_name("api.togglr.com") \
    .with_ssl_hostname_verification(True)

client = Client(config)

# Using new_client with TLS parameters
client = togglr.new_client(
    api_key="api-key",
    base_url="https://api.togglr.com",
    ssl_ca_cert="/path/to/ca-certificate.pem",
    cert_file="/path/to/client-cert.pem",
    key_file="/path/to/client-key.pem",
    ca_cert_data="-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----",
    tls_server_name="api.togglr.com",
    assert_hostname=True
)

# Using CA certificate data instead of file
config = ClientConfig.default("api-key") \
    .with_ca_cert_data("-----BEGIN CERTIFICATE-----\n...\n-----END CERTIFICATE-----")

# Insecure mode (only for testing)
config = ClientConfig.default("api-key") \
    .with_insecure()  # Disables SSL certificate verification
```

#### Available TLS Options

- `ssl_ca_cert`: Path to CA certificate file
- `cert_file`: Path to client certificate file  
- `key_file`: Path to client private key file
- `ca_cert_data`: CA certificate data (PEM or DER format)
- `assert_hostname`: Enable/disable SSL hostname verification
- `tls_server_name`: TLS Server Name Indication (SNI)
- `insecure`: Skip SSL verification (for testing only)

### Advanced configuration

```python
from togglr import Client, ClientConfig, RequestContext

# Create custom configuration
config = ClientConfig.default("api-key") \
    .with_base_url("https://api.togglr.com") \
    .with_timeout(2.0) \
    .with_retries(3) \
    .with_cache(enabled=True, max_size=1000, ttl_seconds=10) \
    .with_backoff(base_delay=0.2, max_delay=5.0, factor=1.5)

client = Client(config)
```

## Usage

### Creating request context

```python
context = RequestContext.new() \
    .with_user_id("user123") \
    .with_user_email("user@example.com") \
    .with_country("US") \
    .with_device_type("mobile") \
    .with_os("iOS") \
    .with_os_version("15.0") \
    .with_browser("Safari") \
    .with_language("en-US") \
    .with_age(25) \
    .with_gender("female") \
    .set("custom_attribute", "custom_value")
```

### Evaluating feature flags

```python
# Full evaluation
value, enabled, found = client.evaluate("feature_key", context)

# Simple enabled check
is_enabled = client.is_enabled("feature_key", context)

# With default value
is_enabled = client.is_enabled_or_default("feature_key", context, default=False)
```

### Health check

```python
if client.health_check():
    print("API is healthy")
else:
    print("API is not healthy")
```

### Error Reporting and Auto-Disable

The SDK supports reporting feature execution errors for auto-disable functionality:

```python
# Report an error for a feature
client.report_error(
    feature_key="feature_key",
    error_type="timeout",
    error_message="Service did not respond in 5s",
    context={
        "service": "payment-gateway",
        "timeout_ms": 5000,
        "retry_count": 3
    }
)

print("Error reported successfully - queued for processing")
```

### Feature Health Monitoring

Check the health status of features:

```python
# Get feature health status
health = client.get_feature_health("feature_key")
print(f"Feature Health:")
print(f"  Enabled: {health.enabled}")
print(f"  Auto Disabled: {health.auto_disabled}")
if health.error_rate is not None:
    print(f"  Error Rate: {health.error_rate * 100:.2f}%")
if health.last_error_at is not None:
    print(f"  Last Error: {health.last_error_at}")

# Simple health check
is_healthy = client.is_feature_healthy("feature_key")
print(f"Feature is healthy: {is_healthy}")
```

## Caching

The SDK supports optional caching of evaluation results:

```python
client = togglr.new_client(
    api_key="api-key",
    cache={"enabled": True, "max_size": 1000, "ttl_seconds": 10}
)
```

## Retries

The SDK automatically retries requests on temporary errors:

```python
config = ClientConfig.default("api-key") \
    .with_retries(3) \
    .with_backoff(base_delay=0.1, max_delay=2.0, factor=2.0)

client = Client(config)
```

## Logging and Metrics

```python
def custom_logger(message: str, **kwargs):
    print(f"[LOG] {message} {kwargs}")

class CustomMetrics:
    def inc_evaluate_request(self):
        # Increment request counter
        pass
    
    def inc_cache_hit(self):
        # Increment cache hit counter
        pass
    
    def inc_evaluate_error(self, error_code: str):
        # Increment error counter
        pass
    
    def observe_evaluate_latency(self, latency: float):
        # Observe evaluation latency
        pass

config = ClientConfig.default("api-key") \
    .with_logger(custom_logger) \
    .with_metrics(CustomMetrics())

client = Client(config)
```

## Error Handling

```python
try:
    value, enabled, found = client.evaluate("feature_key", context)
except togglr.UnauthorizedError:
    # Authorization error
    pass
except togglr.BadRequestError:
    # Bad request
    pass
except togglr.NotFoundError:
    # Resource not found
    pass
except togglr.InternalServerError:
    # Internal server error
    pass
except togglr.TooManyRequestsError:
    # Rate limit exceeded
    pass
except togglr.FeatureNotFoundError:
    # Feature flag not found
    pass
except togglr.TogglrError as e:
    # Other errors
    print(f"Error: {e}")
```

### Error Report Types

```python
# Create different types of error reports
client.report_error(
    feature_key="feature_key",
    error_type="timeout",
    error_message="Service timeout",
    context={"service": "payment-gateway", "timeout_ms": 5000}
)

client.report_error(
    feature_key="feature_key",
    error_type="validation",
    error_message="Invalid data",
    context={"field": "email", "error_code": "INVALID_FORMAT"}
)

client.report_error(
    feature_key="feature_key",
    error_type="service_unavailable",
    error_message="Service down",
    context={"service": "database", "status_code": 503}
)
```

### Feature Health Types

```python
# FeatureHealth provides detailed health information
from togglr import FeatureHealth

# Access health properties
health = client.get_feature_health("feature_key")
print(f"Feature Key: {health.feature_key}")
print(f"Environment Key: {health.environment_key}")
print(f"Enabled: {health.enabled}")
print(f"Auto Disabled: {health.auto_disabled}")
print(f"Error Rate: {health.error_rate}")  # Optional
print(f"Threshold: {health.threshold}")    # Optional
print(f"Last Error At: {health.last_error_at}")  # Optional
```

## Client Generation

To update the generated client from OpenAPI specification:

```bash
make generate
```

## Building and Testing

```bash
# Install development dependencies
make dev-install

# Build
make build

# Testing
make test

# Linting
make lint

# Format code
make format

# Clean
make clean
```

## Examples

Complete usage examples are located in the `examples/` directory:

- `simple_example.py` - Simple usage example
- `advanced_example.py` - Advanced example with custom configuration
- `tls_example.py` - TLS/SSL configuration examples

## Requirements

- Python 3.8+
- httpx
- pydantic
- cachetools (optional)

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/togglr-project/togglr-sdk-python",
    "name": "togglr-sdk",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.8",
    "maintainer_email": "Roman Chudov <roman.chudov@gmail.com>",
    "keywords": "sdk, togglr, feature-flag",
    "author": "Roman Chudov",
    "author_email": "Roman Chudov <roman.chudov@gmail.com>",
    "download_url": "https://files.pythonhosted.org/packages/f4/35/999666b4a452579b877e3f0f92b5aa40d2a39a161f8cb3e93333023696fa/togglr_sdk-1.1.0.tar.gz",
    "platform": null,
    "description": "# Togglr Python SDK\n\nPython SDK for working with Togglr - feature flag management system.\n\n## Installation\n\n```bash\npip install togglr-sdk\n```\n\n## Quick Start\n\n```python\nimport togglr\nfrom togglr import RequestContext\n\n# Create client with default configuration\nclient = togglr.new_client(\n    api_key=\"your-api-key-here\",\n    base_url=\"http://localhost:8090\",\n    timeout=1.0,\n    cache={\"enabled\": True, \"max_size\": 1000, \"ttl_seconds\": 10}\n)\n\n# Use context manager for proper resource cleanup\nwith client:\n    # Create request context\n    context = RequestContext.new() \\\n        .with_user_id(\"user123\") \\\n        .with_country(\"US\") \\\n        .with_device_type(\"mobile\") \\\n        .with_os(\"iOS\") \\\n        .with_os_version(\"15.0\") \\\n        .with_browser(\"Safari\") \\\n        .with_language(\"en-US\")\n    \n    # Evaluate feature flag\n    try:\n        value, enabled, found = client.evaluate(\"new_ui\", context)\n        if found:\n            print(f\"Feature enabled: {enabled}, value: {value}\")\n        else:\n            print(\"Feature not found\")\n    except togglr.TogglrError as e:\n        print(f\"Error evaluating feature: {e}\")\n    \n    # Simple enabled check\n    try:\n        is_enabled = client.is_enabled(\"new_ui\", context)\n        print(f\"Feature is enabled: {is_enabled}\")\n    except togglr.FeatureNotFoundError:\n        print(\"Feature not found\")\n    except togglr.TogglrError as e:\n        print(f\"Error checking feature: {e}\")\n    \n    # With default value\n    is_enabled = client.is_enabled_or_default(\"new_ui\", context, default=False)\n    print(f\"Feature enabled (with default): {is_enabled}\")\n```\n\n## Configuration\n\n### Creating a client\n\n```python\n# With default settings\nclient = togglr.new_client(\"api-key\")\n\n# With custom configuration\nclient = togglr.new_client(\n    api_key=\"api-key\",\n    base_url=\"https://api.togglr.com\",\n    timeout=2.0,\n    retries=3,\n    cache={\"enabled\": True, \"max_size\": 1000, \"ttl_seconds\": 10},\n    insecure=True  # Skip SSL verification for self-signed certificates\n)\n```\n\n### TLS/SSL Configuration\n\nThe SDK supports comprehensive TLS/SSL configuration for secure connections:\n\n```python\n# Using with_ methods\nconfig = ClientConfig.default(\"api-key\") \\\n    .with_base_url(\"https://api.togglr.com\") \\\n    .with_ssl_ca_cert(\"/path/to/ca-certificate.pem\") \\\n    .with_client_cert(\"/path/to/client-cert.pem\", \"/path/to/client-key.pem\") \\\n    .with_tls_server_name(\"api.togglr.com\") \\\n    .with_ssl_hostname_verification(True)\n\nclient = Client(config)\n\n# Using new_client with TLS parameters\nclient = togglr.new_client(\n    api_key=\"api-key\",\n    base_url=\"https://api.togglr.com\",\n    ssl_ca_cert=\"/path/to/ca-certificate.pem\",\n    cert_file=\"/path/to/client-cert.pem\",\n    key_file=\"/path/to/client-key.pem\",\n    ca_cert_data=\"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\",\n    tls_server_name=\"api.togglr.com\",\n    assert_hostname=True\n)\n\n# Using CA certificate data instead of file\nconfig = ClientConfig.default(\"api-key\") \\\n    .with_ca_cert_data(\"-----BEGIN CERTIFICATE-----\\n...\\n-----END CERTIFICATE-----\")\n\n# Insecure mode (only for testing)\nconfig = ClientConfig.default(\"api-key\") \\\n    .with_insecure()  # Disables SSL certificate verification\n```\n\n#### Available TLS Options\n\n- `ssl_ca_cert`: Path to CA certificate file\n- `cert_file`: Path to client certificate file  \n- `key_file`: Path to client private key file\n- `ca_cert_data`: CA certificate data (PEM or DER format)\n- `assert_hostname`: Enable/disable SSL hostname verification\n- `tls_server_name`: TLS Server Name Indication (SNI)\n- `insecure`: Skip SSL verification (for testing only)\n\n### Advanced configuration\n\n```python\nfrom togglr import Client, ClientConfig, RequestContext\n\n# Create custom configuration\nconfig = ClientConfig.default(\"api-key\") \\\n    .with_base_url(\"https://api.togglr.com\") \\\n    .with_timeout(2.0) \\\n    .with_retries(3) \\\n    .with_cache(enabled=True, max_size=1000, ttl_seconds=10) \\\n    .with_backoff(base_delay=0.2, max_delay=5.0, factor=1.5)\n\nclient = Client(config)\n```\n\n## Usage\n\n### Creating request context\n\n```python\ncontext = RequestContext.new() \\\n    .with_user_id(\"user123\") \\\n    .with_user_email(\"user@example.com\") \\\n    .with_country(\"US\") \\\n    .with_device_type(\"mobile\") \\\n    .with_os(\"iOS\") \\\n    .with_os_version(\"15.0\") \\\n    .with_browser(\"Safari\") \\\n    .with_language(\"en-US\") \\\n    .with_age(25) \\\n    .with_gender(\"female\") \\\n    .set(\"custom_attribute\", \"custom_value\")\n```\n\n### Evaluating feature flags\n\n```python\n# Full evaluation\nvalue, enabled, found = client.evaluate(\"feature_key\", context)\n\n# Simple enabled check\nis_enabled = client.is_enabled(\"feature_key\", context)\n\n# With default value\nis_enabled = client.is_enabled_or_default(\"feature_key\", context, default=False)\n```\n\n### Health check\n\n```python\nif client.health_check():\n    print(\"API is healthy\")\nelse:\n    print(\"API is not healthy\")\n```\n\n### Error Reporting and Auto-Disable\n\nThe SDK supports reporting feature execution errors for auto-disable functionality:\n\n```python\n# Report an error for a feature\nclient.report_error(\n    feature_key=\"feature_key\",\n    error_type=\"timeout\",\n    error_message=\"Service did not respond in 5s\",\n    context={\n        \"service\": \"payment-gateway\",\n        \"timeout_ms\": 5000,\n        \"retry_count\": 3\n    }\n)\n\nprint(\"Error reported successfully - queued for processing\")\n```\n\n### Feature Health Monitoring\n\nCheck the health status of features:\n\n```python\n# Get feature health status\nhealth = client.get_feature_health(\"feature_key\")\nprint(f\"Feature Health:\")\nprint(f\"  Enabled: {health.enabled}\")\nprint(f\"  Auto Disabled: {health.auto_disabled}\")\nif health.error_rate is not None:\n    print(f\"  Error Rate: {health.error_rate * 100:.2f}%\")\nif health.last_error_at is not None:\n    print(f\"  Last Error: {health.last_error_at}\")\n\n# Simple health check\nis_healthy = client.is_feature_healthy(\"feature_key\")\nprint(f\"Feature is healthy: {is_healthy}\")\n```\n\n## Caching\n\nThe SDK supports optional caching of evaluation results:\n\n```python\nclient = togglr.new_client(\n    api_key=\"api-key\",\n    cache={\"enabled\": True, \"max_size\": 1000, \"ttl_seconds\": 10}\n)\n```\n\n## Retries\n\nThe SDK automatically retries requests on temporary errors:\n\n```python\nconfig = ClientConfig.default(\"api-key\") \\\n    .with_retries(3) \\\n    .with_backoff(base_delay=0.1, max_delay=2.0, factor=2.0)\n\nclient = Client(config)\n```\n\n## Logging and Metrics\n\n```python\ndef custom_logger(message: str, **kwargs):\n    print(f\"[LOG] {message} {kwargs}\")\n\nclass CustomMetrics:\n    def inc_evaluate_request(self):\n        # Increment request counter\n        pass\n    \n    def inc_cache_hit(self):\n        # Increment cache hit counter\n        pass\n    \n    def inc_evaluate_error(self, error_code: str):\n        # Increment error counter\n        pass\n    \n    def observe_evaluate_latency(self, latency: float):\n        # Observe evaluation latency\n        pass\n\nconfig = ClientConfig.default(\"api-key\") \\\n    .with_logger(custom_logger) \\\n    .with_metrics(CustomMetrics())\n\nclient = Client(config)\n```\n\n## Error Handling\n\n```python\ntry:\n    value, enabled, found = client.evaluate(\"feature_key\", context)\nexcept togglr.UnauthorizedError:\n    # Authorization error\n    pass\nexcept togglr.BadRequestError:\n    # Bad request\n    pass\nexcept togglr.NotFoundError:\n    # Resource not found\n    pass\nexcept togglr.InternalServerError:\n    # Internal server error\n    pass\nexcept togglr.TooManyRequestsError:\n    # Rate limit exceeded\n    pass\nexcept togglr.FeatureNotFoundError:\n    # Feature flag not found\n    pass\nexcept togglr.TogglrError as e:\n    # Other errors\n    print(f\"Error: {e}\")\n```\n\n### Error Report Types\n\n```python\n# Create different types of error reports\nclient.report_error(\n    feature_key=\"feature_key\",\n    error_type=\"timeout\",\n    error_message=\"Service timeout\",\n    context={\"service\": \"payment-gateway\", \"timeout_ms\": 5000}\n)\n\nclient.report_error(\n    feature_key=\"feature_key\",\n    error_type=\"validation\",\n    error_message=\"Invalid data\",\n    context={\"field\": \"email\", \"error_code\": \"INVALID_FORMAT\"}\n)\n\nclient.report_error(\n    feature_key=\"feature_key\",\n    error_type=\"service_unavailable\",\n    error_message=\"Service down\",\n    context={\"service\": \"database\", \"status_code\": 503}\n)\n```\n\n### Feature Health Types\n\n```python\n# FeatureHealth provides detailed health information\nfrom togglr import FeatureHealth\n\n# Access health properties\nhealth = client.get_feature_health(\"feature_key\")\nprint(f\"Feature Key: {health.feature_key}\")\nprint(f\"Environment Key: {health.environment_key}\")\nprint(f\"Enabled: {health.enabled}\")\nprint(f\"Auto Disabled: {health.auto_disabled}\")\nprint(f\"Error Rate: {health.error_rate}\")  # Optional\nprint(f\"Threshold: {health.threshold}\")    # Optional\nprint(f\"Last Error At: {health.last_error_at}\")  # Optional\n```\n\n## Client Generation\n\nTo update the generated client from OpenAPI specification:\n\n```bash\nmake generate\n```\n\n## Building and Testing\n\n```bash\n# Install development dependencies\nmake dev-install\n\n# Build\nmake build\n\n# Testing\nmake test\n\n# Linting\nmake lint\n\n# Format code\nmake format\n\n# Clean\nmake clean\n```\n\n## Examples\n\nComplete usage examples are located in the `examples/` directory:\n\n- `simple_example.py` - Simple usage example\n- `advanced_example.py` - Advanced example with custom configuration\n- `tls_example.py` - TLS/SSL configuration examples\n\n## Requirements\n\n- Python 3.8+\n- httpx\n- pydantic\n- cachetools (optional)\n",
    "bugtrack_url": null,
    "license": null,
    "summary": "SDK for Togglr feature flags management system",
    "version": "1.1.0",
    "project_urls": {
        "Documentation": "https://github.com/togglr-project/togglr-sdk-python",
        "Homepage": "https://github.com/togglr-project/togglr-sdk-python",
        "Repository": "https://github.com/togglr-project/togglr-sdk-python.git"
    },
    "split_keywords": [
        "sdk",
        " togglr",
        " feature-flag"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "81d5086d5e0b9bf27d762096e91a8bf57505e3cc8547cdc896b47799b3e972ca",
                "md5": "2e30110c25d142999f9de86e5983bd07",
                "sha256": "267a43eb436a0436115c195117dadbe04e8b6d610de5f5a198c97cdd300985d5"
            },
            "downloads": -1,
            "filename": "togglr_sdk-1.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "2e30110c25d142999f9de86e5983bd07",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.8",
            "size": 19385,
            "upload_time": "2025-10-18T06:32:16",
            "upload_time_iso_8601": "2025-10-18T06:32:16.091373Z",
            "url": "https://files.pythonhosted.org/packages/81/d5/086d5e0b9bf27d762096e91a8bf57505e3cc8547cdc896b47799b3e972ca/togglr_sdk-1.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "f435999666b4a452579b877e3f0f92b5aa40d2a39a161f8cb3e93333023696fa",
                "md5": "18574e77080e7c2843b0c33744578530",
                "sha256": "a968780e4ee14a817f57c07009edb008aea95139270bbe2ececf2704bea1be07"
            },
            "downloads": -1,
            "filename": "togglr_sdk-1.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "18574e77080e7c2843b0c33744578530",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.8",
            "size": 19195,
            "upload_time": "2025-10-18T06:32:17",
            "upload_time_iso_8601": "2025-10-18T06:32:17.658349Z",
            "url": "https://files.pythonhosted.org/packages/f4/35/999666b4a452579b877e3f0f92b5aa40d2a39a161f8cb3e93333023696fa/togglr_sdk-1.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-10-18 06:32:17",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "togglr-project",
    "github_project": "togglr-sdk-python",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": false,
    "requirements": [
        {
            "name": "httpx",
            "specs": [
                [
                    ">=",
                    "0.24.0"
                ]
            ]
        },
        {
            "name": "pydantic",
            "specs": [
                [
                    ">=",
                    "2.0.0"
                ]
            ]
        },
        {
            "name": "typing-extensions",
            "specs": [
                [
                    ">=",
                    "4.0.0"
                ]
            ]
        },
        {
            "name": "cachetools",
            "specs": [
                [
                    ">=",
                    "5.3.0"
                ]
            ]
        }
    ],
    "lcname": "togglr-sdk"
}
        
Elapsed time: 1.96920s