Skip to content

Commit e5b6eb7

Browse files
author
Charles Larivier
committed
Merge branch 'feature/refactor-connection-2' into develop
2 parents 0d6c30a + 7e8115c commit e5b6eb7

24 files changed

+277
-255
lines changed

src/metabase/metabase.py

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,9 @@
1-
from weakref import WeakValueDictionary
2-
31
import requests
42

53
from metabase.exceptions import AuthenticationError
64

75

8-
class Singleton(type):
9-
_instances = WeakValueDictionary()
10-
11-
def __call__(cls, *args, **kw):
12-
if cls not in cls._instances:
13-
instance = super(Singleton, cls).__call__(*args, **kw)
14-
cls._instances[cls] = instance
15-
return cls._instances[cls]
16-
17-
18-
class Metabase(metaclass=Singleton):
6+
class Metabase:
197
def __init__(self, host: str, user: str, password: str, token: str = None):
208
self._host = host
219
self.user = user

src/metabase/resource.py

Lines changed: 13 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ class Resource:
1111
ENDPOINT: str
1212
PRIMARY_KEY: str = "id"
1313

14-
def __init__(self, **kwargs):
14+
def __init__(self, _using: Metabase, **kwargs):
1515
self._attributes = []
16+
self._using = _using
1617

1718
for k, v in kwargs.items():
1819
self._attributes.append(k)
@@ -31,42 +32,38 @@ def __repr__(self):
3132
+ ")"
3233
)
3334

34-
@staticmethod
35-
def connection() -> Metabase:
36-
return Metabase()
37-
3835

3936
class ListResource(Resource):
4037
@classmethod
41-
def list(cls):
38+
def list(cls, using: Metabase):
4239
"""List all instances."""
43-
response = cls.connection().get(cls.ENDPOINT)
44-
records = [cls(**record) for record in response.json()]
40+
response = using.get(cls.ENDPOINT)
41+
records = [cls(_using=using, **record) for record in response.json()]
4542
return records
4643

4744

4845
class GetResource(Resource):
4946
@classmethod
50-
def get(cls, id: int):
47+
def get(cls, id: int, using: Metabase):
5148
"""Get a single instance by ID."""
52-
response = cls.connection().get(cls.ENDPOINT + f"/{id}")
49+
response = using.get(cls.ENDPOINT + f"/{id}")
5350

5451
if response.status_code == 404 or response.status_code == 204:
5552
raise NotFoundError(f"{cls.__name__}(id={id}) was not found.")
5653

57-
return cls(**response.json())
54+
return cls(_using=using, **response.json())
5855

5956

6057
class CreateResource(Resource):
6158
@classmethod
62-
def create(cls, **kwargs):
59+
def create(cls, using: Metabase, **kwargs):
6360
"""Create an instance and save it."""
64-
response = cls.connection().post(cls.ENDPOINT, json=kwargs)
61+
response = using.post(cls.ENDPOINT, json=kwargs)
6562

6663
if response.status_code not in (200, 202):
6764
raise HTTPError(response.content.decode())
6865

69-
return cls(**response.json())
66+
return cls(_using=using, **response.json())
7067

7168

