Skip to content

Commit 5be1d1b

Browse files
author
Charles Larivier
committed
feat: add Field
1 parent 3d18e54 commit 5be1d1b

File tree

2 files changed

+208
-0
lines changed

2 files changed

+208
-0
lines changed

metabase/resources/field.py

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from __future__ import annotations
2+
3+
from enum import Enum
4+
from typing import Any, Dict, List, Optional
5+
6+
from metabase.resource import GetResource, UpdateResource
7+
from metabase.missing import MISSING
8+
9+
10+
class Field(GetResource, UpdateResource):
11+
ENDPOINT = "/api/field"
12+
13+
id: int
14+
table_id: int
15+
fk_target_id: Optional[int]
16+
parent_id: Optional[int]
17+
18+
name: str
19+
display_name: str
20+
description: str
21+
22+
database_type: str
23+
semantic_type: Field.SemanticType
24+
effective_type: str
25+
base_type: str
26+
27+
dimensions: List[str]
28+
dimension_options: List[str]
29+
default_dimension_option: Optional[int]
30+
31+
database_position: int
32+
custom_position: int
33+
visibility_type: str
34+
points_of_interest: str
35+
has_field_values: Field.FieldValue
36+
37+
active: bool
38+
preview_display: bool
39+
40+
target: Optional[Field]
41+
42+
settings: dict
43+
caveats: str
44+
coercion_strategy: str
45+
46+
updated_at: str
47+
created_at: str
48+
49+
class FieldValue(str, Enum):
50+
none = "none"
51+
auto_list = "auto-list"
52+
list = "list"
53+
search = "search"
54+
55+
class SemanticType(str, Enum):
56+
primary_key = "Type/PK"
57+
foreign_key = "Type/FK"
58+
59+
avatar_url = "type/AvatarURL"
60+
birthdate = "type/Birthdate"
61+
cancelation_date = "type/CancelationDate"
62+
cancelation_time = "type/CancelationTime"
63+
cancelation_timestamp = "type/CancelationTimestamp"
64+
category = "type/Category"
65+
city = "type/City"
66+
comment = "type/Comment"
67+
company = "type/Company"
68+
cost = "type/Cost"
69+
country = "type/Country"
70+
creation_date = "type/CreationDate"
71+
creation_time = "type/CreationTime"
72+
creation_timestamp = "type/CreationTimestamp"
73+
currency = "type/Currency"
74+
deletion_date = "type/DeletionDate"
75+
deletion_time = "type/DeletionTime"
76+
deletion_timestamp = "type/DeletionTimestamp"
77+
description = "type/Description"
78+
discount = "type/Discount"
79+
email = "type/Email"
80+
enum = "type/Enum"
81+
gross_margin = "type/GrossMargin"
82+
image_url = "type/ImageURL"
83+
income = "type/Income"
84+
join_date = "type/JoinDate"
85+
join_time = "type/JoinTime"
86+
join_timestamp = "type/JoinTimestamp"
87+
latitude = "type/Latitude"
88+
longitude = "type/Longitude"
89+
name = "type/Name"
90+
number = "type/Number"
91+
owner = "type/Owner"
92+
price = "type/Price"
93+
product = "type/Product"
94+
quantity = "type/Quantity"
95+
score = "type/Score"
96+
serialized_json = "type/SerializedJSON"
97+
share = "type/Share"
98+
source = "type/Source"
99+
state = "type/State"
100+
subscription = "type/Subscription"
101+
title = "type/Title"
102+
url = "type/URL"
103+
user = "type/User"
104+
zip_code = "type/ZipCode"
105+
106+
class VisibilityType(str, Enum):
107+
details_only = "details-only"
108+
hidden = "hidden"
109+
normal = "normal"
110+
retired = "retired"
111+
sensitive = "sensitive"
112+
113+
@classmethod
114+
def get(cls, id: int) -> Field:
115+
return super(Field, cls).get(id)
116+
117+
def update(
118+
self,
119+
display_name: str = MISSING,
120+
description: str = MISSING,
121+
semantic_type: Field.SemanticType = MISSING,
122+
visibility_type: Field.VisibilityType = MISSING,
123+
fk_target_field_id: int = MISSING,
124+
has_field_values: Field.FieldValue = MISSING,
125+
points_of_interest: str = MISSING,
126+
settings: str = MISSING,
127+
caveats: str = MISSING,
128+
coercion_strategy: str = MISSING,
129+
**kwargs
130+
) -> None:
131+
return super(Field, self).update(
132+
display_name=display_name,
133+
description=description,
134+
semantic_type=semantic_type,
135+
visibility_type=visibility_type,
136+
fk_target_field_id=fk_target_field_id,
137+
has_field_values=has_field_values,
138+
points_of_interest=points_of_interest,
139+
settings=settings,
140+
caveats=caveats,
141+
coercion_strategy=coercion_strategy
142+
)
143+
144+
def related(self) -> Dict[str, Any]:
145+
return self.connection().get(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/related").json()
146+
147+
def discard_values(self):
148+
self.connection().post(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/discard_values")
149+
150+
def rescan_values(self):
151+
self.connection().post(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/rescan_values")

tests/resources/test_field.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
from metabase.resources.field import Field
2+
from tests.helpers import IntegrationTestCase
3+
4+
5+
class FieldTests(IntegrationTestCase):
6+
def setUp(self) -> None:
7+
super(FieldTests, self).setUp()
8+
9+
def test_import(self):
10+
"""Ensure Field can be imported from Metabase."""
11+
from metabase import Field
12+
self.assertIsNotNone(Field())
13+
14+
def test_get(self):
15+
"""Ensure Field.get() returns a Field instance for a given ID."""
16+
field = Field.get(1)
17+
18+
self.assertIsInstance(field, Field)
19+
self.assertEqual(1, field.id)
20+
21+
def test_update(self):
22+
"""Ensure Field.update() updates an existing Field in Metabase."""
23+
field = Field.get(1)
24+
25+
display_name = field.display_name
26+
semantic_type = field.semantic_type
27+
field.update(
28+
display_name="New Name",
29+
semantic_type=Field.SemanticType.zip_code
30+
)
31+
32+
# assert local instance is mutated
33+
self.assertEqual("New Name", field.display_name)
34+
self.assertEqual(Field.SemanticType.zip_code, field.semantic_type)
35+
36+
# assert metabase object is mutated
37+
f = Field.get(field.id)
38+
self.assertEqual("New Name", f.display_name)
39+
self.assertEqual(Field.SemanticType.zip_code, f.semantic_type)
40+
41+
# teardown
42+
f.update(display_name=display_name, semantic_type=semantic_type)
43+
44+
def test_related(self):
45+
"""Ensure Field.related() returns a dict."""
46+
field = Field.get(1)
47+
related = field.related()
48+
49+
self.assertIsInstance(related, dict)
50+
51+
def test_discard_values(self):
52+
# TODO
53+
pass
54+
55+
def test_rescan_values(self):
56+
# TODO
57+
pass

0 commit comments

Comments
 (0)