MindsDB 25.3.1.0__py3-none-any.whl → 25.3.3.0__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 (30) hide show
  1. mindsdb/__about__.py +1 -1
  2. mindsdb/__main__.py +1 -2
  3. mindsdb/api/executor/sql_query/steps/union_step.py +21 -24
  4. mindsdb/api/http/gui.py +5 -4
  5. mindsdb/api/http/initialize.py +19 -19
  6. mindsdb/integrations/handlers/github_handler/generate_api.py +228 -0
  7. mindsdb/integrations/handlers/github_handler/github_handler.py +15 -8
  8. mindsdb/integrations/handlers/github_handler/requirements.txt +1 -1
  9. mindsdb/integrations/handlers/jira_handler/__init__.py +1 -0
  10. mindsdb/integrations/handlers/jira_handler/jira_handler.py +22 -80
  11. mindsdb/integrations/handlers/pgvector_handler/pgvector_handler.py +3 -3
  12. mindsdb/integrations/handlers/redshift_handler/redshift_handler.py +1 -0
  13. mindsdb/integrations/handlers/salesforce_handler/requirements.txt +1 -1
  14. mindsdb/integrations/handlers/salesforce_handler/salesforce_handler.py +20 -25
  15. mindsdb/integrations/handlers/salesforce_handler/salesforce_tables.py +2 -2
  16. mindsdb/integrations/handlers/slack_handler/slack_handler.py +2 -1
  17. mindsdb/integrations/handlers/timescaledb_handler/timescaledb_handler.py +11 -6
  18. mindsdb/integrations/libs/api_handler_generator.py +583 -0
  19. mindsdb/integrations/libs/ml_handler_process/learn_process.py +9 -3
  20. mindsdb/utilities/config.py +1 -1
  21. mindsdb/utilities/render/sqlalchemy_render.py +11 -5
  22. {mindsdb-25.3.1.0.dist-info → mindsdb-25.3.3.0.dist-info}/METADATA +219 -220
  23. {mindsdb-25.3.1.0.dist-info → mindsdb-25.3.3.0.dist-info}/RECORD +26 -28
  24. {mindsdb-25.3.1.0.dist-info → mindsdb-25.3.3.0.dist-info}/WHEEL +1 -1
  25. mindsdb/integrations/handlers/jira_handler/jira_table.py +0 -172
  26. mindsdb/integrations/handlers/jira_handler/requirements.txt +0 -1
  27. mindsdb/integrations/handlers/timescaledb_handler/tests/__init__.py +0 -0
  28. mindsdb/integrations/handlers/timescaledb_handler/tests/test_timescaledb_handler.py +0 -47
  29. {mindsdb-25.3.1.0.dist-info → mindsdb-25.3.3.0.dist-info}/LICENSE +0 -0
  30. {mindsdb-25.3.1.0.dist-info → mindsdb-25.3.3.0.dist-info}/top_level.txt +0 -0
mindsdb/__about__.py CHANGED
@@ -1,6 +1,6 @@
1
1
  __title__ = 'MindsDB'
2
2
  __package_name__ = 'mindsdb'
3
- __version__ = '25.3.1.0'
3
+ __version__ = '25.3.3.0'
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'
mindsdb/__main__.py CHANGED
@@ -9,10 +9,10 @@ import secrets
9
9
  import traceback
10
10
  import threading
11
11
  from enum import Enum
12
- from packaging import version
13
12
  from dataclasses import dataclass, field
14
13
  from typing import Callable, Optional, Tuple, List
15
14
 
15
+ from packaging import version
16
16
  from sqlalchemy.orm.attributes import flag_modified
17
17
 
18
18
  from mindsdb.utilities import log
@@ -46,7 +46,6 @@ try:
46
46
  except RuntimeError:
47
47
  logger.info('Torch multiprocessing context already set, ignoring...')
48
48
 
49
-
50
49
  _stop_event = threading.Event()
51
50
 
52
51
 
@@ -1,9 +1,9 @@
1
- import hashlib
2
-
3
1
  from mindsdb.api.executor.planner.steps import UnionStep
4
2
 
5
3
  from mindsdb.api.executor.sql_query.result_set import ResultSet
