Architecture
Goals
Before we dig into the specific architecture we should discuss the guiding principles that impact our architecture.
Dos:
- Consistent - Be predictable, using the same naming conventions whenever possible
- Simple - Most operation should only require 2-3 short lines of code.
- Pythonic - API spec naming conventions should be made pythonic.
- Portable - Architecture should be language agnostic
- (+) Structs
- (+) Functions
- (+) Namespaces
- (+) Composition
- (-) Inheritance
- (-) Polymorphism
Don'ts:
- Verbosity
- Don't overwhelm users with complicated object and function names.
- Avoid special casing shared concepts across different namespaces and contexts.
- Avoid new object types when existing known objects would suffice (e.g.,
dict
,list
)
client (Client)
Ideally, the client
builder function is the only thing most users will need to import.
This function should handle constructing the necessary Config
, Auth
and Session
objects used
to instantiate the Client
for the requested service name (e.g., clusters
, vpcs
, servers/virtual
).
Each service currently has an autogenerated Client
class which wraps a Session
object and provides methods for all the included paths (e.g., GetAll
, CreateServer
).
NOTES:
- All function and argument names are converted to the more pythonic snakecase format (e.g.,
GetAll
->get_all
,vpcId
->vpc_id
) - We currently gatekeep which paths are include in our
scripts/apigen.py
file. - Composition over inheritance as that seemed easier to implement across languages.
- We're passing a shared
Session
object into each generated independentClient
object rather than having shared logic in anAbstractClient
parent. - Namespacing over unique object names
- Rather than having complicated names you should be able to just load the service you care about
- We don't wrap method input / outputs in custom object types since folks already know how
list
,dict
, etc work.
Session
The Session object wraps all API requests calls, including any config defaults and passing the Auth
object.
The goal of this object is limit the complexity of the generated Client
objects for each service.
NOTES:
- All requests have the content type set to "application/json"`
- Any common error handling occurs in one place
- We just auto-extract the
json
and return theresults
item.
Config
The config handles loading values from ~/.config/denvr.toml
, or the location of the DENVR_CONFIG
environment variable.
An Auth
object is created from either:
DENVR_USERNAME
andDENVR_PASSWOR
- The
username
andpassword
values in thecredentials
section of thedenvr.toml
file.
Auth
An object for handling requesting and refreshing access tokens given an initial username and password.
It is callable and subtypes requests.auth.AuthBase
so that we can pass it as the auth
keyword to requests
NOTE: The password isn't stored in the object and will be deleted when it goes out of scope in the Auth
and Config
constructors.
Waiter
A Waiter
object connects an API action like apps.create_catalog_application
with a check function which polls until the resource is ready (e.g., status is "ONLINE"
).
The waiter
function provides a convenient way to create waiter objects for the most common operations.