Skip to content

opensampl.load.routing

Decorator which ensures we are routing our db operations through a backend if configured, or directly if not.

route(route_endpoint, method='POST', send_file=False)

Handle routing to backend or direct database operations based on environment configuration via decorator.

Parameters:

Name Type Description Default
route_endpoint str

The backend endpoint to route to if ROUTE_TO_BACKEND is True.

required
method request_methods

If routing through backend, the request method. Default: POST.

'POST'
send_file bool

If True sends a file to backend. Otherwise, json. Default: False.

False

Returns:

Type Description

Decorator function that handles routing logic.

Source code in opensampl/load/routing.py
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
def route(route_endpoint: str, method: request_methods = "POST", send_file: bool = False):
    """
    Handle routing to backend or direct database operations based on environment configuration via decorator.

    Args:
        route_endpoint: The backend endpoint to route to if ROUTE_TO_BACKEND is True.
        method: If routing through backend, the request method. Default: POST.
        send_file: If True sends a file to backend. Otherwise, json. Default: False.

    Returns:
        Decorator function that handles routing logic.

    """

    def decorator(func: Callable) -> Callable:
        """
        Wrap the function with routing logic.

        Args:
            func: The function to be decorated.

        Returns:
            Wrapped function with routing capabilities.

        """

        @wraps(func)
        def wrapper(*args: list, **kwargs: dict) -> Optional[Callable]:
            """
            Handle the actual routing logic.

            Args:
                *args: Positional arguments passed to the wrapped function.
                **kwargs: Keyword arguments passed to the wrapped function.

            Returns:
                Result from either backend request or direct function call.

            Raises:
                requests.exceptions.RequestException: If backend request fails.

            """
            session = kwargs.pop("session", None)
            config = BaseConfig()
            config.check_routing_dependencies()

            logger.debug(f"{config.ROUTE_TO_BACKEND=}")

            # Config Validation deals with making sure we have a backend url if going through backend and
            # a database url if we are doing db operations directly
            if config.ROUTE_TO_BACKEND:
                headers = {
                    "access-key": config.API_KEY,
                }

                pyld = func(*args, **kwargs, _config=config)
                if send_file:
                    request_params = pyld
                    logger.debug(f"data={pyld.get('data')}")
                    logger.debug(f"filesize in bytes={len(pyld.get('files').get('file')[1])}")
                else:
                    request_params = {
                        "json": pyld,
                    }
                    headers.update({"Content-Type": "application/json"})
                    logger.debug(f"headers={json.dumps(headers, indent=4)}")
                    logger.debug(f"json={json.dumps(pyld, indent=4)}")
                # Extract data from the function
                try:
                    logger.debug(f"method={method} type={type(method)}")
                    logger.debug(f"request url={config.BACKEND_URL}/{route_endpoint}")
                    response = requests.request(
                        method=str(method),
                        url=f"{config.BACKEND_URL}/{route_endpoint}",
                        headers=headers,
                        **request_params,
                        timeout=300,
                        verify=not config.INSECURE_REQUESTS,
                    )
                    logger.debug(f"{response.request.method=}, {response.status_code=}, {response.url=}")
                    response.raise_for_status()
                    logger.debug(f"Response: {response.json()}")
                except requests.exceptions.RequestException as e:
                    logger.debug(f"Error making request to backend: {e}")
                    raise
            else:
                if not session:
                    session = sessionmaker(create_engine(config.DATABASE_URL))()  # ty: ignore[no-matching-overload]

                return func(*args, **kwargs, session=session, _config=config)

        return wrapper

    return decorator