6
4
  from mindsdb.api.executor.exceptions import WrongArgumentError
5
+ from mindsdb.api.executor.utilities.sql import query_df_with_type_infer_fallback
6
+ import numpy as np
7
7
 
8
8
  from .base import BaseStepCall
9
9
 
@@ -30,27 +30,24 @@ class UnionStepCall(BaseStepCall):
30
30
  # if type1 != type2:
31
31
  # raise ErSqlWrongArguments(f'UNION types mismatch: {type1} != {type2}')
32
32
 
33
- data = ResultSet()
34
- for col in left_result.columns:
35
- data.add_column(col)
36
-
37
- records_hashes = []
38
- results = []
39
- for row in left_result.to_lists():
40
- if step.unique:
41
- checksum = hashlib.sha256(str(row).encode()).hexdigest()
42
- if checksum in records_hashes:
43
- continue
44
- records_hashes.append(checksum)
45
- results.append(list(row))
46
-
47
- for row in right_result.to_lists():
48
- if step.unique:
49
- checksum = hashlib.sha256(str(row).encode()).hexdigest()
50
- if checksum in records_hashes:
51
- continue
52
- records_hashes.append(checksum)
53
- results.append(list(row))
54
- data.add_raw_values(results)
33
+ table_a, names = left_result.to_df_cols()
34
+ table_b, _ = right_result.to_df_cols()
35
+
36
+ op = 'UNION ALL'
37
+ if step.unique:
38
+ op = 'UNION'
39
+ query = f"""
40
+ SELECT * FROM table_a
41
+ {op}
42
+ SELECT * FROM table_b
43
+ """
44
+
45
+ resp_df, _description = query_df_with_type_infer_fallback(query, {
46
+ 'table_a': table_a,
47
+ 'table_b': table_b
48
+ })
49
+ resp_df.replace({np.nan: None}, inplace=True)
50
+
51
+ data = ResultSet().from_df_cols(resp_df, col_names=names)
55
52
 
56
53
  return data
mindsdb/api/http/gui.py CHANGED
@@ -5,6 +5,7 @@ from pathlib import Path
5
5
  from zipfile import ZipFile
6
6
 
7
7
  import requests
8
+ from packaging.version import Version
8
9
 
9
10
  from mindsdb.utilities.config import Config
10
11
  from mindsdb.utilities import log
@@ -65,7 +66,7 @@ def download_gui(destignation, version):
65
66
  """
66
67
 
67
68
 
68
- def update_static(gui_version_lv):
69
+ def update_static(gui_version: Version):
69
70
  """Update Scout files basing on compatible-config.json content.
70
71
  Files will be downloaded and updated if new version of GUI > current.
71
72
  Current GUI version stored in static/version.txt.
@@ -74,11 +75,11 @@ def update_static(gui_version_lv):
74
75
  static_path = Path(config["paths"]["static"])
75
76
 
76
77
  logger.info(
77
- f"New version of GUI available ({gui_version_lv.vstring}). Downloading..."
78
+ f"New version of GUI available ({gui_version.base_version}). Downloading..."
78
79
  )
79
80
 
80
81
  temp_dir = tempfile.mkdtemp(prefix="mindsdb_gui_files_")
81
- success = download_gui(temp_dir, gui_version_lv.vstring)
82
+ success = download_gui(temp_dir, gui_version.base_version)
82
83
  if success is False:
83
84
  shutil.rmtree(temp_dir)
84
85
  return False
@@ -90,5 +91,5 @@ def update_static(gui_version_lv):
90
91
  shutil.copytree(temp_dir, str(static_path))
91
92
  shutil.rmtree(temp_dir_for_rm)
92
93
 
93
- logger.info(f"GUI version updated to {gui_version_lv.vstring}")
94
+ logger.info(f"GUI version updated to {gui_version.base_version}")
94
95
  return True
@@ -6,7 +6,6 @@ import traceback
6
6
  import webbrowser
7
7
  from pathlib import Path
8
8
  from http import HTTPStatus
9
- from distutils.version import LooseVersion
10
9
 
11
10
  import requests
12
11
  from flask import Flask, url_for, make_response, request, send_from_directory
