MaIS Client Configuration
All uses of this package begin by instantiating a
stanford.mais.client.MAISClient. But you should not instantiate the
class directly. Instead, you should use one of the conveneice class methods
for the Environment you plan on accessing.
What is an Environment?
MaIS operates multiple instances of its services, grouped into Environments.
The Environment that everyone knows is PROD, the production environment
accessible through URLs like workgroup.stanford.edu and
accounts.stanford.edu. Changes made in PROD propagate to downstream systems
like Active Directory and LDAP, and to third-party systems like Google
Groups.
In addition to PROD, developers will often interact with the UAT
Environment. UAT (short for “User Acceptance Testing”) is an environment with
production-quality data, but which does not touch downstream production
systems. For example, changes in UAT will not affect production LDAP, but they
do propagate to a separate (UAT) LDAP environment.
UAT exists both for MaIS developers, and for clients: MaIS developers use UAT as a final testing area before a change ‘goes live’ in PROD. Clients use UAT as a way to visualize changes they plan on making in PROD. If they have a separate non-production infrastructure, some clients also use MaIS’ UAT platform as their non-production infrastructure’s ‘source of truth’, having users authenticate via UAT Stanford Login, use UAT LDAP, use UAT Workgroup Manager, etc.
Clients who are testing data related to the Pinnacle project also have access
to the temporary UAT1 environment. This is an example of how environments can
be ‘spun up’ as needed for larger projects. MaIS developers also create their
own environments, as needed for their work. With proper configuration, this
project (and the MAISClient) can access all of
them.
The MAISClient
To get access to MaIS APIs, you will need a client certificate with permissions
to the appropriate service and environment, and you will also need to
instantiate a MAISClient using the appropriate
class method.
For more information on how to obtain a client certificate, and get permission to use MaIS APIs, read Getting Started with the MaIS Web APIs.
The MAISClient class has three classmethods
available, for connecting to the three Environments that most users will use:
prod()connects you to MaIS’ production APIs. This is what you will probably use in production code.uat()connects you to MaIS’ UAT APIs. This is what you will probably use during testing and development.uat1()connects you to MaIS’ special UAT1 API. This is what you will probably use during Pinnacle project work.
Warning
Not all MaIS applications and APIs are present in the UAT1 environment.
Here is a partial example (with parameters elided) showing how to access the different MAIS Environments:
# A MAISClient connected to the PROD Environment
client_prod = MAISClient.prod(...)
# A MAISClient connected to UAT
client_uat = MAISClient.uat(...)
# This Account API client is connected to PROD
aclient_prod = AccountClient(client_prod)
# This Account API client is connected to UAT
aclient_uat = AccountClient(client_uat)
The only difference between accessing PROD and accessing UAT, is in which
MAISClient you instantiate.
Constructor Parameters
All of the MAISClient constructor class methods
take the same parameters:
cert: A path to a client certificatekey: optional path to a client certificate key.
The parameters can be any “path-like” object, including a string or a
pathlib.Path.
For example, here is a basic case, with client certificate and private key in separate files, at hard-coded paths:
client = MAISClient.prod(
cert='/etc/myapp/cert.pem',
key='/etc/myapp/key.pem'
)
If your path contains characters like ~ to refer to the user’s home
directory, you should make the path into a pathlib.Path and call
pathlib.Path.expanduser() before passing it to the constructor. You may
also wish to subsequently call pathlib.Path.resolve() with
strict=True to make the path absolute, and ensure it is valid. For
example:
my_cert_and_key = pathlib.Path('~/symlink/app.pem')
try:
cert = my_cert_and_key.expanduser().resolve(strict=True)
client = MAISClient.prod(
cert=cert,
)
except OSError:
print(f"Path `{my_cert_and_key}` not found")
The above example also shows how the MAISClient
supports PEM files which contain the certficiate and its private key in a
single file.
Tip
When the certificate and private key are in the same PEM file, the private key is placed after the certificate.
Warning
The PEM format supports storing private keys in encrypted format, where a passphrase is needed to decrypt the private key. This project does not support those types of private keys.
Timeouts
This package uses Python Requests to make API calls. By default, Requests does not have any explicit timeouts; timeouts are left up to the operating system. On Linux, for example, connections may take up to ~30 seconds to time out, and open connections may wait for hours without reading data.
For more control over timeouts, the MAISClient
constructor has the timeout parameter, giving you two options for setting a
timeout:
To specify a single timeout (in seconds), used for both connecting and reading data, provide a single float number as the timeout.
Important
This does not combine the two timeouts into one, it just specifies the same number for both timeouts.
To specify separate timeouts for connecting and reading, create an instance of the
stanford.mais.client.Timeoutclass and use that.
Here is an example of both methods:
# Long connect and read timeouts of 20 seconds each
client1 = MAISClient.prod(
cert='/etc/myapp/cert.pem',
key='/etc/myapp/key.pem',
timeout=20
)
# A short connect timeout (3.5 seconds); a long (20-second) read timeout
client2 = MAISClient.prod(
cert='/etc/myapp/cert.pem',
key='/etc/myapp/key.pem',
timeout=stanford.mais.client.Timeout(
connect=3.5,
read=20,
)
)
Custom Environments
If you are a MaIS developer, you might wish to use a
MAISClient with an environment that is not PROD,
UAT, or UAT1. Hello!
This is the only situation in which you would call the
MAISClient constructor directly, instead of
using one of its class methods (like
prod() or
uat()). You will need to create a
_URLs instance, which is a
typing.TypedDict containing one key for each MaIS API: The key is the
API name and the value is the base URL.
dev_environment = stanford.mais.client._URLs(
account='https://localhost:4000/accounts/',
)
credentials = pathlib.Path(os.environ['devcert'])
dev_client = MAISClient(
cert=credentials.expanduser().resolve(strict=True),
urls=dev_environment,
)
dev_account = AccountClient(dev_client)
Tip
You may also wish to set a custom Timeout.
Module Documentation
Here is detailed documentation for the stanford.mais.client module.
To begin using any of the MaIS API clients, you must first instantiate a
MAISClient. This stores configuration common to
all clients.
- class stanford.mais.client.MAISClient(urls: _URLs, cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None)[source]
The
MAISClientis the first thing you will instantiate when you want to interact with a MaIS API.You probably do not want to call this directly. For convenience, you should one of the ready-made constructors,
MAISClient.prod()andMAISClient.uat().MAISClient.uat1()is also available for the temporary UAT1 environment.This class is documented here only to be a reference for the parameters you will need to provide when using a convenience constructor (in particular,
cert,key, andtimeout).- urls: _URLs
A mapping of API to URL.
This mapping uses API names (like
account) as keys, and the base URL as the value.If you decided to not use the convenience constructors (
MAISClient.prod(), etc.), then you will need to provide entries for each API you plan on using.- Raises:
TypeError – You did not provide a mapping.
- cert: PathLike
The path to a TLS client certificate.
This may contain either a single TLS client certificate, or a TLS private key followed by a certificate. The combined format (key and cert in one file) was common in the old days; the former format (key and cert in separate files) is often preferred today.
The certificate (and key, if included) must be in PEM format (the text format that has “BEGIN” and “END” lines).
A test load will be made before the constructor completes.
Note
If you provide your own
session, then this parameter is ignored, though the test will still be performed.Warning
If you provide a non-absolute path, it may not be relative to a home directory (like
~), or to a variable (like%UserProfile%). If you have such a path, run it throughpathlib.Path.resolve()first.- Raises:
FileNotFoundError – The file does not exist.
PermissionError – You do not have read permission on the file.
ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.
- key: PathLike | None = None
The path to a TLS private key.
This must be the private key associated with the provided certificate, any must only be set if the private key is in a separate file from the certificate. If the private key and certificate are in the same file, then this must be set to
None.The file must be in PEM format (the text format that has “BEGIN” and “END” lines), and must contain a single key.
Warning
The private key must not be password-protected. Enabling support for this is covered in https://github.com/psf/requests/issues/2519.
A test load of the key and certificate will be made before the constructor completes.
Note
If you provide your own
session, then this parameter is ignored. though the test will still be performedWarning
If you provide a non-absolute path, it may not be relative to a home directory (like
~), or to a variable (like%UserProfile%). If you have such a path, run it throughpathlib.Path.resolve()first.- Raises:
FileNotFoundError – The file does not exist.
PermissionError – We do not have read permission on the file.
ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.
- session: Session
The Requests Session to use for API requests.
In most cases, you should not provide a Session during instance creation. The default behavior is to let the class constructor create the Session, using the
cert, (optional)key, anddefault_timeoutparameters. Some headers are also pre-configured.If you provide your own Session, then you are responsible for configuring it, and the
cert,key, andtimeoutparameters are ignored.
- timeout: Timeout | float | None = None
The timeout to use for requests.
Requests does not support setting default timeouts, so we internally subclass
requests.Sessionto implement a default timeout. This is the timeout that will be used.In addition, to match what Requests supports, we accept
None(to not set a specific timeout) and a single float (covering both timeouts).There are two separate timeouts, a connect timeout and a read timeout. See the documentation of
Timeoutfor more information.
- classmethod prod(cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None) MAISClient[source]
Return a client configured to connect to production (PROD) APIs.
The returned client has all of the URLs pre-configured.
Note
A new client instance is created every time you call this. If you want to take advantage of caching, call this only once per thread.
- Parameters:
cert – See
MAISClientfor more information.key – See
MAISClientfor more information.timeout – See
MAISClientfor more information.
- Raises:
FileNotFoundError – The file does not exist.
PermissionError – We do not have read permission on the file.
ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.
- classmethod uat(cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None) MAISClient[source]
Return a client configured to connect to production-track test (UAT) APIs.
The returned client has all of the URLs pre-configured.
Note
A new client instance is created every time you call this. If you want to take advantage of caching, call this only once per thread.
- Parameters:
cert – See
MAISClientfor more information.key – See
MAISClientfor more information.timeout – See
MAISClientfor more information.
- Raises:
FileNotFoundError – The file does not exist.
PermissionError – We do not have read permission on the file.
ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.
- classmethod uat1(cert: PathLike, key: PathLike | None = None, timeout: Timeout | float | None = None) MAISClient[source]
Return a client configured to connect to connect to UAT1 APIs, used for Sequoia testing.
UAT1 is available for the Account, Authority (also known as “Privilege”) APIs, since those are the APIs that directly depend on data from HR.
If you have credentials that work for UAT, they will work for UAT1.
Note
UAT1 does have a Workgroup API, but it uses the XML-based 1.0 API. This Python package uses the JSON-based 2.0 API. Therefore, this Python package will not work with UAT1 at this time.
Note
A new client instance is created every time you call this. If you want to take advantage of caching, call this only once per thread.
- Parameters:
cert – See
MAISClientfor more information.key – See
MAISClientfor more information.timeout – See
MAISClientfor more information.
- Raises:
FileNotFoundError – The file does not exist.
PermissionError – We do not have read permission on the file.
ssl.SSLError – The private key and certificate do not match, or there was some other problem loading the certificate.
Depending on your application, you may wish to set a non-default timeout. The
Timeout class can help you do that.
- class stanford.mais.client.Timeout(connect: float, read: float)[source]
Allows providing granular timeouts when making web requests.
This library uses Requests to make HTTPS calls. Requests has a peculiar way for dealing with timeouts. See the Requests documentation for more information on timeouts.
If you want to configure granular timeouts, you may do so using this class. The instance of this class is then passed to the
MAISClientconstructor.- connect: float
The connection timeout.
How long to wait for the TCP connection to open. This should be larger than a multiple of 3.0, due to TCP mechanics.
Warning
If connecting to a DNS name which has multiple IPs, each IP will be tried, and so the total timeout will be this value multiplied by the number of IPs.
Finally, if you plan on instantiating a
MAISClient that points to an Environment that
does not have a convenience function (like
prod() or
uat()), you will need to provide a
_URLs.