# python-orchard-features
---
A python library for managing features flags in a user context. It supports getting all features,
or a single feature either `account` (legacy), or `profile` based contexts.

## Example Usage

### Basic Python Usage

An example when not in a server based setting such as a python package, lambda, or daemon:

```python
from requests.structures import CaseInsensitiveDict

from owsrequest.context import RequestContext

from pythonfeatures import pythonfeatures

# ...

def main():
    features_response = pythonfeatures.get_all_features(RequestContext(
        CaseInsensitiveDict({
        'Orchard-Identity-Id': '5c8229d3cb82e62ebed79659'})))

    is_enabled_my_feature_flag = features_response.message.get(
        'my_feature_flag') == 'enabled'
    # ...

    if is_enabled_my_feature_flag:
        # ...
        # your code here

```

or as a single feature check

```python
from requests.structures import CaseInsensitiveDict

from owsrequest.context import RequestContext

from pythonfeatures import pythonfeatures

# ...

def main():
    feature_response = pythonfeatures.get_single_feature(
        'my_feature_flag', RequestContext(CaseInsensitiveDict({
        'Orchard-Identity-Id': '5c8229d3cb82e62ebed79659'})))

    is_enabled_my_feature_flag = feature_response.message == 'enabled'
    # ...

    if is_enabled_my_feature_flag:
        # ...
        # your code here

```

### Python Usage in Flask

In Flask the main difference is that you just pass in the `g.request_context` from your route.
If that does not exist, you will have to wrap `request.headers` in the `RequestContest` class.

##### E.g. `ows-users`:

In a handler, use `g.request_context`.

```python
from pythonfeatures import pythonfeatures

#...

@app.route(
    '/features',
    methods=['GET'],
    endpoint='get_features')
def get_features():
    """Get variants for all features."""

    features_response = pythonfeatures.get_all_features(
        g.request_context)
    #...

    is_enabled_my_feature_flag = features_response.message.get(
        'my_feature_flag') == 'enabled'
    # ...

    if is_enabled_my_feature_flag:
        # ...
        # your code here

```


## UWSGI
Make sure the `--enable-threads` option is enabled in `uwsgi-startup.sh` in case of both Multi-threaded or Multi-process setup.

Example of Multi-threaded in ows-users:

```
  uwsgi --http :8080 --chdir /var/app --wsgi-file ${WSGI_PATH} ${UWSGI_MODULE} --master \
  --processes 1 --threads 15 \
  --uid ${UWSGI_UID} --gid ${UWSGI_GID} -t ${UWSGI_TIMEOUT} \
  --http-keepalive --add-header ${UWSGI_HEADERS} \
  --buffer-size ${UWSGI_BUFFER_SIZE} --enable-threads
```

Example of Multi-process:

```
 uwsgi --http :8080 --chdir /var/app --wsgi-file ${WSGI_PATH} ${UWSGI_MODULE} --master \
 --processes 3 --uid ${UWSGI_UID} --gid ${UWSGI_GID} -t ${UWSGI_TIMEOUT} \
 --http-keepalive --add-header ${UWSGI_HEADERS} --buffer-size ${UWSGI_BUFFER_SIZE} \
 --enable-threads

```




## Environment
python-orchard-features connects to the split.io web console and the environments defined there using a SPLITIO_API_KEY.

### On QA/Prod:
This library searches for that in "python-orchard-features" secrets_manager. You can configure local usage of the secrets manager and generate the required tokens using this [aws-creds-generator](https://github.com/theorchard/collab/tree/master/jcarrion/aws-creds-generator) script.
For giving any ows service access to these secrets refer to these: https://github.com/theorchard/terraform-infra/pull/614/files terraform changes.

### Localhost mode
If the secrets_manager_client is unable to detect credentials for the SPLITIO_API_KEY python-orchard-features will
automatically default to run in localhost mode. In localhost mode, the only split flags recognized by the environment are
those defined in a .split file. The location of the .split file can be configured by changing the SPLIT_FILE_PATH config variable.
By default it looks for `$HOME/.split` .


## Get feature by attributes

Attributes are the way to match split.io's rules. Like "If user identity_id in [ ... ]".
Bypassing request context is not recommended, however if you need to target `Account` based context at the `vendor_id` or `subaccount_id` level without an `orchard_user_id` (e.g. 'alw:789', 'oa:123') this is the function to use:

```python
from pythonfeatures import pythonfeatures

# ...

def main():
    feature_response = pythonfeatures.get_single_feature_by_attributes(
        'my_feature_flag', {
        'identity_id': '5c8229d3cb82e62ebed79659'})

    is_enabled_my_feature_flag = feature_response.message == 'enabled'
    # ...

    if is_enabled_my_feature_flag:
        # ...
        # your code here

```

NOTE: `Orchard-Identity-Id` header is mapped to `identity_id` in Split.io.

## Testing

### Lint

`make lint`

### Unit

`make test`
