Skip to content

Commit 3ad3ff5

Browse files
author
Charles Larivier
committed
Merge branch 'feature/card' into develop
2 parents cb3538d + 4c236b4 commit 3ad3ff5

File tree

5 files changed

+313
-38
lines changed

5 files changed

+313
-38
lines changed

README.md

Lines changed: 37 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -111,43 +111,43 @@ df = dataset.to_pandas()
111111

112112
For a full list of endpoints and methods, see [Metabase API](https://www.metabase.com/docs/latest/api-documentation.html).
113113

114-
| Endpoints | Support |
115-
|-----------------------|:----------:|
116-
| Activity ||
117-
| Alert ||
118-
| Automagic dashboards ||
119-
| Card | |
120-
| Collection ||
121-
| Dashboard ||
122-
| Database ||
123-
| Dataset ||
124-
| Email ||
125-
| Embed ||
126-
| Field ||
127-
| Geojson ||
128-
| Ldap ||
129-
| Login history ||
130-
| Metric ||
131-
| Native query snippet ||
132-
| Notify ||
133-
| Permissions ||
134-
| Premium features ||
135-
| Preview embed ||
136-
| Public ||
137-
| Pulse ||
138-
| Revision ||
139-
| Search ||
140-
| Segment ||
141-
| Session ||
142-
| Setting ||
143-
| Setup ||
144-
| Slack ||
145-
| Table ||
146-
| Task ||
147-
| Tiles ||
148-
| Transform ||
149-
| User ||
150-
| Util ||
114+
| Endpoints | Support | Notes |
115+
|-----------------------|:----------:|-------|
116+
| Activity || |
117+
| Alert || |
118+
| Automagic dashboards || |
119+
| Card | ⚠️ | Partial support; list/create/update/archive. Missing additional functionality (i.e. POST /api/card/:card-id:/favorite |
120+
| Collection || |
121+
| Dashboard || |
122+
| Database || |
123+
| Dataset || |
124+
| Email || |
125+
| Embed || |
126+
| Field || |
127+
| Geojson || |
128+
| Ldap || |
129+
| Login history || |
130+
| Metric || |
131+
| Native query snippet || |
132+
| Notify || |
133+
| Permissions || |
134+
| Premium features || |
135+
| Preview embed || |
136+
| Public || |
137+
| Pulse || |
138+
| Revision || |
139+
| Search || |
140+
| Segment || |
141+
| Session || |
142+
| Setting || |
143+
| Setup || |
144+
| Slack || |
145+
| Table || |
146+
| Task || |
147+
| Tiles || |
148+
| Transform || |
149+
| User || |
150+
| Util || |
151151

152152
## Contributing
153153
Contributions are welcome!

src/metabase/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from metabase.metabase import Metabase
2+
from metabase.resources.card import Card
23
from metabase.resources.database import Database
34
from metabase.resources.dataset import Dataset
45
from metabase.resources.field import Field

src/metabase/resource.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ def update(self, **kwargs) -> None:
8181
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}", json=params
8282
)
8383

84-
if response.status_code != 200:
84+
if response.status_code not in (200, 202):
8585
raise HTTPError(response.json())
8686

8787
for k, v in kwargs.items():