@@ -14,6 +13,7 @@ from flask.json import dumps
14
13
  from flask_compress import Compress
15
14
  from flask_restx import Api
16
15
  from werkzeug.exceptions import HTTPException
16
+ from packaging.version import Version, parse as parse_version
17
17
 
18
18
  from mindsdb.__about__ import __version__ as mindsdb_version
19
19
  from mindsdb.api.http.gui import update_static
@@ -72,7 +72,7 @@ def custom_output_json(data, code, headers=None):
72
72
  return resp
73
73
 
74
74
 
75
- def get_last_compatible_gui_version() -> LooseVersion:
75
+ def get_last_compatible_gui_version() -> Version:
76
76
  logger.debug("Getting last compatible frontend..")
77
77
  try:
78
78
  res = requests.get('https://mindsdb-web-builds.s3.amazonaws.com/compatible-config.json', timeout=5)
@@ -95,7 +95,7 @@ def get_last_compatible_gui_version() -> LooseVersion:
95
95
  logger.error(f"Cant decode compatible-config.json: {e}")
96
96
  return False
97
97
 
98
- current_mindsdb_lv = LooseVersion(mindsdb_version)
98
+ current_mindsdb_lv = parse_version(mindsdb_version)
99
99
 
100
100
  try:
101
101
  gui_versions = {}
@@ -103,31 +103,31 @@ def get_last_compatible_gui_version() -> LooseVersion:
103
103
  max_gui_lv = None
104
104
  for el in versions['mindsdb']:
105
105
  if el['mindsdb_version'] is None:
106
- gui_lv = LooseVersion(el['gui_version'])
106
+ gui_lv = parse_version(el['gui_version'])
107
107
  else:
108
- mindsdb_lv = LooseVersion(el['mindsdb_version'])
109
- gui_lv = LooseVersion(el['gui_version'])
110
- if mindsdb_lv.vstring not in gui_versions or gui_lv > gui_versions[mindsdb_lv.vstring]:
111
- gui_versions[mindsdb_lv.vstring] = gui_lv
108
+ mindsdb_lv = parse_version(el['mindsdb_version'])
109
+ gui_lv = parse_version(el['gui_version'])
110
+ if mindsdb_lv.base_version not in gui_versions or gui_lv > gui_versions[mindsdb_lv.base_version]:
111
+ gui_versions[mindsdb_lv.base_version] = gui_lv
112
112
  if max_mindsdb_lv is None or max_mindsdb_lv < mindsdb_lv:
113
113
  max_mindsdb_lv = mindsdb_lv
114
114
  if max_gui_lv is None or max_gui_lv < gui_lv:
115
115
  max_gui_lv = gui_lv
116
116
 
117
- all_mindsdb_lv = [LooseVersion(x) for x in gui_versions.keys()]
117
+ all_mindsdb_lv = [parse_version(x) for x in gui_versions.keys()]
118
118
  all_mindsdb_lv.sort()
119
119
 
120
- if current_mindsdb_lv.vstring in gui_versions:
121
- gui_version_lv = gui_versions[current_mindsdb_lv.vstring]
120
+ if current_mindsdb_lv.base_version in gui_versions:
121
+ gui_version_lv = gui_versions[current_mindsdb_lv.base_version]
122
122
  elif current_mindsdb_lv > all_mindsdb_lv[-1]:
123
123
  gui_version_lv = max_gui_lv
124
124
  else:
125
- lower_versions = {key: value for key, value in gui_versions.items() if LooseVersion(key) < current_mindsdb_lv}
125
+ lower_versions = {key: value for key, value in gui_versions.items() if parse_version(key) < current_mindsdb_lv}
126
126
  if len(lower_versions) == 0:
127
- gui_version_lv = gui_versions[all_mindsdb_lv[0].vstring]
127
+ gui_version_lv = gui_versions[all_mindsdb_lv[0].base_version]
128
128
  else:
129
- all_lower_versions = [LooseVersion(x) for x in lower_versions.keys()]
130
- gui_version_lv = gui_versions[all_lower_versions[-1].vstring]
129
+ all_lower_versions = [parse_version(x) for x in lower_versions.keys()]
130
+ gui_version_lv = gui_versions[all_lower_versions[-1].base_version]
131
131
  except Exception as e:
