Skip to content

Commit 91daf0a

Browse files
committed
bugfix: ViewField label is no longer required
1 parent 26a6553 commit 91daf0a

File tree

3 files changed

+108
-22
lines changed

3 files changed

+108
-22
lines changed

docs/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## 2025-05-09 "ViewField Label Bugfix" - version 1.12.1
4+
5+
### Fixed
6+
- Made `label` field optional in `ViewField` model to prevent errors when processing views with unlabeled fields
7+
- Added test coverage to ensure views with missing labels are handled correctly
8+
9+
### Technical Details
10+
This bugfix addresses an issue where the `ViewField` model in the metadata views module required a label for each field. In the Iconik API, the label field is actually optional, and this update ensures the SDK correctly handles fields without labels, preventing potential errors when processing API responses.
11+
312
## 2025-05-08 "Metadata Field API Improvements & Bugfixes" - version 1.12.0
413

514
### Fixed

pythonik/models/metadata/views.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,14 +25,16 @@ def __getitem__(self, item):
2525

2626
class ViewOption(BaseModel):
2727
"""Option for a view field."""
28+
2829
label: str
2930
value: str
3031

3132

3233
class ViewField(BaseModel):
3334
"""Field configuration for a view."""
35+
3436
name: str
35-
label: str
37+
label: Optional[str] = None
3638
auto_set: Optional[bool] = False
3739
date_created: Optional[str] = None
3840
date_modified: Optional[str] = None
@@ -57,13 +59,15 @@ class ViewField(BaseModel):
5759

5860
class CreateViewRequest(BaseModel):
5961
"""Request model for creating a view."""
62+
6063
name: str
6164
description: Optional[str] = None
6265
view_fields: List[ViewField]
6366

6467

6568
class UpdateViewRequest(BaseModel):
6669
"""Request model for updating a view."""
70+
6771
name: Optional[str] = None
6872
description: Optional[str] = None
6973
view_fields: Optional[List[ViewField]] = None

pythonik/tests/test_metadata.py

Lines changed: 94 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -688,6 +688,79 @@ def test_get_views_empty():
688688
assert result.data.per_page == 10
689689

690690