src/metabase/resources/card.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
from __future__ import annotations
2+
3+
from typing import List
4+
5+
from metabase.missing import MISSING
6+
from metabase.resource import CreateResource, GetResource, ListResource, UpdateResource
7+
8+
9+
class Card(ListResource, CreateResource, GetResource, UpdateResource):
10+
ENDPOINT = "/api/card"
11+
12+
id: int
13+
table_id: int
14+
database_id: int
15+
collection_id: int
16+
creator_id: int
17+
made_public_by_id: int
18+
public_uuid: str
19+
20+
name: str
21+
description: str
22+
23+
collection: dict # TODO: Collection
24+
collection_position: int
25+
26+
query_type: str
27+
dataset_query: dict # TODO: DatasetQuery
28+
display: str
29+
visualization_settings: dict # TODO: VisualizationSettings
30+
result_metadata: List[dict]
31+
32+
embedding_params: dict
33+
cache_ttl: str
34+
creator: "User"
35+
36+
favorite: bool
37+
archived: bool
38+
enable_embedding: bool
39+
40+
updated_at: str
41+
created_at: str
42+
43+
@classmethod
44+
def list(cls) -> List[Card]:
45+
"""
46+
Get all the Cards. Option filter param f can be used to change the set
47+
of Cards that are returned; default is all, but other options include
48+
mine, fav, database, table, recent, popular, and archived.
49+
50+
See corresponding implementation functions above for the specific behavior
51+
of each filter option. :card_index:.
52+
"""
53+
# TODO: add support for endpoint parameters: f, model_id.
54+
return super(Card, cls).list()
55+
56+
@classmethod
57+
def get(cls, id: int) -> Card:
58+
"""
59+
Get Card with ID.
60+
"""
61+
return super(Card, cls).get(id)
62+
63+
@classmethod
64+
def create(
65+
cls,
66+
name: str,
67+
dataset_query: dict, # TODO: DatasetQuery
68+
visualization_settings: dict, # TODO: VisualizationSettings
69+
display: str,
70+
description: str = None,
71+
collection_id: str = None,
72+
collection_position: int = None,
73+
result_metadata: List[dict] = None,
74+
metadata_checksum: str = None,
75+
cache_ttl: int = None,
76+
**kwargs,
77+
) -> Card:
78+
"""
79+
Create a new Card.
80+
"""
81+
return super(Card, cls).create(
82+
name=name,
83+
dataset_query=dataset_query,
84+
visualization_settings=visualization_settings,
85+
display=display,
86+
description=description,
87+
collection_id=collection_id,
88+
collection_position=collection_position,
89+
result_metadata=result_metadata,
90+
metadata_checksum=metadata_checksum,
91+
cache_ttl=cache_ttl,
92+
**kwargs,
93+
)
94+
95+
def update(
96+
self,
97+
name: str = MISSING,
98+
dataset_query: dict = MISSING, # TODO: DatasetQuery
99+
visualization_settings: dict = MISSING, # TODO: VisualizationSettings
100+
display: str = MISSING,
101+
description: str = MISSING,
102+
collection_id: str = MISSING,
103+
collection_position: int = MISSING,
104+
result_metadata: List[dict] = MISSING,
105+
metadata_checksum: str = MISSING,
106+
archived: bool = MISSING,
107+
enable_embedding: bool = MISSING,
108+
embedding_params: dict = MISSING,
109+
cache_ttl: int = None,
110+
**kwargs,
111+
) -> None:
112+
"""
113+
Update a Card.
114+
"""
115+
return super(Card, self).update(
116+
name=name,
117+
dataset_query=dataset_query,
118+
visualization_settings=visualization_settings,
119+
display=display,
120+
description=description,
121+
collection_id=collection_id,
122+
collection_position=collection_position,
123+
result_metadata=result_metadata,
124+
metadata_checksum=metadata_checksum,
125+
archived=archived,
126+
enable_embedding=enable_embedding,
127+
embedding_params=embedding_params,
128+
cache_ttl=cache_ttl,
129+
)
130+
131+
def archive(self):
132+
"""Archive a Metric."""
133+
return self.update(
134+
archived=True, revision_message="Archived by metabase-python."
135+
)