132
132
  logger.error(f"Error in compatible-config.json structure: {e}")
133
133
  return False
@@ -136,7 +136,7 @@ def get_last_compatible_gui_version() -> LooseVersion:
136
136
  return gui_version_lv
137
137
 
138
138
 
139
- def get_current_gui_version() -> LooseVersion:
139
+ def get_current_gui_version() -> Version:
140
140
  logger.debug("Getting current frontend version..")
141
141
  config = Config()
142
142
  static_path = Path(config['paths']['static'])
@@ -148,7 +148,7 @@ def get_current_gui_version() -> LooseVersion:
148
148
  current_gui_version = f.readline()
149
149
 
150
150
  current_gui_lv = (
151
- None if current_gui_version is None else LooseVersion(current_gui_version)
151
+ None if current_gui_version is None else parse_version(current_gui_version)
152
152
  )
153
153
  logger.debug(f"Current frontend version: {current_gui_lv}.")
154
154
 
@@ -163,7 +163,7 @@ def initialize_static():
163
163
  required_gui_version = config['gui'].get('version')
164
164
 
165
165
  if required_gui_version is not None:
166
- required_gui_version_lv = LooseVersion(required_gui_version)
166
+ required_gui_version_lv = parse_version(required_gui_version)
167
167
  success = True
