What is the modules folder used for?

The modules folder is where you put all of your Python code. You can use it to manipulate the data stream of any of the sources defined in the config.yaml. You can also manipulate the data that will be transmitted to Veza using the SDK.

  • each python file in the module must be referenced in the config.yaml file

  • @hooks are defined in the order they are found

  • fetch function can be defined in any of the python files, though conventionally we use modules/<appname>.py for this.

  • make sure you install and import modules required to function

# modules/layer7.py

from oaa.modules.lazy_dicts import lazy_dict_table

def fetch(connection=None, source=None, **kwargs):
    '''
    Fetch the data. This is the function that is called by oaa with the custom
    source defined.

    '''
    layer7 = Layer7API(connection, source)

    if source.params['resource'] == '/api-management/1.0/apis':
        res = layer7.fetch_apis(**source.params)
    else:
        res = layer7.fetch_paginated(**source.params)

    # Use lazy_dict_table for memory-efficient processing
    return lazy_dict_table(res)

The entire modules/layer7.py file can be found below.

# modules/layer7.py
'''
This module pull user, roles, group membership from the Layer7API API
'''

import time
import logging
import petl
from dotenv import load_dotenv
from oaa.modules.params_or_env import params_or_env
from oaa.modules import BaseURLSession
# from unittest.mock import MagicMock
logger = logging.getLogger(__name__)

load_dotenv()

class Layer7API:

    """Docstring for FLoqast. """

    def __init__(self, connection, source):
        """TODO: to be defined1.

        :connection: TODO
        :source: TODO

        """
        self._connection = connection
        self._source = source
        self.base_url = connection.params['layer7_base_uri']
        self.api_key = params_or_env(self._connection, self._source, 'LAYER7_API_TOKEN')
        self._session = None

    @property
    def session(self):
        if not self._session:
            self._session = BaseURLSession(self.base_url)
            self._session.headers.update(self.headers())

        return self._session

    def mkpath(self, path):
        return f'{self.base_url}{path}'

    def headers(self):
        header_dict = {
            'Authorization': f'Bearer {{self.api_key}}',
        }

        return header_dict

    def get(self, url, params=None, **kwargs):
        # self.session

        # Status: 429 means raite limit exhausted. Try again after x seconds
        res = self.session.get(url, params=params)

        if res.status_code == 429:
            # make this more rubust, exponential backoff
            time.sleep(5)

            return self.get(url, params=params, **kwargs)

        return res.json()

    def fetch_apis(self, *args, **kwargs):

        for api in self.fetch_paginated(*args, **kwargs):
            apiUuid = api['uuid']
            api['managers'] = [u['uuid'] for u in self.get(f'/api-management/0.1/permissions/apis/{apiUuid}').json()]
            yield api

    def fetch_paginated(self, resource, query={},
                        resource_key='results',
                        pagination_strategy='link-next',
                        _next=None,
                        **kwargs):

        if resource[0] != '/':
            resource = '/' + resource

        if _next:
            # TODO do we need to get just the path part of the URL?
            resource = _next
        res = self.get(resource,
                       params=query)

        if res is None:
            logger.debug('url: %s', resource)
            logger.error('res: %s', res)

            raise Exception(f'{resource} - {res}')
        res_json = res.json()

        yield from res_json.get(resource_key, [])

        if pagination_strategy == 'link-next':
            _next = res_json.get('_links', {}).get('next', {}).get('href')

            if _next:
                logger.info('fetching more results %s', _next)

                yield from self.fetch_paginated(resource,
                                                resource_key=resource_key,
                                                pagination_strategy=pagination_strategy,
                                                query=query,
                                                _next=_next, **kwargs)
        elif pagination_strategy == 'total-current':
            total_pages = res_json['totalPages']
            current_page = res_json['currentPage']

            if current_page < total_pages:
                query.setdefault('page', 1)
                query['page'] += 1
                yield from self.fetch_paginated(resource,
                                                query=query,
                                                resource_key=resource_key,
                                                pagination_strategy=pagination_strategy,
                                                **kwargs)


# ------------- the custom module fetch function

def fetch(connection=None, source=None, **kwargs):
    '''
    Fetch the data. This is the function that is called by oaa with the custom
    source defined.

    '''
    from oaa.modules.lazy_dicts import lazy_dict_table
    
    layer7 = Layer7API(connection, source)

    if source.params['resource'] == '/api-management/1.0/apis':
        res = layer7.fetch_apis(**source.params)
    else:
        res = layer7.fetch_paginated(**source.params)

    # Use lazy_dict_table for memory-efficient processing
    return lazy_dict_table(res)