Skip to content

Commit

Permalink
Add user docs for SDK classes
Browse files Browse the repository at this point in the history
  • Loading branch information
deadlycoconuts committed Mar 11, 2022
1 parent 69430de commit 61f502a
Show file tree
Hide file tree
Showing 12 changed files with 360 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ such as the use of experiment engines, enrichers (pre-processors) or ensemblers

Hence to build a router using Turing SDK, you would need to incrementally define these components and build a
`RouterConfig` object (you can find more information on how these individual components need to be built in the
XXX section).
[Using Turing SDK Classes](../use-turing-sdk-classes) section).

Using the example shown in the `README` of the Turing SDK documentation main page, you need to construct a
`RouterConfig` instance by specifying the various components as arguments:
Expand Down
12 changes: 12 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/01-router.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Router

A `Router` object represents a router that is created on Turing API. It does not (and should not) ever be created
manually by using its constructor directly. Instead, you should only be manipulating with `Router` instances that
get returned as a result of using the various `Router` class and instance methods that interact with Turing API.

A `Router` object has attributes such as `id`, `name`, `project_id`, `environment_name`, `monitoring_url`, `status`
and `endpoint`. It also has a `config` attribute, which is a `RouterConfig` containing the current configuration for
the router.

When trying to replicate configuration from an existing router, always retrieve the underlying `RouterConfig` from
the `Router` instance by accessing its `config` attribute.
13 changes: 13 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/02-router-version.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# RouterVersion

A `RouterVersion` represents a single version (and configuration) of a Turing Router. Just as `Router` objects, they
should almost never be created manually by using their constructor.

Besides assessing attributes of a `RouterVersion` object directly, which will allow you to access attributes such as
`id`, `version`, `created_at`, `updated_at`, `environment_name`, `status`, `name`, `monitoring_url`, `log_config`,
you may also consider retrieving the entire router configuration from a specific `RouterVersion` object as a
`RouterConfig` for further manipulation:

```python
my_config = router_version.get_config()
```
46 changes: 46 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/03-router-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
# RouterConfig

`RouterConfig` objects are what you would probably interact most frequently with when using Turing SDK. They
essentially carry a router's configuration and define the ways in which a router should be run. All of the
methods that interact with Turing API that involve the updating or creating of routers involve the
use of `RouterConfig` objects as arguments.

As you would have seen before, a `RouterConfig` object is built using multiple parts:

```python
@dataclass
class RouterConfig:
"""
Class to create a new RouterConfig. Can be built up from its individual components or initialised instantly
from an appropriate API response
:param environment_name: name of the environment
:param name: name of the router
:param routes: list of routes used by the router
:param rules: list of rules used by the router
:param default_route_id: default route id to be used
:param experiment_engine: experiment engine config file
:param resource_request: resources to be provisioned for the router
:param timeout: request timeout which when exceeded, the request to the router will be terminated
:param log_config: logging config settings to be used with the router
:param enricher: enricher config settings to be used with the router
:param ensembler: ensembler config settings to be used with the router
"""
environment_name: str
name: str
routes: Union[List[Route], List[Dict[str, str]]] = None
rules: Union[List[TrafficRule], List[Dict]] = None
default_route_id: str = None
experiment_engine: Union[ExperimentConfig, Dict] = None
resource_request: Union[ResourceRequest, Dict[str, Union[str, int]]] = None
timeout: str = None
log_config: Union[LogConfig, Dict[str, Union[str, bool, int]]] = None
enricher: Union[Enricher, Dict] = None
ensembler: Union[RouterEnsemblerConfig, Dict] = None
```

When constructing a `RouterConfig` object from scratch, it is **highly recommended** that you construct each
individual component using the Turing SDK classes provided instead of using `dict` objects which do not perform any
schema validation.

In the following pages of this subsection, we will go through the usage of these individual components separately.
19 changes: 19 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/04-route.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# Route

Creating a route with Turing SDK is just as simple as doing it on the UI; one only needs to specify the `id`,
`endpoint`, and `timeout` of the route:

```python
@dataclass
class Route:
"""
Class to create a new Route object
:param id: route's name
:param endpoint: endpoint of the route. Must be a valid URL
:param timeout: timeout indicating the duration past which the request execution will end
"""
id: str
endpoint: str
timeout: str
```
17 changes: 17 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/05-experiment-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# ExperimentConfig

