Metadata-Version: 2.4
Name: cmdop-proxy
Version: 0.1.1
Summary: Provider-neutral proxy leases for Python applications.
Project-URL: Homepage, https://cmdop.com
Project-URL: Documentation, https://docs.cmdop.com
Project-URL: Repository, https://github.com/commandoperator/cmdop-proxy
Project-URL: Bug Tracker, https://github.com/commandoperator/cmdop-proxy/issues
License-Expression: Apache-2.0
Keywords: cmdop,httpx,lease,proxy,socks5
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: >=3.12
Requires-Dist: httpx[socks]<1,>=0.28
Provides-Extra: dev
Requires-Dist: build>=1.2; extra == 'dev'
Requires-Dist: pytest-asyncio<2,>=0.25; extra == 'dev'
Requires-Dist: pytest<9,>=8; extra == 'dev'
Requires-Dist: ruff<1,>=0.11; extra == 'dev'
Requires-Dist: twine<7,>=5; extra == 'dev'
Description-Content-Type: text/markdown

# cmdop-proxy

`cmdop-proxy` gives Python applications one provider-neutral contract for
acquiring and using bounded proxy leases. It is a client library, not a proxy
server and not a scraping framework.

The package intentionally separates two responsibilities:

- a broker selects and leases an endpoint;
- the application sends traffic directly through that endpoint with its native
  HTTP client.

Provider management APIs and account keys do not belong in this package.

## Install

```bash
pip install cmdop-proxy
```

SOCKS5 support is installed by default.

## Managed production proxy

The normal consumer API does not require a broker URL, pool, provider or target
profile. The library reads `CMDOP_PROXY_TOKEN` or the Docker secret
`/run/secrets/cmdop_proxy_token`, then owns lease, renewal and release:

```python
from cmdop_proxy import ProxySession

async with await ProxySession.open() as proxy:
    async with proxy.async_httpx_client(timeout=30) as client:
        response = await client.get("https://example.com/")
```

## Static/offline use

```python
import asyncio
from datetime import timedelta

from cmdop_proxy import (
    AcquireRequest,
    IdentityMode,
    ProxyEndpoint,
    ProxyProtocol,
    StaticBroker,
    create_async_client,
)


async def main() -> None:
    broker = StaticBroker(
        pools={
            "parser": [
                ProxyEndpoint(
                    endpoint_id="local-1",
                    host="127.0.0.1",
                    port=8080,
                    protocol=ProxyProtocol.HTTP,
                    username="user",
                    password="secret",
                )
            ]
        }
    )
    lease = await broker.acquire(
        AcquireRequest(
            pool="parser",
            purpose="source-acquisition",
            target="example",
            protocols=(ProxyProtocol.HTTP,),
            identity_mode=IdentityMode.FIXED,
            affinity="account-primary",
            ttl=timedelta(minutes=15),
        ),
        idempotency_key="run-001",
    )

    async with create_async_client(lease, timeout=30) as client:
        response = await client.get("https://example.com/")
        response.raise_for_status()


asyncio.run(main())
```

`StaticBroker` is deterministic, process-local and network-free. It is useful
for tests, local deployments and as the reference semantics for remote broker
contract tests. It does not persist leases across process restarts.

## Security properties

- Credentials never appear in endpoint or lease `repr` output.
- Proxy URLs percent-encode user information.
- The library does not set `HTTP_PROXY`, `HTTPS_PROXY` or any other global
  environment variable.
- Observations are typed categories and cannot carry response bodies, cookies,
  headers or arbitrary error messages.
- A renewal preserves identity or fails; it never silently changes endpoint.

Applications should keep leases in memory and persist only the lease ID when a
remote broker supports lease recovery.

## Cloudflare control plane

`CloudflareBroker` implements the same async contract over the versioned
`proxy.sdkrouter.com` API. The workload receives only its broker token; provider
management keys stay in Worker secrets.

```python
from cmdop_proxy import CloudflareBroker

async with CloudflareBroker(
    token=workload_token,
) as broker:
    lease = await broker.acquire(request, idempotency_key="parser-run-001")
```

The broker token is excluded from `repr`, remote error messages are mapped to
stable typed exceptions, and arbitrary response bodies are never copied into
exceptions.