691+
def test_get_views_with_missing_labels():
692+
"""Test getting views where some ViewFields have no labels."""
693+
with requests_mock.Mocker() as m:
694+
app_id = str(uuid.uuid4())
695+
auth_token = str(uuid.uuid4())
696+
view_id = str(uuid.uuid4())
697+
698+
# Create expected response with a mix of labeled and unlabeled fields
699+
view = ViewResponse(
700+
id=view_id,
701+
name="Test View",
702+
description="A test view with mixed label fields",
703+
date_created="2024-12-20T18:40:03.279Z",
704+
date_modified="2024-12-20T18:40:03.279Z",
705+
view_fields=[
706+
# Field with a label
707+
ViewField(
708+
name="field1",
709+
label="Field 1",
710+
required=True,
711+
field_type="string",
712+
),
713+
# Field without a label
714+
ViewField(
715+
name="field2",
716+
required=False,
717+
field_type="string",
718+
),
719+
# Another field without a label but with options
720+
ViewField(
721+
name="field3",
722+
field_type="select",
723+
options=[
724+
ViewOption(label="Option 1", value="opt1"),
725+
ViewOption(label="Option 2", value="opt2"),
726+
],
727+
),
728+
],
729+
)
730+
response = ViewListResponse(objects=[view], page=1, pages=1, per_page=10)
731+
732+
# Mock the API call
733+
mock_address = MetadataSpec.gen_url(VIEWS_BASE)
734+
m.get(mock_address, json=response.model_dump())
735+
736+
# Make the request
737+
client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3)
738+
result = client.metadata().get_views()
739+
740+
# Verify response
741+
assert result.response.ok
742+
assert len(result.data.objects) == 1
743+
assert result.data.objects[0].id == view_id
744+
assert result.data.objects[0].name == view.name
745+
746+
# Verify the fields were processed correctly
747+
view_fields = result.data.objects[0].view_fields
748+
assert len(view_fields) == 3
749+
750+
# Field with label
751+
assert view_fields[0].name == "field1"
752+
assert view_fields[0].label == "Field 1"
753+
754+
# Field without label should have None as the label value
755+
assert view_fields[1].name == "field2"
756+
assert view_fields[1].label is None
757+
758+
# Another field without label but with options
759+
assert view_fields[2].name == "field3"
760+
assert view_fields[2].label is None
761+
assert len(view_fields[2].options) == 2
762+
763+
691764
def test_update_view():
692765
"""Test updating a view."""
693766
with requests_mock.Mocker() as m:
@@ -1574,7 +1647,9 @@ def test_create_field_for_all_types(requests_mock, field_type_enum: IconikFieldT
15741647
assert response.response.ok
15751648
assert response.response.status_code == 201
15761649
assert response.data is not None
1577-
assert isinstance(response.data, FieldResponse), "Response data should be a FieldResponse instance"
1650+
assert isinstance(response.data, FieldResponse), (
1651+
"Response data should be a FieldResponse instance"
1652+
)
15781653
assert response.data.name == field_name
15791654
assert response.data.label == field_label
15801655
assert (
@@ -1655,20 +1730,21 @@ def test_create_field_with_unknown_type_raises_validation_error(requests_mock):
16551730
# Backward compatibility alias tests
16561731
# ---------------------------------
16571732

1733+
16581734
def test_create_metadata_field_alias(requests_mock):
16591735
"""Test that the deprecated create_metadata_field method works as an alias for create_field."""
16601736
app_id = str(uuid.uuid4())
16611737
auth_token = str(uuid.uuid4())
16621738
client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3)
16631739
spec_instance = client.metadata()
1664-
1740+
16651741
field_name = "test_alias_field"
16661742
field_create_payload = FieldCreate(
16671743
name=field_name,
16681744
label="Test Alias Field",
16691745
field_type="string",
16701746
)
1671-
1747+
16721748
# Mock the API response
16731749
expected_response = FieldResponse(
16741750
name=field_name,
@@ -1687,18 +1763,18 @@ def test_create_metadata_field_alias(requests_mock):
16871763
sortable=False,
16881764
use_as_facet=False,
16891765
)
1690-
1766+
16911767
# Mock the API endpoint
16921768
mock_address = spec_instance.gen_url(FIELDS_BASE_PATH)
16931769
requests_mock.post(
16941770
mock_address,
16951771
json=json.loads(expected_response.model_dump_json()),
16961772
status_code=201,
16971773
)
1698-
1774+
16991775
# Call the deprecated method
17001776
result = spec_instance.create_metadata_field(field_create_payload)
1701-
1777+
17021778
# Verify results
17031779
assert result.response.ok
17041780
assert result.response.status_code == 201
@@ -1713,17 +1789,16 @@ def test_update_metadata_field_alias(requests_mock):
17131789
app_id = str(uuid.uuid4())
17141790
auth_token = str(uuid.uuid4())
17151791
client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3)
1716-
1792+
17171793
field_name = "test_alias_update_field"
17181794
field_update_payload = FieldUpdate(
1719-
label="Updated Alias Test Field",
1720-
description="Updated via the alias method."
1795+
label="Updated Alias Test Field", description="Updated via the alias method."
17211796
)
1722-
1797+
17231798
# Mock the API response
17241799
expected_response = FieldResponse(
17251800
name=field_name,
1726-
label="Updated Alias Test Field",
1801+
label="Updated Alias Test Field",
17271802
description="Updated via the alias method.",
17281803
field_type="string",
17291804
date_created="2024-01-01T00:00:00Z",
@@ -1739,7 +1814,7 @@ def test_update_metadata_field_alias(requests_mock):
17391814
sortable=False,
17401815
use_as_facet=False,
17411816
)
1742-
1817+
17431818
# Mock the API endpoint
17441819
mock_address = MetadataSpec.gen_url(
17451820
FIELD_BY_NAME_PATH.format(field_name=field_name)
@@ -1749,12 +1824,10 @@ def test_update_metadata_field_alias(requests_mock):
17491824
json=json.loads(expected_response.model_dump_json()),
17501825
status_code=200,
17511826
)
1752-
1827+
17531828
# Call the deprecated method
1754-
result = client.metadata().update_metadata_field(
1755-
field_name, field_update_payload
1756-
)
1757-
1829+
result = client.metadata().update_metadata_field(field_name, field_update_payload)
1830+
17581831
# Verify results
17591832
assert result.response.ok
17601833
assert result.response.status_code == 200
@@ -1769,18 +1842,18 @@ def test_delete_metadata_field_alias(requests_mock):
17691842
app_id = str(uuid.uuid4())
17701843
auth_token = str(uuid.uuid4())
17711844
client = PythonikClient(app_id=app_id, auth_token=auth_token, timeout=3)
1772-
1845+
17731846
field_name = "field_to_delete_via_alias"
1774-
1847+
17751848
# Mock the API endpoint
17761849
mock_address = MetadataSpec.gen_url(
17771850
FIELD_BY_NAME_PATH.format(field_name=field_name)
17781851
)
17791852
requests_mock.delete(mock_address, status_code=204)
1780-
1853+
17811854
# Call the deprecated method
17821855
result = client.metadata().delete_metadata_field(field_name)
1783-
1856+
17841857
# Verify results
17851858
assert result.response.ok
17861859
assert result.response.status_code == 204

0 commit comments

Comments
 (0)