MindsDB 25.3.4.0__py3-none-any.whl → 25.3.4.2__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of MindsDB might be problematic. Click here for more details.

Files changed (32) hide show
  1. mindsdb/__about__.py +2 -2
  2. mindsdb/api/executor/datahub/datanodes/integration_datanode.py +5 -2
  3. mindsdb/api/executor/datahub/datanodes/system_tables.py +131 -138
  4. mindsdb/api/mysql/mysql_proxy/libs/constants/mysql.py +74 -0
  5. mindsdb/integrations/handlers/confluence_handler/confluence_api_client.py +176 -0
  6. mindsdb/integrations/handlers/confluence_handler/confluence_handler.py +54 -59
  7. mindsdb/integrations/handlers/confluence_handler/confluence_tables.py +753 -0
  8. mindsdb/integrations/handlers/confluence_handler/connection_args.py +8 -8
  9. mindsdb/integrations/handlers/langchain_handler/requirements.txt +1 -1
  10. mindsdb/integrations/handlers/lightwood_handler/requirements.txt +3 -3
  11. mindsdb/integrations/handlers/litellm_handler/requirements.txt +1 -1
  12. mindsdb/integrations/handlers/llama_index_handler/requirements.txt +1 -1
  13. mindsdb/integrations/handlers/ms_teams_handler/ms_graph_api_teams_client.py +278 -55
  14. mindsdb/integrations/handlers/ms_teams_handler/ms_teams_handler.py +52 -21
  15. mindsdb/integrations/handlers/ms_teams_handler/ms_teams_tables.py +6 -29
  16. mindsdb/integrations/handlers/mssql_handler/mssql_handler.py +37 -1
  17. mindsdb/integrations/handlers/mysql_handler/mysql_handler.py +30 -1
  18. mindsdb/integrations/handlers/oracle_handler/oracle_handler.py +53 -5
  19. mindsdb/integrations/handlers/postgres_handler/postgres_handler.py +37 -1
  20. mindsdb/integrations/handlers/ray_serve_handler/ray_serve_handler.py +18 -16
  21. mindsdb/integrations/handlers/snowflake_handler/snowflake_handler.py +68 -2
  22. mindsdb/integrations/utilities/handlers/auth_utilities/__init__.py +1 -1
  23. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/__init__.py +1 -1
  24. mindsdb/integrations/utilities/handlers/auth_utilities/microsoft/ms_graph_api_auth_utilities.py +97 -18
  25. mindsdb/utilities/render/sqlalchemy_render.py +30 -6
  26. {mindsdb-25.3.4.0.dist-info → mindsdb-25.3.4.2.dist-info}/METADATA +226 -231
  27. {mindsdb-25.3.4.0.dist-info → mindsdb-25.3.4.2.dist-info}/RECORD +30 -30
  28. {mindsdb-25.3.4.0.dist-info → mindsdb-25.3.4.2.dist-info}/WHEEL +1 -1
  29. mindsdb/integrations/handlers/confluence_handler/confluence_table.py +0 -193
  30. mindsdb/integrations/handlers/confluence_handler/requirements.txt +0 -1
  31. {mindsdb-25.3.4.0.dist-info → mindsdb-25.3.4.2.dist-info}/licenses/LICENSE +0 -0
  32. {mindsdb-25.3.4.0.dist-info → mindsdb-25.3.4.2.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py CHANGED
@@ -1,10 +1,10 @@
1
1
  __title__ = 'MindsDB'
2
2
  __package_name__ = 'mindsdb'
3
- __version__ = '25.3.4.0'
3
+ __version__ = '25.3.4.2'
4
4
  __description__ = "MindsDB's AI SQL Server enables developers to build AI tools that need access to real-time data to perform their tasks"
5
5
  __email__ = "jorge@mindsdb.com"
6
6
  __author__ = 'MindsDB Inc'
7
7
  __github__ = 'https://github.com/mindsdb/mindsdb'
8
8
  __pypi__ = 'https://pypi.org/project/mindsdb/'
9
- __license__ = 'SSPL v1'
9
+ __license__ = 'Elastic License 2.0'
10
10
  __copyright__ = 'Copyright(c) 2018 MindsDB, Inc'
@@ -64,6 +64,7 @@ class IntegrationDataNode(DataNode):
64
64
  df = response.data_frame
65
65
  # case independent
66
66
  columns = [str(c).lower() for c in df.columns]
67
+ df.columns = columns
67
68
 
68
69
  col_name = None
69
70
  # looking for specific column names
@@ -78,8 +79,10 @@ class IntegrationDataNode(DataNode):
78
79
  names = df[df.columns[col_name]]
79
80
 
80
81
  # type
81
- if 'type' in columns:
82
- types = df[df.columns[columns.index('type')]]
82
+ if 'mysql_data_type' in columns:
83
+ types = df['mysql_data_type']
84
+ elif 'type' in columns:
85
+ types = df['type']
83
86
  else:
84
87
  types = [None] * len(names)
85
88
 
@@ -1,14 +1,17 @@
1
+ from typing import Optional, Literal
2
+ from dataclasses import dataclass, astuple, fields
1
3
 
2
4
  import pandas as pd
3
5
  from mindsdb_sql_parser.ast.base import ASTNode
4
- from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
5
6
 
7
+ from mindsdb.utilities import log
8
+ from mindsdb.utilities.config import config
9
+ from mindsdb.integrations.utilities.sql_utils import extract_comparison_conditions
10
+ from mindsdb.api.mysql.mysql_proxy.libs.constants.mysql import MYSQL_DATA_TYPE
6
11
  from mindsdb.api.executor.datahub.classes.tables_row import (
7
12
  TABLES_ROW_TYPE,
8
13
  TablesRow,
9
14
  )
10
- from mindsdb.utilities import log
11
- from mindsdb.utilities.config import config
12
15
 
13
16
  logger = log.getLogger(__name__)
14
17
 
@@ -162,132 +165,108 @@ class TablesTable(Table):
162
165
  return df
163
166
 
164
167
 
165
- class ColumnsTable(Table):
168
+ @dataclass
169
+ class ColumnsTableRow:
170
+ """Represents a row in the COLUMNS table.
171
+ Fields description: https://dev.mysql.com/doc/refman/8.4/en/information-schema-columns-table.html
172
+ NOTE: attrs order matter, don't change it.
173
+ """
174
+ TABLE_CATALOG: Literal['def'] = 'def'
175
+ TABLE_SCHEMA: Optional[str] = None
176
+ TABLE_NAME: Optional[str] = None
177
+ COLUMN_NAME: Optional[str] = None
178
+ ORDINAL_POSITION: int = 0
179
+ COLUMN_DEFAULT: Optional[str] = None
180
+ IS_NULLABLE: Literal['YES', 'NO'] = 'YES'
181
+ DATA_TYPE: str = MYSQL_DATA_TYPE.VARCHAR.value
182
+ CHARACTER_MAXIMUM_LENGTH: Optional[int] = None
183
+ CHARACTER_OCTET_LENGTH: Optional[int] = None
184
+ NUMERIC_PRECISION: Optional[int] = None
185
+ NUMERIC_SCALE: Optional[int] = None
186
+ DATETIME_PRECISION: Optional[int] = None
187
+ CHARACTER_SET_NAME: Optional[str] = None
188
+ COLLATION_NAME: Optional[str] = None
189
+ COLUMN_TYPE: Optional[str] = None
190
+ COLUMN_KEY: Optional[str] = None
191
+ EXTRA: Optional[str] = None
192
+ PRIVILEGES: str = 'select'
193
+ COLUMN_COMMENT: Optional[str] = None
194
+ GENERATION_EXPRESSION: Optional[str] = None
195
+
196
+ def __post_init__(self):
197
+ # region check mandatory fields
198
+ mandatory_fields = ['TABLE_SCHEMA', 'TABLE_NAME', 'COLUMN_NAME']
199
+ if any(getattr(self, field_name) is None for field_name in mandatory_fields):
200
+ raise ValueError('One of mandatory fields is missed when creating ColumnsTableRow')
201
+ # endregion
202
+
203
+ # region set default values depend on type
204
+ defaults = {
205
+ 'COLUMN_TYPE': self.DATA_TYPE
206
+ }
207
+ if MYSQL_DATA_TYPE(self.DATA_TYPE) in (
208
+ MYSQL_DATA_TYPE.TIMESTAMP,
209
+ MYSQL_DATA_TYPE.DATETIME,
210
+ MYSQL_DATA_TYPE.DATE
211
+ ):
212
+ defaults = {
213
+ 'DATETIME_PRECISION': 0,
214
+ 'COLUMN_TYPE': self.DATA_TYPE
215
+ }
216
+ elif MYSQL_DATA_TYPE(self.DATA_TYPE) in (
217
+ MYSQL_DATA_TYPE.FLOAT,
218
+ MYSQL_DATA_TYPE.DOUBLE,
219
+ MYSQL_DATA_TYPE.DECIMAL
220
+ ):
221
+ defaults = {
222
+ 'NUMERIC_PRECISION': 12,
223
+ 'NUMERIC_SCALE': 0,
224
+ 'COLUMN_TYPE': self.DATA_TYPE
225
+ }
226
+ elif MYSQL_DATA_TYPE(self.DATA_TYPE) in (
227
+ MYSQL_DATA_TYPE.TINYINT,
228
+ MYSQL_DATA_TYPE.SMALLINT,
229
+ MYSQL_DATA_TYPE.MEDIUMINT,
230
+ MYSQL_DATA_TYPE.INT,
231
+ MYSQL_DATA_TYPE.BIGINT
232
+ ):
233
+ defaults = {
234
+ 'NUMERIC_PRECISION': 20,
235
+ 'NUMERIC_SCALE': 0,
236
+ 'COLUMN_TYPE': self.DATA_TYPE
237
+ }
238
+ elif MYSQL_DATA_TYPE(self.DATA_TYPE) is MYSQL_DATA_TYPE.VARCHAR:
239
+ defaults = {
240
+ 'CHARACTER_MAXIMUM_LENGTH': 1024,
241
+ 'CHARACTER_OCTET_LENGTH': 3072,
242
+ 'CHARACTER_SET_NAME': 'utf8',
243
+ 'COLLATION_NAME': 'utf8_bin',
244
+ 'COLUMN_TYPE': 'varchar(1024)'
245
+ }
246
+ else:
247
+ # show as MYSQL_DATA_TYPE.TEXT:
248
+ defaults = {
249
+ 'CHARACTER_MAXIMUM_LENGTH': 65535, # from https://bugs.mysql.com/bug.php?id=90685
250
+ 'CHARACTER_OCTET_LENGTH': 65535, #
251
+ 'CHARACTER_SET_NAME': 'utf8',
252
+ 'COLLATION_NAME': 'utf8_bin',
253
+ 'COLUMN_TYPE': 'text'
254
+ }
255
+
256
+ for key, value in defaults.items():
257
+ setattr(self, key, value)
166
258
 
259
+ self.DATA_TYPE = self.DATA_TYPE.lower()
260
+ self.COLUMN_TYPE = self.COLUMN_TYPE.lower()
261
+ # endregion
262
+
263
+
264
+ class ColumnsTable(Table):
167
265
  name = 'COLUMNS'
168
- columns = [
169
- "TABLE_CATALOG",
170
- "TABLE_SCHEMA",
171
- "TABLE_NAME",
172
- "COLUMN_NAME",
173
- "ORDINAL_POSITION",
174
- "COLUMN_DEFAULT",
175
- "IS_NULLABLE",
176
- "DATA_TYPE",
177
- "CHARACTER_MAXIMUM_LENGTH",
178
- "CHARACTER_OCTET_LENGTH",
179
- "NUMERIC_PRECISION",
180
- "NUMERIC_SCALE",
181
- "DATETIME_PRECISION",
182
- "CHARACTER_SET_NAME",
183
- "COLLATION_NAME",
184
- "COLUMN_TYPE",
185
- "COLUMN_KEY",
186
- "EXTRA",
187
- "PRIVILEGES",
188
- "COLUMN_COMMENT",
189
- "GENERATION_EXPRESSION",
190
- ]
266
+ columns = [field.name for field in fields(ColumnsTableRow)]
191
267
 
192
268
  @classmethod
193
269
  def get_data(cls, inf_schema=None, query: ASTNode = None, **kwargs):
194
-
195
- # NOTE there is a lot of types in mysql, but listed below should be enough for our purposes
196
- row_templates = {
197
- "text": [
198
- "def",
199
- "SCHEMA_NAME",
200
- "TABLE_NAME",
201
- "COLUMN_NAME",
202
- "COL_INDEX",
203
- None,
204
- "YES",
205
- "varchar",
206
- 1024,
207
- 3072,
208
- None,
209
- None,
210
- None,
211
- "utf8",
212
- "utf8_bin",
213
- "varchar(1024)",
214
- None,
215
- None,
216
- "select",
217
- None,
218
- None,
219
- ],
220
- "timestamp": [
221
- "def",
222
- "SCHEMA_NAME",
223
- "TABLE_NAME",
224
- "COLUMN_NAME",
225
- "COL_INDEX",
226
- "CURRENT_TIMESTAMP",
227
- "YES",
228
- "timestamp",
229
- None,
230
- None,
231
- None,
232
- None,
233
- 0,
234
- None,
235
- None,
236
- "timestamp",
237
- None,
238
- None,
239
- "select",
240
- None,
241
- None,
242
- ],
243
- "bigint": [
244
- "def",
245
- "SCHEMA_NAME",
246
- "TABLE_NAME",
247
- "COLUMN_NAME",
248
- "COL_INDEX",
249
- None,
250
- "YES",
251
- "bigint",
252
- None,
253
- None,
254
- 20,
255
- 0,
256
- None,
257
- None,
258
- None,
259
- "bigint unsigned",
260
- None,
261
- None,
262
- "select",
263
- None,
264
- None,
265
- ],
266
- "float": [
267
- "def",
268
- "SCHEMA_NAME",
269
- "TABLE_NAME",
270
- "COLUMN_NAME",
271
- "COL_INDEX",
272
- None,
273
- "YES",
274
- "float",
275
- None,
276
- None,
277
- 12,
278
- 0,
279
- None,
280
- None,
281
- None,
282
- "float",
283
- None,
284
- None,
285
- "select",
286
- None,
287
- None,
288
- ],
289
- }
290
-
291
270
  result = []
292
271
 
293
272
  databases, tables_names = _get_scope(query)
@@ -320,20 +299,34 @@ class ColumnsTable(Table):
320
299
  for i, column in enumerate(table_columns):
321
300
  column_name = column['name']
322
301
  column_type = column.get('type', 'text')
323
- if column_type in ('double precision', 'real', 'numeric'):
324
- column_type = 'float'
325
- elif column_type in ('integer', 'smallint', 'int'):
326
- column_type = 'bigint'
327
- elif column_type in ('timestamp without time zone', 'timestamp with time zone', 'date'):
328
- column_type = 'timestamp'
329
- elif column_type not in row_templates:
330
- column_type = 'text'
331
- result_row = row_templates[column_type].copy()
332
- result_row[1] = db_name
333
- result_row[2] = table_name
334
- result_row[3] = column_name
335
- result_row[4] = i
336
- result.append(result_row)
302
+
303
+ # region infer type
304
+ if isinstance(column_type, MYSQL_DATA_TYPE) is False:
305
+ if column_type in ('double precision', 'real', 'numeric', 'float'):
306
+ column_type = MYSQL_DATA_TYPE.FLOAT
307
+ elif column_type in ('integer', 'smallint', 'int', 'bigint'):
308
+ column_type = MYSQL_DATA_TYPE.BIGINT
309
+ elif column_type in (
310
+ 'timestamp without time zone',
311
+ 'timestamp with time zone',
312
+ 'date', 'timestamp'
313
+ ):
314
+ column_type = MYSQL_DATA_TYPE.DATETIME
315
+ else:
316
+ column_type = MYSQL_DATA_TYPE.VARCHAR
317
+ # endregion
318
+
319
+ column_row = astuple(
320
+ ColumnsTableRow(
321
+ TABLE_SCHEMA=db_name,
322
+ TABLE_NAME=table_name,
323
+ COLUMN_NAME=column_name,
324
+ DATA_TYPE=column_type.value,
325
+ ORDINAL_POSITION=i
326
+ )
327
+ )
328
+
329
+ result.append(column_row)
337
330
 
338
331
  df = pd.DataFrame(result, columns=cls.columns)
339
332
  return df
@@ -8,6 +8,7 @@
8
8
  * permission of MindsDB Inc
9
9
  *******************************************************
10
10
  """
11
+ import enum
11
12
 
12
13
  # CAPABILITIES
13
14
  # As defined in : https://dev.mysql.com/doc/dev/mysql-server/8.0.0/group__group__cs__capabilities__flags.html
@@ -101,6 +102,8 @@ COMMANDS = COMMANDS()
101
102
 
102
103
 
103
104
  # FIELD TYPES
105
+ # https://dev.mysql.com/doc/dev/mysql-server/latest/field__types_8h_source.html
106
+ # https://mariadb.com/kb/en/result-set-packets/
104
107
  class TYPES(object):
105
108
  __slots__ = ()
106
109
  MYSQL_TYPE_DECIMAL = 0
@@ -123,6 +126,11 @@ class TYPES(object):
123
126
  MYSQL_TYPE_TIMESTAMP2 = 17
124
127
  MYSQL_TYPE_DATETIME2 = 18
125
128
  MYSQL_TYPE_TIME2 = 19
129
+ MYSQL_TYPE_TYPED_ARRAY = 20
130
+ MYSQL_TYPE_VECTOR = 242,
131
+ MYSQL_TYPE_INVALID = 243,
132
+ MYSQL_TYPE_BOOL = 244,
133
+ MYSQL_TYPE_JSON = 245,
126
134
  MYSQL_TYPE_NEWDECIMAL = 246
127
135
  MYSQL_TYPE_ENUM = 247
128
136
  MYSQL_TYPE_SET = 248
@@ -135,9 +143,75 @@ class TYPES(object):
135
143
  MYSQL_TYPE_GEOMETRY = 255
136
144
 
137
145
 
146
+ C_TYPES = TYPES()
138
147
  TYPES = TYPES()
139
148
 
140
149
 
150
+ class MYSQL_DATA_TYPE(enum.Enum):
151
+ TINYINT = 'TINYINT'
152
+ SMALLINT = 'SMALLINT'
153
+ MEDIUMINT = 'MEDIUMINT'
154
+ INT = 'INT'
155
+ BIGINT = 'BIGINT'
156
+ FLOAT = 'FLOAT'
157
+ DOUBLE = 'DOUBLE'
158
+ DECIMAL = 'DECIMAL'
159
+ YEAR = 'YEAR'
160
+ TIME = 'TIME'
161
+ DATE = 'DATE'
162
+ DATETIME = 'DATETIME'
163
+ TIMESTAMP = 'TIMESTAMP'
164
+ CHAR = 'CHAR'
165
+ BINARY = 'BINARY'
166
+ VARCHAR = 'VARCHAR'
167
+ VARBINARY = 'VARBINARY'
168
+ TINYBLOB = 'TINYBLOB'
169
+ TINYTEXT = 'TINYTEXT'
170
+ BLOB = 'BLOB'
171
+ TEXT = 'TEXT'
172
+ MEDIUMBLOB = 'MEDIUMBLOB'
173
+ MEDIUMTEXT = 'MEDIUMTEXT'
174
+ LONGBLOB = 'LONGBLOB'
175
+ LONGTEXT = 'LONGTEXT'
176
+ BIT = 'BIT'
177
+ BOOL = 'BOOL'
178
+ BOOLEAN = 'BOOLEAN'
179
+
180
+
181
+ # Map between data types and C types
182
+ # https://dev.mysql.com/doc/c-api/8.0/en/c-api-prepared-statement-type-codes.html
183
+ DATA_C_TYPE_MAP = {
184
+ MYSQL_DATA_TYPE.TINYINT: C_TYPES.MYSQL_TYPE_TINY,
185
+ MYSQL_DATA_TYPE.SMALLINT: C_TYPES.MYSQL_TYPE_SHORT,
186
+ MYSQL_DATA_TYPE.MEDIUMINT: C_TYPES.MYSQL_TYPE_INT24,
187
+ MYSQL_DATA_TYPE.INT: C_TYPES.MYSQL_TYPE_LONG,
188
+ MYSQL_DATA_TYPE.BIGINT: C_TYPES.MYSQL_TYPE_LONGLONG,
189
+ MYSQL_DATA_TYPE.FLOAT: C_TYPES.MYSQL_TYPE_FLOAT,
190
+ MYSQL_DATA_TYPE.DOUBLE: C_TYPES.MYSQL_TYPE_DOUBLE,
191
+ MYSQL_DATA_TYPE.DECIMAL: C_TYPES.MYSQL_TYPE_NEWDECIMAL,
192
+ MYSQL_DATA_TYPE.YEAR: C_TYPES.MYSQL_TYPE_SHORT,
193
+ MYSQL_DATA_TYPE.TIME: C_TYPES.MYSQL_TYPE_TIME,
194
+ MYSQL_DATA_TYPE.DATE: C_TYPES.MYSQL_TYPE_DATE,
195
+ MYSQL_DATA_TYPE.DATETIME: C_TYPES.MYSQL_TYPE_DATETIME,
196
+ MYSQL_DATA_TYPE.TIMESTAMP: C_TYPES.MYSQL_TYPE_TIMESTAMP,
197
+ MYSQL_DATA_TYPE.CHAR: C_TYPES.MYSQL_TYPE_STRING,
198
+ MYSQL_DATA_TYPE.BINARY: C_TYPES.MYSQL_TYPE_STRING,
199
+ MYSQL_DATA_TYPE.VARCHAR: C_TYPES.MYSQL_TYPE_VAR_STRING,
200
+ MYSQL_DATA_TYPE.VARBINARY: C_TYPES.MYSQL_TYPE_VAR_STRING,
201
+ MYSQL_DATA_TYPE.TINYBLOB: C_TYPES.MYSQL_TYPE_TINY_BLOB,
202
+ MYSQL_DATA_TYPE.TINYTEXT: C_TYPES.MYSQL_TYPE_TINY_BLOB,
203
+ MYSQL_DATA_TYPE.BLOB: C_TYPES.MYSQL_TYPE_BLOB,
204
+ MYSQL_DATA_TYPE.TEXT: C_TYPES.MYSQL_TYPE_BLOB,
205
+ MYSQL_DATA_TYPE.MEDIUMBLOB: C_TYPES.MYSQL_TYPE_MEDIUM_BLOB,
206
+ MYSQL_DATA_TYPE.MEDIUMTEXT: C_TYPES.MYSQL_TYPE_MEDIUM_BLOB,
207
+ MYSQL_DATA_TYPE.LONGBLOB: C_TYPES.MYSQL_TYPE_LONG_BLOB,
208
+ MYSQL_DATA_TYPE.LONGTEXT: C_TYPES.MYSQL_TYPE_LONG_BLOB,
209
+ MYSQL_DATA_TYPE.BIT: C_TYPES.MYSQL_TYPE_BIT,
210
+ MYSQL_DATA_TYPE.BOOL: C_TYPES.MYSQL_TYPE_TINY,
211
+ MYSQL_DATA_TYPE.BOOLEAN: C_TYPES.MYSQL_TYPE_TINY
212
+ }
213
+
214
+
141
215
  class FIELD_FLAG(object):
142
216
  __slots__ = ()
143
217
  NOT_NULL = 1 # field cannot be null
@@ -0,0 +1,176 @@
1
+ from typing import List
2
+
3
+ import requests
4
+
5
+
6
+ class ConfluenceAPIClient:
7
+ def __init__(self, url: str, username: str, password: str):
8
+ self.url = url
9
+ self.username = username
10
+ self.password = password
11
+ self.session = requests.Session()
12
+ self.session.auth = (self.username, self.password)
13
+ self.session.headers.update({"Accept": "application/json"})
14
+
15
+ def get_spaces(
16
+ self,
17
+ ids: List[int] = None,
18
+ keys: List[str] = None,
19
+ space_type: str = None,
20
+ status: str = None,
21
+ sort_condition: str = None,
22
+ limit: int = None,
23
+ ):
24
+ url = f"{self.url}/wiki/api/v2/spaces"
25
+ params = {
26
+ "description-format": "view",
27
+ }
28
+ if ids:
29
+ params["ids"] = ids
30
+ if keys:
31
+ params["keys"] = keys
32
+ if space_type:
33
+ params["type"] = space_type
34
+ if status:
35
+ params["status"] = status
36
+ if sort_condition:
37
+ params["sort"] = sort_condition
38
+ if limit:
39
+ params["limit"] = limit
40
+
41
+ return self._paginate(url, params)
42
+
43
+ def get_pages(
44
+ self,
45
+ page_ids: List[int] = None,
46
+ space_ids: List[int] = None,
47
+ statuses: List[str] = None,
48
+ title: str = None,
49
+ sort_condition: str = None,
50
+ limit: int = None,
51
+ ) -> List[dict]:
52
+ url = f"{self.url}/wiki/api/v2/pages"
53
+ params = {
54
+ "body-format": "storage",
55
+ }
56
+ if page_ids:
57
+ params["id"] = page_ids
58
+ if space_ids:
59
+ params["space-id"] = space_ids
60
+ if statuses:
61
+ params["status"] = statuses
62
+ if title:
63
+ params["title"] = title
64
+ if sort_condition:
65
+ params["sort"] = sort_condition
66
+ if limit:
67
+ params["limit"] = limit
68
+
69
+ return self._paginate(url, params)
70
+
71
+ def get_blogposts(
72
+ self,
73
+ post_ids: List[int] = None,
74
+ space_ids: List[str] = None,
75
+ statuses: List[str] = None,
76
+ title: str = None,
77
+ sort_condition: str = None,
78
+ limit: int = None,
79
+ ) -> List[dict]:
80
+ url = f"{self.url}/wiki/api/v2/blogposts"
81
+ params = {
82
+ "body-format": "storage",
83
+ }
84
+ if post_ids:
85
+ params["id"] = post_ids
86
+ if space_ids:
87
+ params["space-id"] = space_ids
88
+ if statuses:
89
+ params["status"] = statuses
90
+ if title:
91
+ params["title"] = title
92
+ if sort_condition:
93
+ params["sort"] = sort_condition
94
+ if limit:
95
+ params["limit"] = limit
96
+
97
+ return self._paginate(url, params)
98
+
99
+ def get_whiteboard_by_id(self, whiteboard_id: int) -> dict:
100
+ url = f"{self.url}/wiki/api/v2/whiteboards/{whiteboard_id}"
101
+
102
+ return self._make_request("GET", url)
103
+
104
+ def get_database_by_id(self, database_id: int) -> dict:
105
+ url = f"{self.url}/wiki/api/v2/databases/{database_id}"
106
+
107
+ return self._make_request("GET", url)
108
+
109
+ def get_tasks(
110
+ self,
111
+ task_ids: List[int] = None,
112
+ space_ids: List[str] = None,
113
+ page_ids: List[str] = None,
114
+ blogpost_ids: List[str] = None,
115
+ created_by_ids: List[str] = None,
116
+ assigned_to_ids: List[str] = None,
117
+ completed_by_ids: List[str] = None,
118
+ status: str = None,
119
+ limit: int = None,
120
+ ) -> List[dict]:
121
+ url = f"{self.url}/wiki/api/v2/tasks"
122
+ params = {
123
+ "body-format": "storage",
124
+ }
125
+ if task_ids:
126
+ params["id"] = task_ids
127
+ if space_ids:
128
+ params["space-id"] = space_ids
129
+ if page_ids:
130
+ params["page-id"] = page_ids
131
+ if blogpost_ids:
132
+ params["blogpost-id"] = blogpost_ids
133
+ if created_by_ids:
134
+ params["created-by"] = created_by_ids
135
+ if assigned_to_ids:
136
+ params["assigned-to"] = assigned_to_ids
137
+ if completed_by_ids:
138
+ params["completed-by"] = completed_by_ids
139
+ if status:
140
+ params["status"] = status
141
+ if limit:
142
+ params["limit"] = limit
143
+
144
+ return self._paginate(url, params)
145
+
146
+ def _paginate(self, url: str, params: dict = None) -> List[dict]:
147
+ results = []
148
+ response = self._make_request("GET", url, params)
149
+ results.extend(response["results"])
150
+
151
+ while response["_links"].get("next"):
152
+ next_url = response["_links"].get("next")
153
+ next_params = {}
154
+ if params:
155
+ next_params.update(params)
156
+ if "cursor=" in next_url:
157
+ # cursor= is 7 characters long
158
+ cursor_start = next_url.find("cursor=") + 7
159
+ cursor_value = next_url[cursor_start:]
160
+ if "&" in cursor_value:
161
+ cursor_value = cursor_value.split("&")[0]
162
+ next_params["cursor"] = cursor_value
163
+ response = self._make_request("GET", url, next_params)
164
+ else:
165
+ response = self._make_request("GET", next_url)
166
+ results.extend(response["results"])
167
+
168
+ return results
169
+
170
+ def _make_request(self, method: str, url: str, params: dict = None, data: dict = None) -> dict:
171
+ response = self.session.request(method, url, params=params, json=data)
172
+
173
+ if response.status_code != 200:
174
+ raise Exception(f"Request failed with status code {response.status_code}: {response.text}")
175
+
176
+ return response.json()