168
168
  if (
169
169
  current_gui_version_lv is None
@@ -176,7 +176,7 @@ def initialize_static():
176
176
  return False
177
177
 
178
178
  # ignore versions like '23.9.2.2'
179
- if current_gui_version_lv is not None and len(current_gui_version_lv.version) < 3:
179
+ if current_gui_version_lv is not None and len(current_gui_version_lv.release) < 3:
180
180
  if current_gui_version_lv == last_gui_version_lv:
181
181
  return True
182
182
  logger.debug("Updating gui..")
@@ -0,0 +1,228 @@
1
+ import inspect
2
+ from typing import List
3
+ from dataclasses import dataclass
4
+
5
+ import pandas as pd
6
+ import github
7
+
8
+ from mindsdb.integrations.utilities.sql_utils import (FilterCondition, FilterOperator, SortColumn)
9
+ from mindsdb.integrations.libs.api_handler import APIResource
10
+
11
+
12
+ @dataclass
13
+ class Type:
14
+ name: str
15
+ sub_type: str = None
16
+ optional: bool = False
17
+
18
+
19
+ @dataclass
20
+ class GHMethod:
21
+ name: str
22
+ table_name: str
23
+ params: dict
24
+ output: Type
25
+
26
+
27
+ def parse_annotations(annotations):
28
+ '''
29
+ Parse string annotation, and extract type, input examples:
30
+ - Milestone | Opt[str]
31
+ - PaginatedList[Issue]
32
+ '''
33
+ type_name, sub_type = None, None
34
+ if not isinstance(annotations, str):
35
+
36
+ return Type(getattr(annotations, '__name__', None))
37
+ for item in annotations.split('|'):
38
+ item = item.strip()
39
+ if item is None:
40
+ continue
41
+ if '[' in item:
42
+ type_name = item[: item.find('[')]
43
+ item2 = item[item.find('[') + 1: item.rfind(']')]
44
+ if type_name == 'Opt':
45
+ inner_type = parse_annotations(item2)
46
+ inner_type.optional = Type
47
+ return inner_type
48
+ if type_name == 'dict':
49
+ item2 = item2[item2.find(',') + 1:]
50
+ sub_type = parse_annotations(item2).name
51
+ else:
52
+ type_name = item
53
+ # get only first type
54
+ break
55
+ return Type(type_name, sub_type)
56
+
57
+
58
+ def get_properties(cls):
59
+ # find properties of the class
60
+
61
+ properties = {}
62
+ for prop_name, prop in inspect.getmembers(cls):
63
+ if prop_name.startswith('_'):
64
+ continue
65
+ if not isinstance(prop, property):
66
+ continue
67
+ sig2 = inspect.signature(prop.fget)
68
+
69
+ properties[prop_name] = parse_annotations(sig2.return_annotation)
70
+ return properties
71
+
72
+
73
+ def get_github_types():
74
+ # get github types
75
+ types = {}
76
+
77
+ GithubObject = github.GithubObject.GithubObject
78
+ for module_name, module in inspect.getmembers(github, inspect.ismodule):
79
+ cls = getattr(module, module_name, None)
80
+ if cls is None:
81
+ continue
82
+ if issubclass(cls, GithubObject):
83
+
84
+ # remove inherited props
85
+ parent_props = []
86
+ for cls2 in cls.__bases__:
87
+ parent_props += get_properties(cls2).keys()
88
+
89
+ properties = {}
90
+ for k, v in get_properties(cls).items():
91
+ if k not in parent_props:
92
+ properties[k] = v
93
+
94
+ types[module_name] = properties
95
+ return types
96
+
97
+
98
+ def get_github_methods(cls):
99
+ '''
100
+ Analyse class in order to find methods which return list of objects.
101
+ '''
102
+ methods = []
103
+
104
+ for method_name, method in inspect.getmembers(cls, inspect.isfunction):
105
+ sig = inspect.signature(method)
106
+
107
+ return_type = parse_annotations(sig.return_annotation)
108
+ list_prefix = 'get_'
109
+ if not (method_name.startswith(list_prefix) and return_type.name == 'PaginatedList'):
110
+ continue
111
+
112
+ table_name = method_name[len(list_prefix):]
113
+
114
+ params = {}
115
+ for param_name, param in sig.parameters.items():
116
+ params[param_name] = parse_annotations(param.annotation)
117
+
118
+ methods.append(GHMethod(
119
+ name=method_name,
120
+ table_name=table_name,
121
+ params=params,
122
+ output=return_type
123
+ ))
124
+ return methods
125
+
126
+
127
+ class GHTable(APIResource):
128
+ def __init__(self, *args, method: GHMethod = None, github_types=None, **kwargs):
129
+ self.method = method
130
+ self.github_types = github_types
131
+
132
+ self.output_columns = {}
133
+ if method.output.sub_type in self.github_types:
134
+ self.output_columns = self.github_types[method.output.sub_type]
135
+
136
+ # check params:
137
+ self.params, self.list_params = [], []
138
+ for name, param_type in method.params.items():
139
+ self.params.append(name)
140
+ if param_type.name == 'list':
141
+ self.list_params.append(name)
142
+
143
+ self._allow_sort = 'sort' in method.params
144
+
145
+ super().__init__(*args, **kwargs)
146
+
147
+ def repr_value(self, value, type_name):
148
+ if value is None or type_name in ('bool', 'int', 'float'):
149
+ return value
150
+ if type_name in self.github_types:
151
+ properties = self.github_types[type_name]
152
+ if 'login' in properties:
153
+ value = getattr(value, 'login')
154
+ elif 'url' in properties:
155
+ value = getattr(value, 'url')
156
+ return str(value)
157
+
158
+ def get_columns(self) -> List[str]:
159
+ return list(self.output_columns.keys())
160
+
161
+ def list(
162
+ self,
163
+ conditions: List[FilterCondition] = None,
164
+ limit: int = None,
165
+ sort: List[SortColumn] = None,
166
+ targets: List[str] = None,
167
+ **kwargs
168
+ ) -> pd.DataFrame:
169
+
170
+ if limit is None:
171
+ limit = 20
172
+
173
+ method_kwargs = {}
174
+ if sort is not None and self._allow_sort:
175
+ for col in sort:
176
+ method_kwargs['sort'] = col.column
177
+ method_kwargs['direction'] = 'asc' if col.ascending else 'desc'
178
+ sort.applied = True
179
+ # supported only 1 column
180
+ break
181
+
182
+ if conditions:
183
+ for condition in conditions:
184
+ if condition.column not in self.params:
185
+ continue
186
+
187
+ if condition.column in self.list_params:
188
+ if condition.op == FilterOperator.IN:
189
+ method_kwargs[condition.column] = condition.value
190
+ elif condition.op == FilterOperator.EQUAL:
191
+ method_kwargs[condition.column] = [condition]
192
+ condition.applied = True
193
+ else:
194
+ method_kwargs[condition.column] = condition.value
195
+ condition.applied = True
196
+
197
+ connection = self.handler.connect()
198
+ method = getattr(connection.get_repo(self.handler.repository), self.method.name)
199
+
200
+ data = []
201
+ count = 0
202
+ for record in method(**method_kwargs):
203
+ item = {}
204
+ for name, output_type in self.output_columns.items():
205
+
206
+ # workaround to prevent making addition request per property.
207
+ if name in targets:
208
+ # request only if is required
209
+ value = getattr(record, name)
210
+ else:
211
+ value = getattr(record, '_' + name).value
212
+ if value is not None:
213
+ if output_type.name == 'list':
214
+ value = ",".join([
215
+ str(self.repr_value(i, output_type.sub_type))
216
+ for i in value
217
+ ])
218
+ else:
219
+ value = self.repr_value(value, output_type.name)
220
+ item[name] = value
221
+
222
+ data.append(item)
223
+
224
+ count += 1
225
+ if limit <= count:
226
+ break
227
+
228
+ return pd.DataFrame(data, columns=self.get_columns())
@@ -12,7 +12,7 @@ from mindsdb.integrations.handlers.github_handler.github_tables import (
12
12
  GithubMilestonesTable,
13
13
  GithubProjectsTable, GithubFilesTable
14
14
  )
15
-
15
+ from mindsdb.integrations.handlers.github_handler.generate_api import get_github_types, get_github_methods, GHTable
16
16
  from mindsdb.integrations.libs.api_handler import APIHandler
17
17
  from mindsdb.integrations.libs.response import (
18
18
  HandlerStatusResponse as StatusResponse,
@@ -22,6 +22,7 @@ from mindsdb.utilities import log
22
22
 
23
23
  logger = log.getLogger(__name__)
24
24
 
25
+
25
26
  class GithubHandler(APIHandler):
26
27
  """The GitHub handler implementation"""
27
28
 
@@ -43,16 +44,22 @@ class GithubHandler(APIHandler):
43
44
  self.connection = None
44
45
  self.is_connected = False
45
46
 
47
+ # custom tables
46
48
  self._register_table("issues", GithubIssuesTable(self))
47
- self._register_table("pull_requests", GithubPullRequestsTable(self))
48
- self._register_table("commits", GithubCommitsTable(self))
49
- self._register_table("releases", GithubReleasesTable(self))
50
- self._register_table("branches", GithubBranchesTable(self))
51
- self._register_table("contributors", GithubContributorsTable(self))
52
- self._register_table("milestones", GithubMilestonesTable(self))
53
- self._register_table("projects", GithubProjectsTable(self))
54
49
  self._register_table("files", GithubFilesTable(self))
55
50
 
51
+ # generated tables
52
+ github_types = get_github_types()
53
+
54
+ # generate tables from repository object
55
+ for method in get_github_methods(github.Repository.Repository):
56
+ if method.table_name in self._tables:
57
+ continue
58
+
59
+ table = GHTable(self, github_types=github_types, method=method)
60
+ self._register_table(method.table_name, table)
61
+
62
+
56
63
  def connect(self) -> StatusResponse:
57
64
  """Set up the connection required by the handler.
58
65
 
@@ -1 +1 @@
1
- pygithub
1
+ pygithub==2.6.1
@@ -4,6 +4,7 @@ from .__about__ import __version__ as version, __description__ as description
4
4
  try:
5
5
  from .jira_handler import JiraHandler as Handler
6
6
  import_error = None
7
+
7
8
  except Exception as e:
8
9
  Handler = None
9
10
  import_error = e
@@ -1,23 +1,15 @@
1
- from mindsdb.integrations.handlers.jira_handler.jira_table import JiraProjectsTable
2
1
  from mindsdb.integrations.libs.api_handler import APIHandler
3
2
  from mindsdb.integrations.libs.response import (
4
3
  HandlerStatusResponse as StatusResponse,
5
4
  )
6
5
  from mindsdb.utilities import log
7
- from mindsdb_sql_parser import parse_sql
8
-
9
- from atlassian import Jira
10
- from typing import Optional
11
- import requests
6
+ from mindsdb.integrations.libs.api_handler_generator import APIResourceGenerator
12
7
 
13
8
 
14
9
  logger = log.getLogger(__name__)
15
10
 
16
- class JiraHandler(APIHandler):
17
- """
18
- This handler handles connection and execution of the Airtable statements.
19
- """
20
11
 
12
+ class JiraHandler(APIHandler):
21
13
 
22
14
  def __init__(self, name=None, **kwargs):
23
15
  """
@@ -30,64 +22,38 @@ class JiraHandler(APIHandler):
30
22
  super().__init__(name)
31
23
  self.connection_data = kwargs.get("connection_data", {})
32
24
 
33
- self.parser = parse_sql
34
- self.dialect = 'jira'
35
- self.kwargs = kwargs
36
25
  self.connection = None
37
26
  self.is_connected = False
38
27
 
28
+ # todo store parsed data in files
29
+
30
+ self.api_resource_generator = APIResourceGenerator(
31
+ "https://developer.atlassian.com/cloud/jira/platform/swagger-v3.v3.json",
32
+ self.connection_data,
33
+ url_base='/rest/api/3/',
34
+ options={
35
+ 'offset_param': ['startAt', 'offset'],
36
+ 'total_column': ['totalEntryCount', 'total'],
37
+ 'check_connection_table': 'myself'
38
+ }
39
+ )
40
+
41
+ resource_tables = self.api_resource_generator.generate_api_resources(self)
39
42
 
40
- jira_projects_data = JiraProjectsTable(self)
41
- self._register_table("project", jira_projects_data)
43
+ for table_name, resource in resource_tables.items():
44
+ self._register_table(table_name, resource)
42
45
 
43
46
  def __del__(self):
44
47
  if self.is_connected is True:
45
48
  self.disconnect()
46
49
 
47
- def connect(self) -> StatusResponse:
50
+ def connect(self):
48
51
  """
49
52
  Set up the connection required by the handler.
50
53
  Returns:
51
54
  HandlerStatusResponse
52
55
  """
53
-
54
- if self.is_connected is True:
55
- return self.connection
56
-
57
- s = requests.Session()
58
- if self.connection_data.get("cloud", False):
59
- params = {
60
- "cloud": True,
61
- "username": self.connection_data['jira_username'],
62
- "password": self.connection_data['jira_api_token'],
63
- "url": self.connection_data['jira_url'],
64
- }
65
- else:
66
- params = {
67
- "cloud": False,
68
- "url": self.connection_data['jira_url'],
69
- "session": s
70
- }
71
-
72
- s.headers['Authorization'] = f"Bearer {self.connection_data['jira_api_token']}"
73
-
74
- self.connection = Jira(**params)
75
- self.is_connected = True
76
-
77
-
78
- return self.connection
79
-
80
- def disconnect(self):
81
- """
82
- Close any existing connections.
83
- """
84
-
85
- if self.is_connected is False:
86
- return
87
-
88
- self.connection.close()
89
- self.is_connected = False
90
- return self.is_connected
56
+ return
91
57
 
92
58
  def check_connection(self) -> StatusResponse:
93
59
  """
@@ -97,37 +63,13 @@ class JiraHandler(APIHandler):
97
63
  """
98
64
 
99
65
  response = StatusResponse(False)
100
- need_to_close = self.is_connected is False
101
-
66
+
102
67
  try:
103
- self.connect()
68
+ self.api_resource_generator.check_connection()
104
69
  response.success = True
105
70
  except Exception as e:
106
71
  logger.error(f"Error connecting to Jira API: {e}!")
107
72
  response.error_message = e
108
73
 
109
74
  self.is_connected = response.success
110
-
111
75
  return response
112
-
113
- def native_query(self, query: str) -> StatusResponse:
114
- """Receive and process a raw query.
115
- Parameters
116
- ----------
117
- query : str
118
- query in a native format
119
- Returns
120
- -------
121
- StatusResponse
122
- Request status
123
- """
124
- ast = parse_sql(query)
125
- return self.query(ast)
126
-
127
- def construct_jql(self):
128
- """construct jql & returns it to JiraProjectsTable class
129
- Returns
130
- -------
131
- Str
132
- """
133
- return 'project = ' + str(self.connection_data['project'])