From a5449900dca7c4b4a0eb46f9ab69f990131cab5e Mon Sep 17 00:00:00 2001 From: Vincent Pochet Date: Mon, 4 Nov 2024 12:01:49 +0100 Subject: [PATCH] feat(felixible aggregation): Expose expression in API (#281) * feat(felixible aggregation): Expose expression in API * feat(felixible aggregation): Add documentation for expression evaluation endpoint --- .../billable_metrics/clients.py | 24 ++++++++++++- lago_python_client/models/__init__.py | 3 ++ lago_python_client/models/billable_metric.py | 19 ++++++++++- tests/fixtures/billable_metric.json | 7 ++-- .../billable_metric_evaluate_expression.json | 5 +++ tests/fixtures/billable_metric_index.json | 2 ++ tests/test_billable_metric_client.py | 34 +++++++++++++++++++ 7 files changed, 87 insertions(+), 7 deletions(-) create mode 100644 tests/fixtures/billable_metric_evaluate_expression.json diff --git a/lago_python_client/billable_metrics/clients.py b/lago_python_client/billable_metrics/clients.py index 568a8d55..fa7e399a 100644 --- a/lago_python_client/billable_metrics/clients.py +++ b/lago_python_client/billable_metrics/clients.py @@ -8,7 +8,13 @@ FindCommandMixin, UpdateCommandMixin, ) -from ..models.billable_metric import BillableMetricResponse +from ..models.billable_metric import ( + BillableMetricEvaluateExpression, + BillableMetricEvaluateExpressionResponse, + BillableMetricResponse, +) +from ..services.request import make_headers, make_url, send_post_request +from ..services.response import Response, prepare_object_response, get_response_data class BillableMetricClient( @@ -22,3 +28,19 @@ class BillableMetricClient( API_RESOURCE: ClassVar[str] = "billable_metrics" RESPONSE_MODEL: ClassVar[Type[BillableMetricResponse]] = BillableMetricResponse ROOT_NAME: ClassVar[str] = "billable_metric" + + def evaluate_expression( + self, input_object: BillableMetricEvaluateExpression + ) -> BillableMetricEvaluateExpressionResponse: + api_response: Response = send_post_request( + url=make_url( + origin=self.base_url, + path_parts=(self.API_RESOURCE, "evaluate_expression"), + ), + headers=make_headers(api_key=self.api_key), + ) + + return prepare_object_response( + response_model=BillableMetricEvaluateExpressionResponse, + data=get_response_data(response=api_response, key="expression_result"), + ) diff --git a/lago_python_client/models/__init__.py b/lago_python_client/models/__init__.py index fe98c200..13730de8 100644 --- a/lago_python_client/models/__init__.py +++ b/lago_python_client/models/__init__.py @@ -3,6 +3,9 @@ BillableMetric as BillableMetric, BillableMetricFilter as BillableMetricFilter, BillableMetricFilters as BillableMetricFilters, + BillableMetricEvaluateExpressionEvent as BillableMetricEvaluateExpressionEvent, + BillableMetricEvaluateExpression as BillableMetricEvaluateExpression, + BillableMetricEvaluateExpressionResponse as BillableMetricEvaluateExpressionResponse, ) from .charge import ( Charge as Charge, diff --git a/lago_python_client/models/billable_metric.py b/lago_python_client/models/billable_metric.py index b76bc9eb..46bcbd30 100644 --- a/lago_python_client/models/billable_metric.py +++ b/lago_python_client/models/billable_metric.py @@ -1,4 +1,4 @@ -from typing import List, Optional +from typing import Any, Dict, List, Optional, Union from lago_python_client.base_model import BaseModel @@ -21,6 +21,7 @@ class BillableMetric(BaseModel): recurring: Optional[bool] aggregation_type: Optional[str] weighted_interval: Optional[str] + expression: Optional[str] field_name: Optional[str] filters: Optional[BillableMetricFilters] @@ -33,9 +34,25 @@ class BillableMetricResponse(BaseResponseModel): recurring: Optional[bool] aggregation_type: Optional[str] weighted_interval: Optional[str] + expression: Optional[str] field_name: Optional[str] created_at: str filters: BillableMetricFilters active_subscriptions_count: int draft_invoices_count: int plans_count: int + + +class BillableMetricEvaluateExpressionEvent(BaseModel): + code: Optional[str] + timestamp: Optional[Union[str, int]] + properties: Optional[Dict[str, Any]] + + +class BillableMetricEvaluateExpression(BaseModel): + expression: str + event: BillableMetricEvaluateExpressionEvent + + +class BillableMetricEvaluateExpressionResponse(BaseResponseModel): + value: Union[str, int, float] diff --git a/tests/fixtures/billable_metric.json b/tests/fixtures/billable_metric.json index f951bd44..a7ab7c4d 100644 --- a/tests/fixtures/billable_metric.json +++ b/tests/fixtures/billable_metric.json @@ -7,16 +7,13 @@ "aggregation_type": "sum_agg", "weighted_interval": null, "recurring": false, + "expression": "1 + 2", "field_name": "amount_sum", "created_at": "2022-04-29T08:59:51Z", "filters": [ { "key": "country", - "values": [ - "france", - "italy", - "spain" - ] + "values": ["france", "italy", "spain"] } ], "active_subscriptions_count": 0, diff --git a/tests/fixtures/billable_metric_evaluate_expression.json b/tests/fixtures/billable_metric_evaluate_expression.json new file mode 100644 index 00000000..0f43e3c1 --- /dev/null +++ b/tests/fixtures/billable_metric_evaluate_expression.json @@ -0,0 +1,5 @@ +{ + "expression_result": { + "value": "2.0" + } +} diff --git a/tests/fixtures/billable_metric_index.json b/tests/fixtures/billable_metric_index.json index 4fc85f9d..d17df3b3 100644 --- a/tests/fixtures/billable_metric_index.json +++ b/tests/fixtures/billable_metric_index.json @@ -8,6 +8,7 @@ "aggregation_type": "sum_agg", "weighted_interval": null, "recurring": false, + "expression": "1 + 2", "field_name": "amount_sum", "created_at": "2022-04-29T08:59:51Z", "filters": [], @@ -23,6 +24,7 @@ "aggregation_type": "sum_agg", "weighted_interval": null, "recurring": false, + "expression": "1 + 2", "field_name": "amount_sum", "created_at": "2022-04-30T08:59:51Z", "filters": [], diff --git a/tests/test_billable_metric_client.py b/tests/test_billable_metric_client.py index 787f5f00..f83c56a8 100644 --- a/tests/test_billable_metric_client.py +++ b/tests/test_billable_metric_client.py @@ -9,6 +9,8 @@ BillableMetric, BillableMetricFilter, BillableMetricFilters, + BillableMetricEvaluateExpression, + BillableMetricEvaluateExpressionEvent, ) @@ -25,6 +27,17 @@ def billable_metric_object(): ) +def billable_metric_evaluate_expression_object(): + return BillableMetricEvaluateExpression( + expression="round(event.properties.value * event.properties.units)", + event=BillableMetricEvaluateExpressionEvent( + code="event_code", + timestamp=1651240791, + properties={"value": 10, "units": 2}, + ), + ) + + def filters(): return BillableMetricFilters(__root__=[BillableMetricFilter(key="country", values=["france", "italy", "spain"])]) @@ -45,6 +58,14 @@ def mock_collection_response(): return billable_metric_response.read() +def mock_evaluate_expression_response(): + this_dir = os.path.dirname(os.path.abspath(__file__)) + data_path = os.path.join(this_dir, "fixtures/billable_metric_evaluate_expression.json") + + with open(data_path, "rb") as evaluate_expression_response: + return evaluate_expression_response.read() + + def test_valid_create_billable_metric_request(httpx_mock: HTTPXMock): client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") @@ -204,3 +225,16 @@ def test_invalid_find_all_billable_metric_request(httpx_mock: HTTPXMock): with pytest.raises(LagoApiError): client.billable_metrics.find_all() + + +def test_valid_evaluate_billable_metric_expression(httpx_mock: HTTPXMock): + client = Client(api_key="886fe239-927d-4072-ab72-6dd345e8dd0d") + + httpx_mock.add_response( + method="POST", + url="https://api.getlago.com/api/v1/billable_metrics/evaluate_expression", + content=mock_evaluate_expression_response(), + ) + response = client.billable_metrics.evaluate_expression(billable_metric_evaluate_expression_object()) + + assert response.value == "2.0"