Skip to content

Commit

Permalink
feat: support ENV-based configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
iloveitaly committed Oct 29, 2023
1 parent 93e6246 commit 3ba4150
Show file tree
Hide file tree
Showing 2 changed files with 77 additions and 14 deletions.
27 changes: 23 additions & 4 deletions netsuite/cli/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import logging
import sys

from ..config import Config
from ..constants import DEFAULT_INI_PATH, DEFAULT_INI_SECTION
from . import helpers, interact, misc, rest_api, restlet, soap_api

Expand All @@ -26,7 +27,7 @@ def main():
"-l",
"--log-level",
help="The log level to use",
default="INFO",
default=None,
choices=("DEBUG", "INFO", "WARNING", "ERROR"),
)
parser.add_argument(
Expand All @@ -42,6 +43,12 @@ def main():
default=DEFAULT_INI_SECTION,
)

parser.add_argument(
"--config-environment",
help="Use environment variables for configuration",
action="store_true",
)

subparser = parser.add_subparsers(help="App CLI", required=True)

misc.add_parser(parser, subparser)
Expand Down Expand Up @@ -74,10 +81,22 @@ def main():
restlet_parser.print_help()
return

config = helpers.load_config_or_error(parser, args.config_path, args.config_section)
config = None

if args.config_environment:
config = Config.from_env()
else:
config = helpers.load_config_or_error(
parser, args.config_path, args.config_section
)

log_level = args.log_level

if log_level is None:
log_level = config.log_level

log_level = getattr(logging, args.log_level)
logging.basicConfig(level=log_level)
log_level_number = getattr(logging, log_level)
logging.basicConfig(level=log_level_number)

ret = args.func(config, args)

Expand Down
64 changes: 54 additions & 10 deletions netsuite/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import configparser
import os
import typing as t
from typing import Dict, Union

from pydantic import BaseModel
Expand All @@ -18,6 +20,7 @@ class TokenAuth(BaseModel):
class Config(BaseModel):
account: str
auth: TokenAuth
log_level: str
# TODO: Support OAuth2
# auth: Union[OAuth2, TokenAuth]

Expand All @@ -26,6 +29,52 @@ def account_slugified(self) -> str:
# https://followingnetsuite.wordpress.com/2018/10/18/suitetalk-sandbox-urls-addendum/
return self.account.lower().replace("_", "-")

@staticmethod
def _reorganize_auth_keys(raw: Dict[str, t.Any]) -> Dict[str, t.Any]:
reorganized: Dict[str, Union[str, Dict[str, str]]] = {"auth": {}}

for key, val in raw.items():
if key in TokenAuth.__fields__:
reorganized["auth"][key] = val
else:
reorganized[key] = val
return reorganized

@classmethod
def from_env(cls):
"""
Initialize config from environment variables.
- `NETSUITE_AUTH_TYPE`: Specifies the type of authentication, defaults to `token`
- `NETSUITE_ACCOUNT`: The Netsuite account number.
- `NETSUITE_CONSUMER_KEY`: The consumer key for OAuth.
- `NETSUITE_CONSUMER_SECRET`: The consumer secret for OAuth.
- `NETSUITE_TOKEN_ID`: The token ID for OAuth.
- `NETSUITE_TOKEN_SECRET`: The token secret for OAuth.
- `NETSUITE_LOG_LEVEL`: log level for NetSuite debugging
Returns a dictionary of available config options.
"""

keys = [
"auth_type",
"account",
"consumer_key",
"consumer_secret",
"token_id",
"token_secret",
"log_level",
]
prefix = "NETSUITE_"
raw = {
k: os.environ[prefix + k.upper()]
for k in keys
if prefix + k.upper() in os.environ
}

reorganized = cls._reorganize_auth_keys(raw)
return cls(**reorganized)

@classmethod
def from_ini(
cls, path: str = DEFAULT_INI_PATH, section: str = DEFAULT_INI_SECTION
Expand All @@ -34,17 +83,12 @@ def from_ini(
with open(path) as fp:
iniconf.read_file(fp)

d: Dict[str, Union[str, Dict[str, str]]] = {"auth": {}}

auth_type = iniconf[section].get("auth_type", "token")
selected_configuration = iniconf[section]

auth_type = selected_configuration.get("auth_type", "token")
if auth_type != "token":
raise RuntimeError(f"Only token auth is supported, not `{auth_type}`")

for key, val in iniconf[section].items():
if auth_type == "token" and key in TokenAuth.__fields__:
d["auth"][key] = val # type: ignore[index]
else:
d[key] = val

return cls(**d) # type: ignore
raw = {key: val for key, val in selected_configuration.items()}
reorganized = cls._reorganize_auth_keys(raw)
return cls(**reorganized) # type: ignore

0 comments on commit 3ba4150

Please sign in to comment.