tests/resources/test_card.py

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
from metabase import Card
2+
from tests.helpers import IntegrationTestCase
3+
4+
5+
class CardTests(IntegrationTestCase):
6+
def setUp(self) -> None:
7+
super(CardTests, self).setUp()
8+
9+
def test_import(self):
10+
"""Ensure Card can be imported from Metabase."""
11+
from metabase import Card
12+
13+
self.assertIsNotNone(Card())
14+
15+
def test_list(self):
16+
"""Ensure Card.list() returns a list of Card instances."""
17+
# fixture
18+
card = Card.create(
19+
name="My Card",
20+
dataset_query={
21+
"type": "query",
22+
"query": {
23+
"source-table": 2,
24+
"aggregation": [["count"]],
25+
"breakout": [["field", 12, {"temporal-unit": "month"}]],
26+
},
27+
"database": 1,
28+
},
29+
visualization_settings={
30+
"graph.dimensions": ["CREATED_AT"],
31+
"graph.metrics": ["count"],
32+
},
33+
display="line",
34+
)
35+
36+
cards = Card.list()
37+
38+
self.assertIsInstance(cards, list)
39+
self.assertTrue(len(cards) > 0)
40+
self.assertTrue(all(isinstance(t, Card) for t in cards))
41+
42+
def test_get(self):
43+
"""Ensure Card.get() returns a Card instance for a given ID."""
44+
card = Card.get(1)
45+
46+
self.assertIsInstance(card, Card)
47+
self.assertEqual(1, card.id)
48+
49+
def test_create(self):
50+
"""Ensure Card.create() creates a Card in Metabase and returns a Card instance."""
51+
card = Card.create(
52+
name="My Card",
53+
dataset_query={
54+
"type": "query",
55+
"query": {
56+
"source-table": 2,
57+
"aggregation": [["count"]],
58+
"breakout": [["field", 12, {"temporal-unit": "month"}]],
59+
},
60+
"database": 1,
61+
},
62+
visualization_settings={
63+
"graph.dimensions": ["CREATED_AT"],
64+
"graph.metrics": ["count"],
65+
},
66+
display="line",
67+
)
68+
69+
self.assertIsInstance(card, Card)
70+
self.assertEqual("My Card", card.name)
71+
self.assertEqual("line", card.display)
72+
self.assertIsInstance(Card.get(card.id), Card) # instance exists in Metabase
73+
74+
# teardown
75+
card.archive()
76+
77+
def test_update(self):
78+
"""Ensure Card.update() updates an existing Card in Metabase."""
79+
# fixture
80+
card = Card.create(
81+
name="My Card",
82+
dataset_query={
83+
"type": "query",
84+
"query": {
85+
"source-table": 2,
86+
"aggregation": [["count"]],
87+
"breakout": [["field", 12, {"temporal-unit": "month"}]],
88+
},
89+
"database": 1,
90+
},
91+
visualization_settings={
92+
"graph.dimensions": ["CREATED_AT"],
93+
"graph.metrics": ["count"],
94+
},
95+
display="line",
96+
)
97+
98+
card = Card.get(1)
99+
100+
name = card.name
101+
card.update(name="New Name")
102+
103+
# assert local instance is mutated
104+
self.assertEqual("New Name", card.name)
105+
106+
# assert metabase object is mutated
107+
t = Card.get(card.id)
108+
self.assertEqual("New Name", t.name)
109+
110+
# teardown
111+
t.archive()
112+
113+
def test_archive(self):
114+
"""Ensure Card.archive() deletes a Card in Metabase."""
115+
# fixture
116+
card = Card.create(
117+
name="My Card",
118+
dataset_query={
119+
"type": "query",
120+
"query": {
121+
"source-table": 2,
122+
"aggregation": [["count"]],
123+
"breakout": [["field", 12, {"temporal-unit": "month"}]],
124+
},
125+
"database": 1,
126+
},
127+
visualization_settings={
128+
"graph.dimensions": ["CREATED_AT"],
129+
"graph.metrics": ["count"],
130+
},
131+
display="line",
132+
)
133+
self.assertIsInstance(card, Card)
134+
135+
card.archive()
136+
self.assertEqual(True, card.archived)
137+
138+
c = Card.get(card.id)
139+
self.assertEqual(True, c.archived)

0 commit comments

Comments
 (0)