Skip to content

Commit 81f420c

Browse files
authored
Merge pull request #86 from stax-labs/feat/multi_config
feat(sdk): allow for multi config
2 parents cfdd181 + 9043ab6 commit 81f420c

12 files changed

Lines changed: 382 additions & 192 deletions

File tree

README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,12 @@ export STAX_ACCESS_KEY=<your_access_key>
2323
export STAX_SECRET_KEY=<your_secret_key>
2424
```
2525

26+
##### Client Auth Configuration
27+
You can configure each client individually by passing in a config on init.
28+
When a client is created it's configuration will be locked in and any change to the configurations will not affect the client.
29+
30+
This can be seen in our [guide](https://github.com/stax-labs/lib-stax-python-sdk/blob/master/examples/auth.py).
31+
2632
*Optional configuration:*
2733

2834
##### Authentication token expiry

examples/auth.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import json
2+
import os
3+
4+
from staxapp.config import Config
5+
from staxapp.openapi import StaxClient
6+
from customscript import get_hostnames
7+
hostname = get_hostnames()
8+
9+
access_key = os.getenv("STAX_ACCESS_KEY")
10+
secret_key = os.getenv("STAX_SECRET_KEY")
11+
12+
Config.hostname = hostname["au1"]
13+
Config.access_key = access_key
14+
Config.secret_key = secret_key
15+
16+
accounts_au1 = StaxClient('accounts')
17+
18+
au1_response = accounts_au1.CreateAccountType(
19+
Name="sdk-au1"
20+
)
21+
22+
print(json.dumps(au1_response, indent=4, sort_keys=True))
23+
24+
access_key_2 = os.getenv("STAX_ACCESS_KEY_2")
25+
secret_key_2 = os.getenv("STAX_SECRET_KEY_2")
26+
config = Config(hostname=hostname["us1"], access_key=access_key_2, secret_key=secret_key_2)
27+
28+
us1_accounts = StaxClient('accounts', config=config)
29+
30+
us1_response = us1_accounts.CreateAccountType(
31+
Name="sdk-us1"
32+
)
33+
print(json.dumps(us1_response, indent=4, sort_keys=True))

staxapp/api.py

Lines changed: 26 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,12 @@
77

88

99
class Api:
10-
_requests_auth = None
10+
@classmethod
11+
def get_config(cls, config=None):
12+
if config is None:
13+
config = Config.GetDefaultConfig()
14+
config.init()
15+
return config
1116

1217
@classmethod
1318
def _headers(cls, custom_headers) -> dict:
@@ -17,12 +22,6 @@ def _headers(cls, custom_headers) -> dict:
1722
}
1823
return headers
1924

20-
@classmethod
21-
def _auth(cls, **kwargs):
22-
if not cls._requests_auth:
23-
cls._requests_auth = Config.get_auth_class().requests_auth
24-
return cls._requests_auth(Config.access_key, Config.secret_key, **kwargs)
25-
2625
@staticmethod
2726
def handle_api_response(response):
2827
try:
@@ -31,13 +30,13 @@ def handle_api_response(response):
3130
raise ApiException(str(e), response)
3231

3332
@classmethod
34-
def get(cls, url_frag, params={}, **kwargs):
35-
url_frag = url_frag.replace(f"/{Config.API_VERSION}", "")
36-
url = f"{Config.api_base_url()}/{url_frag.lstrip('/')}"
37-
33+
def get(cls, url_frag, params={}, config=None, **kwargs):
34+
config = cls.get_config(config)
35+
url_frag = url_frag.replace(f"/{config.API_VERSION}", "")
36+
url = f"{config.api_base_url()}/{url_frag.lstrip('/')}"
3837
response = requests.get(
3938
url,
40-
auth=cls._auth(),
39+
auth=config._auth(),
4140
params=params,
4241
headers=cls._headers(kwargs.get("headers", {})),
4342
**kwargs,
@@ -46,43 +45,46 @@ def get(cls, url_frag, params={}, **kwargs):
4645
return response.json()
4746

4847
@classmethod
49-
def post(cls, url_frag, payload={}, **kwargs):
50-
url_frag = url_frag.replace(f"/{Config.API_VERSION}", "")
51-
url = f"{Config.api_base_url()}/{url_frag.lstrip('/')}"
48+
def post(cls, url_frag, payload={}, config=None, **kwargs):
49+
config = cls.get_config(config)
50+
url_frag = url_frag.replace(f"/{config.API_VERSION}", "")
51+
url = f"{config.api_base_url()}/{url_frag.lstrip('/')}"
5252

5353
response = requests.post(
5454
url,
5555
json=payload,
56-
auth=cls._auth(),
56+
auth=config._auth(),
5757
headers=cls._headers(kwargs.get("headers", {})),
5858
**kwargs,
5959
)
6060
cls.handle_api_response(response)
6161
return response.json()
6262

6363
@classmethod
64-
def put(cls, url_frag, payload={}, **kwargs):
65-
url_frag = url_frag.replace(f"/{Config.API_VERSION}", "")
66-
url = f"{Config.api_base_url()}/{url_frag.lstrip('/')}"
64+
def put(cls, url_frag, payload={}, config=None, **kwargs):
65+
config = cls.get_config(config)
66+
url_frag = url_frag.replace(f"/{config.API_VERSION}", "")
67+
url = f"{config.api_base_url()}/{url_frag.lstrip('/')}"
6768

6869
response = requests.put(
6970
url,
7071
json=payload,
71-
auth=cls._auth(),
72+
auth=config._auth(),
7273
headers=cls._headers(kwargs.get("headers", {})),
7374
**kwargs,
7475
)
7576
cls.handle_api_response(response)
7677
return response.json()
7778

7879
@classmethod
79-
def delete(cls, url_frag, params={}, **kwargs):
80-
url_frag = url_frag.replace(f"/{Config.API_VERSION}", "")
81-
url = f"{Config.api_base_url()}/{url_frag.lstrip('/')}"
80+
def delete(cls, url_frag, params={}, config=None, **kwargs):
81+
config = cls.get_config(config)
82+
url_frag = url_frag.replace(f"/{config.API_VERSION}", "")
83+
url = f"{config.api_base_url()}/{url_frag.lstrip('/')}"
8284

8385
response = requests.delete(
8486
url,
85-
auth=cls._auth(),
87+
auth=config._auth(),
8688
params=params,
8789
headers=cls._headers(kwargs.get("headers", {})),
8890
**kwargs,

staxapp/auth.py

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,18 @@
1414

1515

1616
class StaxAuth:
17-
def __init__(self, config_branch, max_retries: int = 3):
18-
config = StaxConfig.api_config
19-
20-
self.identity_pool = config.get(config_branch).get("identityPoolId")
21-
self.user_pool = config.get(config_branch).get("userPoolId")
22-
self.client_id = config.get(config_branch).get("userPoolWebClientId")
23-
self.aws_region = config.get(config_branch).get("region")
17+
def __init__(self, config_branch: str, config: StaxConfig, max_retries: int = 3):
18+
self.config = config
19+
api_config = self.config.api_config
20+
self.identity_pool = api_config.get(config_branch).get("identityPoolId")
21+
self.user_pool = api_config.get(config_branch).get("userPoolId")
22+
self.client_id = api_config.get(config_branch).get("userPoolWebClientId")
23+
self.aws_region = api_config.get(config_branch).get("region")
2424
self.max_retries = max_retries
2525

26-
def requests_auth(self, username, password, **kwargs):
26+
def requests_auth(self, **kwargs):
27+
username = self.config.access_key
28+
password = self.config.secret_key
2729
if username is None:
2830
raise InvalidCredentialsException(
2931
"Please provide an Access Key to your config"
@@ -37,10 +39,10 @@ def requests_auth(self, username, password, **kwargs):
3739
id_creds = self.sts_from_cognito_identity_pool(id_token, **kwargs)
3840
auth = self.sigv4_signed_auth_headers(id_creds)
3941

40-
StaxConfig.expiration = id_creds.get("Credentials").get("Expiration")
41-
StaxConfig.auth = auth
42+
self.config.expiration = id_creds.get("Credentials").get("Expiration")
43+
self.config.auth = auth
4244

43-
return StaxConfig.auth
45+
return self.config.auth
4446

4547
def id_token_from_cognito(
4648
self, username=None, password=None, srp_client=None, **kwargs
@@ -59,6 +61,7 @@ def id_token_from_cognito(
5961
client_id=self.client_id,
6062
client=srp_client,
6163
)
64+
6265
try:
6366
tokens = aws.authenticate_user()
6467
except ClientError as e:
@@ -69,7 +72,7 @@ def id_token_from_cognito(
6972
elif e.response["Error"]["Code"] == "UserNotFoundException":
7073
raise InvalidCredentialsException(
7174
message=str(e),
72-
detail="Please check your Access Key, that you have created your Api Token and that you are using the right STAX REGION",
75+
detail=f"Please check your Access Key, that you have created your Api Token and that you are using the right STAX REGION",
7376
)
7477
else:
7578
raise InvalidCredentialsException(
@@ -121,7 +124,7 @@ def sigv4_signed_auth_headers(self, id_creds):
121124
aws_access_key=id_creds.get("Credentials").get("AccessKeyId"),
122125
aws_secret_access_key=id_creds.get("Credentials").get("SecretKey"),
123126
aws_token=id_creds.get("Credentials").get("SessionToken"),
124-
aws_host=f"{StaxConfig.hostname}",
127+
aws_host=f"{self.config.hostname}",
125128
aws_region=self.aws_region,
126129
aws_service="execute-api",
127130
)
@@ -133,17 +136,20 @@ class RootAuth:
133136
def requests_auth(username, password, **kwargs):
134137
if StaxConfig.expiration and StaxConfig.expiration > datetime.now(timezone.utc):
135138
return StaxConfig.auth
136-
137-
return StaxAuth("JumaAuth").requests_auth(username, password, **kwargs)
139+
config = StaxConfig.GetDefaultConfig()
140+
config.init()
141+
config.access_key = username
142+
config.secret_key = password
143+
return StaxAuth("JumaAuth", config).requests_auth(**kwargs)
138144

139145

140146
class ApiTokenAuth:
141147
@staticmethod
142-
def requests_auth(username, password, **kwargs):
148+
def requests_auth(config: StaxConfig, **kwargs):
143149
# Minimize the potentical for token to expire while still being used for auth (say within a lambda function)
144-
if StaxConfig.expiration and StaxConfig.expiration - timedelta(
150+
if config.expiration and config.expiration - timedelta(
145151
minutes=int(environ.get("TOKEN_EXPIRY_THRESHOLD_IN_MINS", 1))
146152
) > datetime.now(timezone.utc):
147-
return StaxConfig.auth
153+
return config.auth
148154

149-
return StaxAuth("ApiAuth").requests_auth(username, password, **kwargs)
155+
return StaxAuth("ApiAuth", config).requests_auth(**kwargs)

staxapp/config.py

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import logging
22
import os
33
import platform as sysinfo
4+
from distutils.command.config import config
5+
from email.policy import default
46

57
import requests
68

@@ -18,26 +20,34 @@ class Config:
1820
STAX_REGION = os.getenv("STAX_REGION", "au1.staxapp.cloud")
1921
API_VERSION = "20190206"
2022

23+
cached_api_config = dict()
2124
api_config = dict()
2225
access_key = None
2326
secret_key = None
2427
auth_class = None
28+
auth = None
29+
_requests_auth = None
2530
_initialized = False
2631
base_url = None
2732
hostname = f"api.{STAX_REGION}"
2833
org_id = None
29-
auth = None
3034
expiration = None
3135
load_live_schema = True
3236

3337
platform = sysinfo.platform()
3438
python_version = sysinfo.python_version()
3539
sdk_version = staxapp.__version__
3640

41+
def set_config(self):
42+
self.base_url = f"https://{self.hostname}/{self.API_VERSION}"
43+
config_url = f"{self.api_base_url()}/public/config"
44+
if config_url == self.cached_api_config.get("caching"):
45+
self.api_config = self.cached_api_config
46+
else:
47+
self.api_config = Config.get_api_config(config_url)
48+
3749
@classmethod
38-
def set_config(cls):
39-
cls.base_url = f"https://{cls.hostname}/{cls.API_VERSION}"
40-
config_url = f"{cls.api_base_url()}/public/config"
50+
def get_api_config(cls, config_url):
4151
config_response = requests.get(config_url)
4252
try:
4353
config_response.raise_for_status()
@@ -46,30 +56,42 @@ def set_config(cls):
4656
raise ApiException(
4757
str(e), config_response, detail=" Could not load API config."
4858
)
49-
50-
cls.api_config = config_response.json()
51-
52-
@classmethod
53-
def init(cls, config=None):
54-
if cls._initialized:
59+
cls.cached_api_config = config_response.json()
60+
cls.cached_api_config["caching"] = config_url
61+
return config_response.json()
62+
63+
def __init__(self, hostname=None, access_key=None, secret_key=None):
64+
if hostname is not None:
65+
self.hostname = hostname
66+
self.access_key = access_key
67+
self.secret_key = secret_key
68+
69+
def init(self):
70+
if self._initialized:
5571
return
72+
self.set_config()
5673

57-
if not config:
58-
cls.set_config()
74+
self._initialized = True
5975

60-
cls._initialized = True
76+
def _auth(self, **kwargs):
77+
if not self._requests_auth:
78+
self._requests_auth = self.get_auth_class().requests_auth
79+
return self._requests_auth(self, **kwargs)
6180

62-
@classmethod
63-
def api_base_url(cls):
64-
return cls.base_url
81+
def api_base_url(self):
82+
return self.base_url
6583

6684
@classmethod
85+
def GetDefaultConfig(cls):
86+
config = Config(Config.hostname, Config.access_key, Config.secret_key)
87+
return config
88+
6789
def branch(cls):
6890
return os.getenv("STAX_BRANCH", "master")
6991

7092
@classmethod
7193
def schema_url(cls):
72-
return f"{cls.base_url}/public/api-document"
94+
return f"https://{cls.hostname}/{cls.API_VERSION}/public/api-document"
7395

7496
@classmethod
7597
def get_auth_class(cls):
@@ -78,6 +100,3 @@ def get_auth_class(cls):
78100

79101
cls.auth_class = ApiTokenAuth
80102
return cls.auth_class
81-
82-
83-
Config.init()

staxapp/contract.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def validate(cls, data, component):
5454
@staticmethod
5555
def default_swagger_template() -> dict:
5656
# Get the default swagger template from https://api.au1.staxapp.cloud/20190206/public/api-document
57-
schema_response = requests.get(Config.schema_url()).json()
57+
schema_response = requests.get(Config.GetDefaultConfig().schema_url()).json()
5858
template = dict(
5959
openapi="3.0.0",
6060
info={

0 commit comments

Comments
 (0)