7269
class UpdateResource(Resource):
@@ -77,7 +74,7 @@ def update(self, **kwargs) -> None:
7774
ignored from the request.
7875
"""
7976
params = {k: v for k, v in kwargs.items() if v != MISSING}
80-
response = self.connection().put(
77+
response = self._using.put(
8178
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}", json=params
8279
)
8380

@@ -91,7 +88,7 @@ def update(self, **kwargs) -> None:
9188
class DeleteResource(Resource):
9289
def delete(self) -> None:
9390
"""Delete an instance."""
94-
response = self.connection().delete(
91+
response = self._using.delete(
9592
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}"
9693
)
9794

src/metabase/resources/card.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import List
44

5+
from metabase import Metabase
56
from metabase.missing import MISSING
67
from metabase.resource import CreateResource, GetResource, ListResource, UpdateResource
78

@@ -41,7 +42,7 @@ class Card(ListResource, CreateResource, GetResource, UpdateResource):
4142
created_at: str
4243

4344
@classmethod
44-
def list(cls) -> List[Card]:
45+
def list(cls, using: Metabase) -> List[Card]:
4546
"""
4647
Get all the Cards. Option filter param f can be used to change the set
4748
of Cards that are returned; default is all, but other options include
@@ -51,18 +52,19 @@ def list(cls) -> List[Card]:
5152
of each filter option. :card_index:.
5253
"""
5354
# TODO: add support for endpoint parameters: f, model_id.
54-
return super(Card, cls).list()
55+
return super(Card, cls).list(using)
5556

5657
@classmethod
57-
def get(cls, id: int) -> Card:
58+
def get(cls, id: int, using: Metabase) -> Card:
5859
"""
5960
Get Card with ID.
6061
"""
61-
return super(Card, cls).get(id)
62+
return super(Card, cls).get(id, using)
6263

6364
@classmethod
6465
def create(
6566
cls,
67+
using: Metabase,
6668
name: str,
6769
dataset_query: dict, # TODO: DatasetQuery
6870
visualization_settings: dict, # TODO: VisualizationSettings
@@ -79,6 +81,7 @@ def create(
7981
Create a new Card.
8082
"""
8183
return super(Card, cls).create(
84+
using=using,
8285
name=name,
8386
dataset_query=dataset_query,
8487
visualization_settings=visualization_settings,
@@ -129,7 +132,7 @@ def update(
129132
)
130133

131134
def archive(self):
132-
"""Archive a Metric."""
135+
"""Archive a Card."""
133136
return self.update(
134137
archived=True, revision_message="Archived by metabase-python."
135138
)

src/metabase/resources/database.py

Lines changed: 30 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from typing import Any, Dict, List
44

5+
from metabase import Metabase
56
from metabase.missing import MISSING
67
from metabase.resource import (
78
CreateResource,
@@ -47,18 +48,19 @@ class Database(
4748
created_at: str
4849

4950
@classmethod
50-
def list(cls) -> List[Database]:
51-
response = cls.connection().get(cls.ENDPOINT)
52-
records = [cls(**db) for db in response.json().get("data", [])]
51+
def list(cls, using: Metabase) -> List[Database]:
52+
response = using.get(cls.ENDPOINT)
53+
records = [cls(_using=using, **db) for db in response.json().get("data", [])]
5354
return records
5455

5556
@classmethod
56-
def get(cls, id: int) -> Database:
57-
return super(Database, cls).get(id)
57+
def get(cls, id: int, using: Metabase) -> Database:
58+
return super(Database, cls).get(id, using=using)
5859

5960
@classmethod
6061
def create(
6162
cls,
63+
using: Metabase,
6264
name: str,
6365
engine: str,
6466
details: dict,
@@ -75,6 +77,7 @@ def create(
7577
You must be a superuser to do this.
7678
"""
7779
return super(Database, cls).create(
80+
using=using,
7881
name=name,
7982
engine=engine,
8083
details=details,
@@ -128,51 +131,41 @@ def delete(self) -> None:
128131

129132
def fields(self) -> List[Field]:
130133
"""Get a list of all Fields in Database."""
131-
fields = (
132-
self.connection()
133-
.get(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/fields")
134-
.json()
135-
)
136-
return [Field(**payload) for payload in fields]
134+
fields = self._using.get(
135+
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/fields"
136+
).json()
137+
return [Field(_using=self._using, **payload) for payload in fields]
137138

138139
def idfields(self) -> List[Field]:
139140
"""Get a list of all primary key Fields for Database."""
140-
fields = (
141-
self.connection()
142-
.get(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/idfields")
143-
.json()
144-
)
145-
return [Field(**payload) for payload in fields]
141+
fields = self._using.get(
142+
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/idfields"
143+
).json()
144+
return [Field(_using=self._using, **payload) for payload in fields]
146145

147146
def schemas(self) -> List[str]:
148147
"""Returns a list of all the schemas found for the database id."""
149-
return (
150-
self.connection()
151-
.get(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/schemas")
152-
.json()
153-
)
148+
return self._using.get(
149+
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/schemas"
150+
).json()
154151

155152
def tables(self, schema: str) -> List[Table]:
156153
"""Returns a list of Tables for the given Database id and schema."""
157-
tables = (
158-
self.connection()
159-
.get(
160-
self.ENDPOINT
161-
+ f"/{getattr(self, self.PRIMARY_KEY)}"
162-
+ "/schema"
163-
+ f"/{schema}"
164-
)
165-
.json()
166-
)
167-
return [Table(**payload) for payload in tables]
154+
tables = self._using.get(
155+
self.ENDPOINT
156+
+ f"/{getattr(self, self.PRIMARY_KEY)}"
157+
+ "/schema"
158+
+ f"/{schema}"
159+
).json()
160+
return [Table(_using=self._using, **payload) for payload in tables]
168161

169162
def discard_values(self):
170163
"""
171164
Discards all saved field values for this Database.
172165
173166
You must be a superuser to do this.
174167
"""
175-
return self.connection().post(
168+
return self._using.post(
176169
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/discard_values"
177170
)
178171

@@ -182,13 +175,13 @@ def rescan_values(self):
182175
183176
You must be a superuser to do this.
184177
"""
185-
return self.connection().post(
178+
return self._using.post(
186179
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/rescan_values"
187180
)
188181

189182
def sync(self):
190183
"""Update the metadata for this Database. This happens asynchronously."""
191-
return self.connection().post(
184+
return self._using.post(
192185
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/sync"
193186
)
194187

@@ -198,6 +191,6 @@ def sync_schema(self):
198191
199192
You must be a superuser to do this.
200193
"""
201-
return self.connection().post(
194+
return self._using.post(
202195
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/sync_schema"
203196
)

src/metabase/resources/dataset.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import pandas as pd
66

7+
from metabase import Metabase
78
from metabase.resource import CreateResource, Resource
89

910

@@ -37,10 +38,14 @@ class Dataset(CreateResource):
3738
average_execution_time: int = None
3839

3940
@classmethod
40-
def create(cls, database: int, type: str, query: dict, **kwargs) -> Dataset:
41+
def create(
42+
cls, using: Metabase, database: int, type: str, query: dict, **kwargs
43+
) -> Dataset:
4144
"""Execute a query and retrieve the results in the usual format."""
42-
dataset = super(Dataset, cls).create(database=database, type=type, query=query)
43-
dataset.data = Data(**dataset.data)
45+
dataset = super(Dataset, cls).create(
46+
using=using, database=database, type=type, query=query
47+
)
48+
dataset.data = Data(_using=using, **dataset.data)
4449
return dataset
4550

4651
def to_pandas(self) -> pd.DataFrame:

src/metabase/resources/field.py

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from enum import Enum
44
from typing import Any, Dict, List, Optional
55

6+
from metabase import Metabase
67
from metabase.missing import MISSING
78
from metabase.resource import GetResource, UpdateResource
89

@@ -111,9 +112,9 @@ class VisibilityType(str, Enum):
111112
sensitive = "sensitive"
112113

113114
@classmethod
114-
def get(cls, id: int) -> Field:
115+
def get(cls, id: int, using: Metabase) -> Field:
115116
"""Get Field with ID."""
116-
return super(Field, cls).get(id)
117+
return super(Field, cls).get(id, using=using)
117118

118119
def update(
119120
self,
@@ -145,11 +146,9 @@ def update(
145146

146147
def related(self) -> Dict[str, Any]:
147148
"""Return related entities."""
148-
return (
149-
self.connection()
150-
.get(self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/related")
151-
.json()
152-
)
149+
return self._using.get(
150+
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/related"
151+
).json()
153152

154153
def discard_values(self):
155154
"""
@@ -159,7 +158,7 @@ def discard_values(self):
159158
160159
You must be a superuser to do this.
161160
"""
162-
return self.connection().post(
161+
return self._using.post(
163162
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/discard_values"
164163
)
165164

@@ -170,6 +169,6 @@ def rescan_values(self):
170169
171170
You must be a superuser to do this.
172171
"""
173-
return self.connection().post(
172+
return self._using.post(
174173
self.ENDPOINT + f"/{getattr(self, self.PRIMARY_KEY)}" + "/rescan_values"
175174
)

0 commit comments

Comments
 (0)