Skip to content

Commit 3fba133

Browse files
authored
Merge pull request #118 from mattjacksoncello/main
Adding ConfigClass for HTTPX
2 parents ce81ab6 + f4a9352 commit 3fba133

File tree

6 files changed

+292
-23
lines changed

6 files changed

+292
-23
lines changed

USAGE.md

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,70 @@ connect_async_client: Client = new_client(
2929
True)
3030
```
3131

32+
## Client Configuration
33+
34+
The SDK provides a `ClientConfig` class that allows you to configure the underlying httpx client. This includes SSL certificate verification and all other httpx client options.
35+
36+
### SSL Certificate Verification
37+
38+
When connecting to a 1Password Connect server using HTTPS, you may need to configure SSL certificate verification:
39+
40+
```python
41+
from onepasswordconnectsdk.config import ClientConfig
42+
43+
# Verify SSL using a custom CA certificate
44+
config = ClientConfig(ca_file="path/to/ca.pem")
45+
client = new_client("https://connect.example.com", "your-token", config=config)
46+
47+
# Disable SSL verification (not recommended for production)
48+
config = ClientConfig(verify=False)
49+
client = new_client("https://connect.example.com", "your-token", config=config)
50+
```
51+
52+
### Additional Configuration Options
53+
54+
The ClientConfig class accepts all httpx client options as keyword arguments. These options are passed directly to the underlying httpx client:
55+
56+
```python
57+
# Configure timeouts and redirects
58+
config = ClientConfig(
59+
ca_file="path/to/ca.pem",
60+
timeout=30.0, # 30 second timeout
61+
follow_redirects=True, # Follow HTTP redirects
62+
max_redirects=5 # Maximum number of redirects to follow
63+
)
64+
65+
# Configure proxy settings
66+
config = ClientConfig(
67+
proxies={
68+
"http://": "http://proxy.example.com",
69+
"https://": "https://proxy.example.com"
70+
}
71+
)
72+
73+
# Configure custom headers
74+
config = ClientConfig(
75+
headers={
76+
"User-Agent": "CustomApp/1.0",
77+
"X-Custom-Header": "value"
78+
}
79+
)
80+
```
81+
82+
### Async Client Configuration
83+
84+
The same configuration options work for both synchronous and asynchronous clients:
85+
86+
```python
87+
config = ClientConfig(
88+
ca_file="path/to/ca.pem",
89+
timeout=30.0
90+
)
91+
async_client = new_client("https://connect.example.com", "your-token", is_async=True, config=config)
92+
```
93+
94+
For a complete list of available configuration options, see the [httpx client documentation](https://www.python-httpx.org/api/#client).
95+
3296
## Environment Variables
3397

3498
- **OP_CONNECT_TOKEN** – The token to be used to authenticate with the 1Password Connect API.
@@ -166,4 +230,4 @@ async def main():
166230
await async_client.session.aclose() # close the client gracefully when you are done
167231

168232
asyncio.run(main())
169-
```
233+
```
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
#!/usr/bin/env python3
2+
"""
3+
Example script demonstrating how to connect to a 1Password Connect server
4+
using CA certificate verification and list all secrets in a vault.
5+
6+
Shows both synchronous and asynchronous usage.
7+
Update the configuration variables below with your values.
8+
"""
9+
10+
import asyncio
11+
from onepasswordconnectsdk.client import new_client
12+
from onepasswordconnectsdk.config import ClientConfig
13+
14+
# Configuration
15+
CONNECT_URL = "https://connect.example.com" # Your 1Password Connect server URL
16+
TOKEN = "eyJhbGc..." # Your 1Password Connect token
17+
VAULT_ID = "vaults_abc123" # ID of the vault to list secrets from
18+
CA_FILE = "path/to/ca.pem" # Path to your CA certificate file
19+
20+
def list_vault_secrets():
21+
"""
22+
Connect to 1Password Connect server and list all secrets in the specified vault.
23+
Uses CA certificate verification for secure connection.
24+
"""
25+
try:
26+
# Configure client with CA certificate verification
27+
config = ClientConfig(
28+
ca_file=CA_FILE,
29+
timeout=30.0 # 30 second timeout
30+
)
31+
32+
# Initialize client with configuration
33+
client = new_client(CONNECT_URL, TOKEN, config=config)
34+
35+
# Get all items in the vault
36+
items = client.get_items(VAULT_ID)
37+
38+
# Print items
39+
print(f"\nSecrets in vault {VAULT_ID}:")
40+
print("-" * 40)
41+
for item in items:
42+
print(f"- {item.title} ({item.category})")
43+
44+
except Exception as e:
45+
print(f"Error: {str(e)}")
46+
47+
48+
async def list_vault_secrets_async():
49+
"""
50+
Async version: Connect to 1Password Connect server and list all secrets in the specified vault.
51+
Uses CA certificate verification for secure connection.
52+
"""
53+
try:
54+
# Configure client with CA certificate verification
55+
config = ClientConfig(
56+
ca_file=CA_FILE,
57+
timeout=30.0 # 30 second timeout
58+
)
59+
60+
# Initialize async client with configuration
61+
client = new_client(CONNECT_URL, TOKEN, is_async=True, config=config)
62+
63+
# Get all items in the vault
64+
items = await client.get_items(VAULT_ID)
65+
66+
# Print items
67+
print(f"\nSecrets in vault {VAULT_ID} (async):")
68+
print("-" * 40)
69+
for item in items:
70+
print(f"- {item.title} ({item.category})")
71+
72+
# Close the client gracefully
73+
await client.session.aclose()
74+
75+
except Exception as e:
76+
print(f"Error: {str(e)}")
77+
78+
if __name__ == "__main__":
79+
# Run sync version
80+
print("Running synchronous example...")
81+
list_vault_secrets()
82+
83+
# Run async version
84+
print("\nRunning asynchronous example...")
85+
asyncio.run(list_vault_secrets_async())

src/onepasswordconnectsdk/async_client.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
"""Python AsyncClient for connecting to 1Password Connect"""
22
import httpx
33
from httpx import HTTPError
4-
from typing import Dict, List, Union
4+
from typing import Dict, List, Union, Optional
55
import os
66

77
from onepasswordconnectsdk.serializer import Serializer
8+
from onepasswordconnectsdk.config import ClientConfig
89
from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout
910
from onepasswordconnectsdk.errors import (
1011
FailedToRetrieveItemException,
@@ -16,15 +17,29 @@
1617
class AsyncClient:
1718
"""Python Async Client Class"""
1819

19-
def __init__(self, url: str, token: str) -> None:
20-
"""Initialize async client"""
20+
def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None:
21+
"""Initialize async client
22+
23+
Args:
24+
url (str): The url of the 1Password Connect API
25+
token (str): The 1Password Service Account token
26+
config (Optional[ClientConfig]): Optional configuration for httpx client
27+
"""
2128
self.url = url
2229
self.token = token
30+
self.config = config
2331
self.session = self.create_session(url, token)
2432
self.serializer = Serializer()
2533

2634
def create_session(self, url: str, token: str) -> httpx.AsyncClient:
27-
return httpx.AsyncClient(base_url=url, headers=self.build_headers(token), timeout=get_timeout())
35+
headers = self.build_headers(token)
36+
timeout = get_timeout()
37+
38+
if self.config:
39+
client_args = self.config.get_client_args(url, headers, timeout)
40+
return httpx.AsyncClient(**client_args)
41+
42+
return httpx.AsyncClient(base_url=url, headers=headers, timeout=timeout)
2843

2944
def build_headers(self, token: str) -> Dict[str, str]:
3045
return build_headers(token)

src/onepasswordconnectsdk/client.py

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
import httpx
33
from httpx import HTTPError, USE_CLIENT_DEFAULT
44
import json
5-
from typing import Dict, List, Union
5+
from typing import Dict, List, Union, Optional
66
import os
77

88
from onepasswordconnectsdk.async_client import AsyncClient
9+
from onepasswordconnectsdk.config import ClientConfig
910
from onepasswordconnectsdk.serializer import Serializer
1011
from onepasswordconnectsdk.utils import build_headers, is_valid_uuid, PathBuilder, get_timeout
1112
from onepasswordconnectsdk.errors import (
@@ -24,15 +25,29 @@
2425
class Client:
2526
"""Python Client Class"""
2627

27-
def __init__(self, url: str, token: str) -> None:
28-
"""Initialize client"""
28+
def __init__(self, url: str, token: str, config: Optional[ClientConfig] = None) -> None:
29+
"""Initialize client
30+
31+
Args:
32+
url (str): The url of the 1Password Connect API
33+
token (str): The 1Password Service Account token
34+
config (Optional[ClientConfig]): Optional configuration for httpx client
35+
"""
2936
self.url = url
3037
self.token = token
38+
self.config = config
3139
self.session = self.create_session(url, token)
3240
self.serializer = Serializer()
3341

3442
def create_session(self, url: str, token: str) -> httpx.Client:
35-
return httpx.Client(base_url=url, headers=self.build_headers(token), timeout=get_timeout())
43+
headers = self.build_headers(token)
44+
timeout = get_timeout()
45+
46+
if self.config:
47+
client_args = self.config.get_client_args(url, headers, timeout)
48+
return httpx.Client(**client_args)
49+
50+
return httpx.Client(base_url=url, headers=headers, timeout=timeout)
3651

3752
def build_headers(self, token: str) -> Dict[str, str]:
3853
return build_headers(token)
@@ -381,19 +396,21 @@ def sanitize_for_serialization(self, obj):
381396
return self.serializer.sanitize_for_serialization(obj)
382397

383398

384-
def new_client(url: str, token: str, is_async: bool = False) -> Union[AsyncClient, Client]:
399+
def new_client(url: str, token: str, is_async: bool = False, config: Optional[ClientConfig] = None) -> Union[AsyncClient, Client]:
385400
"""Builds a new client for interacting with 1Password Connect
386-
Parameters:
387-
url: The url of the 1Password Connect API
388-
token: The 1Password Service Account token
389-
is_async: Initialize async or sync client
390-
401+
402+
Args:
403+
url (str): The url of the 1Password Connect API
404+
token (str): The 1Password Service Account token
405+
is_async (bool): Initialize async or sync client
406+
config (Optional[ClientConfig]): Optional configuration for httpx client
407+
391408
Returns:
392-
Client: The 1Password Connect client
409+
Union[AsyncClient, Client]: The 1Password Connect client
393410
"""
394411
if is_async:
395-
return AsyncClient(url, token)
396-
return Client(url, token)
412+
return AsyncClient(url, token, config)
413+
return Client(url, token, config)
397414

398415

399416
def new_client_from_environment(url: str = None) -> Union[AsyncClient, Client]:

src/onepasswordconnectsdk/config.py

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
import os
22
import shlex
3-
from typing import List, Dict
4-
from onepasswordconnectsdk.client import Client
3+
from typing import List, Dict, Optional, TYPE_CHECKING
4+
import httpx
5+
6+
if TYPE_CHECKING:
7+
from onepasswordconnectsdk.client import Client
58
from onepasswordconnectsdk.models import (
69
Item,
710
ParsedField,
@@ -16,7 +19,48 @@
1619
)
1720

1821

19-
def load_dict(client: Client, config: dict):
22+
class ClientConfig:
23+
"""Configuration class for 1Password Connect client.
24+
Inherits from httpx.BaseClient to support all httpx client options.
25+
"""
26+
def __init__(self, ca_file: Optional[str] = None, **kwargs):
27+
"""Initialize client configuration
28+
29+
Args:
30+
ca_file (Optional[str]): Path to CA certificate file for SSL verification
31+
**kwargs: Additional httpx client options
32+
"""
33+
self.ca_file = ca_file
34+
self.httpx_options = kwargs
35+
36+
def get_client_args(self, base_url: str, headers: Dict[str, str], timeout: float) -> Dict:
37+
"""Get arguments for httpx client initialization
38+
39+
Args:
40+
base_url (str): Base URL for the client
41+
headers (Dict[str, str]): Headers to include in requests
42+
timeout (float): Request timeout in seconds
43+
44+
Returns:
45+
Dict: Arguments for httpx client initialization
46+
"""
47+
args = {
48+
'base_url': base_url,
49+
'headers': headers,
50+
'timeout': timeout,
51+
}
52+
53+
# Set verify from ca_file first
54+
if self.ca_file:
55+
args['verify'] = self.ca_file
56+
57+
# Allow httpx_options (including verify) to override
58+
args.update(self.httpx_options)
59+
60+
return args
61+
62+
63+
def load_dict(client: "Client", config: dict):
2064
"""Load: Takes a dictionary with keys specifiying the user
2165
desired naming scheme of the values to return. Each key's
2266
value is a dictionary that includes information on where
@@ -83,7 +127,7 @@ def load_dict(client: Client, config: dict):
83127
return config_values
84128

85129

86-
def load(client: Client, config: object):
130+
def load(client: "Client", config: object):
87131
"""Load: Takes a an object with class attributes annotated with tags
88132
describing where to find desired fields in 1Password. Manipulates given object
89133
and fills attributes in with 1Password item field values.
@@ -162,7 +206,7 @@ def _vault_uuid_for_field(field: str, vault_tag: dict):
162206

163207

164208
def _set_values_for_item(
165-
client: Client,
209+
client: "Client",
166210
parsed_item: ParsedItem,
167211
config_dict={},
168212
config_object: object = None,

0 commit comments

Comments
 (0)