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.pyfile. - Composition over inheritance as that seemed easier to implement across languages.
- We're passing a shared
Sessionobject into each generated independentClientobject rather than having shared logic in anAbstractClientparent. - 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
jsonand return theresultsitem.
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_USERNAMEandDENVR_PASSWOR- The
usernameandpasswordvalues in thecredentialssection of thedenvr.tomlfile.
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.