The `ExperimentConfig` class is a simple container to carry configuration related to an experiment to be used by a
Turing Router. Note that as Turing does not create experiments automatically, you would need to create your
experiments separately prior to specifying their configuration here.

Also, notice that `ExperimentConfig` does not contain any fixed schema as it simply carries configuration for
generic experiment engines, which are used as plug-ins for Turing. When building an `ExperimentConfig` from scratch,
you would need to consider the underlying schema for the `config` attribute as well as the appropriate `type` that
corresponds to your selected experiment engine:

```python
@dataclass
class ExperimentConfig:
type: str = "nop"
config: Dict = None
```
19 changes: 19 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/06-resource-request.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
# ResourceRequest

A `ResourceRequest` class carries information related to the resources that should be allocated to a particular
component, e.g. router, ensembler, enricher, etc., and is defined by 4 attributes, `min_replica`, `max_replica`,
`cpu_request`, `memory_request`:

```python
@dataclass
class ResourceRequest:
min_allowed_replica: ClassVar[int] = 0
max_allowed_replica: ClassVar[int] = 20
min_replica: int
max_replica: int
cpu_request: str
memory_request: str
```

Note that the units for CPU and memory requests are measured in cpu units and bytes respectively. You may wish to
read more about how these are measured [here](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/).
65 changes: 65 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/07-log-config.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# LogConfig

Logging for Turing Routers is done through BigQuery or Kafka, and its configuration is managed by the `LogConfig`
class. Two helper classes (child classes of `LogConfig`) have been created to assist you in constructing these objects:

```python
@dataclass
class BigQueryLogConfig(LogConfig):
"""
Class to create a new log config with a BigQuery config
:param table: name of the BigQuery table; if the table does not exist, it will be created automatically
:param service_account_secret: service account which has both JobUser and DataEditor privileges and write access
:param batch_load: optional parameter to indicate if batch loading is used
"""
def __init__(self,
table: str,
service_account_secret: str,
batch_load: bool = None):
self.table = table
self.service_account_secret = service_account_secret
self.batch_load = batch_load

super().__init__(result_logger_type=ResultLoggerType.BIGQUERY)
```

```python
@dataclass
class KafkaLogConfig(LogConfig):
def __init__(self,
brokers: str,
topic: str,
serialization_format: KafkaConfigSerializationFormat):
"""
Method to create a new log config with a Kafka config
:param brokers: comma-separated list of one or more Kafka brokers
:param topic: valid Kafka topic name on the server; data will be written to this topic
:param serialization_format: message serialization format to be used
"""
self.brokers = brokers
self.topic = topic
self.serialization_format = serialization_format

super().__init__(result_logger_type=ResultLoggerType.KAFKA)
```

If you are using a `KafkaLogConfig`, you would additionally have to define a `serialization_format`, which is of a
`KafkaConfigSerializationFormat`:

```python
class KafkaConfigSerializationFormat(Enum):
JSON = "json"
PROTOBUF = "protobuf"
```

If you do not intend to use any logging, simply create a regular `LogConfig` object with `result_loggger_type` set
as `ResultLoggerType.NOP`, without defining the other arguments:

```python
log_config = LogConfig(result_logger_type=ResultLoggerType.NOP)
```

While `ResultLoggerType` may take on the `enum` value of `ResultLoggerType.CONSOLE`, its behaviour is
currently undefined and you will almost certainly experience errors while using it.
40 changes: 40 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/08-enricher.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
# Enricher

An `Enricher` object holds configuration needed to define an enricher:

```python
@dataclass
class Enricher:
"""
Class to create a new Enricher
:param image: registry and name of the image
:param resource_request: ResourceRequest instance containing configs related to the resources required
:param endpoint: endpoint URL of the enricher
:param timeout: request timeout which when exceeded, the request to the enricher will be terminated
:param port: port number exposed by the container
:param env: environment variables required by the container
:param id: id of the enricher
:param service_account: optional service account for the Docker deployment
"""
image: str
resource_request: ResourceRequest
endpoint: str
timeout: str
port: int
env: List['EnvVar']
id: int = None
service_account: str = None
```

## EnvVar

To define environment variables for the `env` attribute in an `Enricher`, you would need to define them using the
`EnvVar` object:

```python
@dataclass
class EnvVar:
name: str
value: str
```
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# RouterEnsemblerConfig

