Skip to content

Commit 42725e5

Browse files
feat: export products as dataframe
1 parent 652f7c1 commit 42725e5

File tree

7 files changed

+444
-15
lines changed

7 files changed

+444
-15
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from ._dataframe_utilities import convert_products_to_dataframe
12
from ._file_utilities import get_products_linked_to_file
23

34
# flake8: noqa
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
from typing import List
2+
3+
import pandas as pd
4+
from nisystemlink.clients.product.models import Product
5+
from pandas import DataFrame
6+
7+
8+
def convert_products_to_dataframe(products: List[Product]) -> DataFrame:
9+
"""Converts a list of products into a normalized dataframe.
10+
11+
Args:
12+
products (List[Product]): A list of product responses retrieved from the API.
13+
14+
Returns:
15+
DataFrame: A Pandas DataFrame containing the normalized product data.
16+
"""
17+
products_dict_representation = [
18+
product.dict(exclude_unset=True) for product in products
19+
]
20+
normalized_products_dataframe = pd.json_normalize(
21+
products_dict_representation, sep="."
22+
)
23+
normalized_products_dataframe.dropna(axis="columns", how="all", inplace=True)
24+
25+
return normalized_products_dataframe

poetry.lock

Lines changed: 313 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ requests = "^2.28.1"
3838
uplink = "^0.9.7"
3939
pydantic = "^1.10.2"
4040
pyyaml = "^6.0.1"
41+
pandas = "^2.1.0"
4142

4243
[tool.poetry.group.dev.dependencies]
4344
black = ">=22.10,<25.0"

tests/unit/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# flake8: noqa

tests/unit/product/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# flake8: noqa
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
from datetime import datetime
2+
from typing import List
3+
4+
import pandas as pd
5+
import pytest
6+
from nisystemlink.clients.product.models import Product
7+
from nisystemlink.clients.product.utilities import convert_products_to_dataframe
8+
from pandas import DataFrame
9+
10+
11+
@pytest.fixture
12+
def mock_product_data() -> List[Product]:
13+
"""Fixture to return a mock product data."""
14+
product = Product(
15+
id="product_id",
16+
part_number="product_part_number",
17+
name="product_name",
18+
family="product_family",
19+
updated_at=datetime(2024, 2, 2, 14, 22, 4, 625155),
20+
file_ids=["file1", "file2"],
21+
keywords=["keyword1", "keyword2"],
22+
properties={"property1": "property1_value", "property2": "property2_value"},
23+
workspace="product_workspace",
24+
)
25+
26+
return [product]
27+
28+
29+
@pytest.fixture
30+
def expected_products_dataframe(mock_product_data) -> DataFrame:
31+
"""Fixture to return the expected DataFrame based on the mock product data."""
32+
product = mock_product_data[0]
33+
expected_dataframe_structure = {
34+
"id": product.id,
35+
"part_number": product.part_number,
36+
"name": product.name,
37+
"family": product.family,
38+
"updated_at": product.updated_at,
39+
"file_ids": product.file_ids,
40+
"keywords": product.keywords,
41+
"workspace": product.workspace,
42+
"properties.property1": "property1_value",
43+
"properties.property2": "property2_value",
44+
}
45+
46+
return pd.json_normalize(expected_dataframe_structure)
47+
48+
49+
@pytest.fixture
50+
def empty_products_data() -> List:
51+
"""Fixture to return an empty list of products."""
52+
return []
53+
54+
55+
@pytest.mark.enterprise
56+
class TestProductDataframeUtilities:
57+
def test__convert_products_to_dataframe__with_complete_data(
58+
self, mock_product_data, expected_products_dataframe
59+
):
60+
"""Test normal case with valid product data."""
61+
products = mock_product_data
62+
63+
products_dataframe = convert_products_to_dataframe(products)
64+
65+
assert not products_dataframe.empty
66+
assert (
67+
products_dataframe.columns.to_list()
68+
== expected_products_dataframe.columns.to_list()
69+
)
70+
pd.testing.assert_frame_equal(
71+
products_dataframe, expected_products_dataframe, check_dtype=True
72+
)
73+
74+
def test__convert_products_to_dataframe__with_empty_data(self, empty_products_data):
75+
"""Test case when the input products data is empty."""
76+
products = empty_products_data
77+
78+
products_dataframe = convert_products_to_dataframe(products)
79+
80+
assert products_dataframe.empty
81+
82+
def test__convert_products_to_dataframe__with_missing_fields(
83+
self, mock_product_data, expected_products_dataframe
84+
):
85+
"""Test case when some fields in product data are missing."""
86+
products = mock_product_data
87+
del products[0].keywords
88+
del products[0].properties
89+
90+
products_dataframe = convert_products_to_dataframe(products)
91+
expected_products_dataframe = expected_products_dataframe.drop(
92+
columns=["keywords", "properties.property1", "properties.property2"]
93+
)
94+
95+
assert not products_dataframe.empty
96+
assert (
97+
products_dataframe.columns.to_list()
98+
== expected_products_dataframe.columns.to_list()
99+
)
100+
pd.testing.assert_frame_equal(
101+
products_dataframe, expected_products_dataframe, check_dtype=True
102+
)

0 commit comments

Comments
 (0)