Ensembling for Turing Routers is done through Standard, Docker or Pyfunc ensemblers, and its configuration is
managed by the `RouterEnsemblerConfig` class. Three helper classes (child classes of `RouterEnsemblerConfig`) have been
created to assist you in constructing these objects:

```python
@dataclass
class StandardRouterEnsemblerConfig(RouterEnsemblerConfig):
def __init__(self,
experiment_mappings: List[Dict[str, str]]):
"""
Method to create a new standard ensembler
:param experiment_mappings: configured mappings between routes and treatments
"""
self.experiment_mappings = experiment_mappings
super().__init__(type="standard")
```

```python
@dataclass
class DockerRouterEnsemblerConfig(RouterEnsemblerConfig):
def __init__(self,
image: str,
resource_request: ResourceRequest,
endpoint: str,
timeout: str,
port: int,
env: List['EnvVar'],
service_account: str = None):
"""
Method to create a new Docker ensembler
:param image: registry and name of the image
:param resource_request: ResourceRequest instance containing configs related to the resources required
:param endpoint: endpoint URL of the ensembler
:param timeout: request timeout which when exceeded, the request to the ensembler will be terminated
:param port: port number exposed by the container
:param env: environment variables required by the container
:param service_account: optional service account for the Docker deployment
"""
self.image = image
self.resource_request = resource_request
self.endpoint = endpoint
self.timeout = timeout
self.port = port
self.env = env
self.service_account = service_account
super().__init__(type="docker")
```

```python
@dataclass
class PyfuncRouterEnsemblerConfig(RouterEnsemblerConfig):
def __init__(self,
project_id: int,
ensembler_id: int,
timeout: str,
resource_request: ResourceRequest):
"""
Method to create a new Pyfunc ensembler
:param project_id: project id of the current project
:param ensembler_id: ensembler_id of the ensembler
:param resource_request: ResourceRequest instance containing configs related to the resources required
:param timeout: request timeout which when exceeded, the request to the ensembler will be terminated
"""
self.project_id = project_id
self.ensembler_id = ensembler_id
self.resource_request = resource_request
self.timeout = timeout
super().__init__(type="pyfunc")
```
53 changes: 53 additions & 0 deletions sdk/docs/how-to/use-turing-sdk-classes/10-traffic-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# TrafficRule

Each traffic rule is defined by at least one `TrafficRuleCondition` and one route. Routes are essentially the `id`s
of `Route` objects that you intend to specify for the entire `TrafficRule`.

```python
@dataclass
class TrafficRule:
"""
Class to create a new TrafficRule based on a list of conditions and routes
:param conditions: list of TrafficRuleConditions that need to ALL be satisfied before routing to the given routes
:param routes: list of routes to send the request to should all the given conditions be met
"""
conditions: Union[List[TrafficRuleCondition], List[Dict[str, List[str]]]]
routes: List[str]
```

## Traffic Rule Condition

When defining a traffic rule, one would need to decide between using a `HeaderTrafficRuleCondition` or a
`PayloadTrafficRuleCondition`. These subclasses can be used to build a `TrafficRuleCondition` without having to
manually set attributes such as `field_source` or `operator`:

```python
@dataclass
class HeaderTrafficRuleCondition(TrafficRuleCondition):
def __init__(self,
field: str,
values: List[str]):
"""
Method to create a new TrafficRuleCondition that is defined on a request header
:param field: name of the field specified
:param values: values that are supposed to match those found in the field
"""
super().__init__(field_source=FieldSource.HEADER, field=field, operator="in", values=values)


@dataclass
class PayloadTrafficRuleCondition(TrafficRuleCondition):
def __init__(self,
field: str,
values: List[str]):
"""
Method to create a new TrafficRuleCondition that is defined on a request payload
:param field: name of the field specified
:param values: values that are supposed to match those found in the field
"""
super().__init__(field_source=FieldSource.PAYLOAD, field=field, operator="in", values=values)
```

2 changes: 1 addition & 1 deletion sdk/tests/router/config/traffic_rule_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ def test_create_payload_traffic_rule_condition(field, values, expected, request)
[
HeaderTrafficRuleCondition(
field="x-region",
values= ["region-a", "region-b"],
values=["region-a", "region-b"],
),
PayloadTrafficRuleCondition(
field="service_type.id",
Expand Down

0 comments on commit 61f502a

Please sign in to comment.