python-databases 0.1.4__py3-none-any.whl → 0.1.5__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.
@@ -9,4 +9,9 @@ from python_databases.elasticsearch_infrastructure.elasticsearch import (
9
9
 
10
10
  load_dotenv()
11
11
 
12
- __all__ = ['UrlProtocol', 'ElasticSearch', 'ElasticSearchOnPrem', 'ElasticSearchCloud']
12
+ __all__ = [
13
+ 'UrlProtocol',
14
+ 'ElasticSearch',
15
+ 'ElasticSearchOnPrem',
16
+ 'ElasticSearchCloud'
17
+ ]
@@ -18,13 +18,13 @@ class UrlProtocol(Enum):
18
18
 
19
19
  class ElasticSearch(ABC):
20
20
  def __init__(
21
- self,
22
- elk_hostname: str,
23
- elasticsearch_port: Optional[int],
24
- kibana_port: Optional[int],
25
- protocol: UrlProtocol,
26
- username: Optional[str],
27
- password: Optional[str]
21
+ self,
22
+ elk_hostname: str,
23
+ elasticsearch_port: Optional[int],
24
+ kibana_port: Optional[int],
25
+ protocol: UrlProtocol,
26
+ username: Optional[str],
27
+ password: Optional[str]
28
28
  ):
29
29
  self.logger = get_logger(self.__class__.__name__)
30
30
  self._change_elasticsearch_logger()
@@ -43,177 +43,136 @@ class ElasticSearch(ABC):
43
43
 
44
44
  self.elk_client = None
45
45
 
46
- def _change_elasticsearch_logger(self):
47
- tracer = get_logger('elasticsearch')
48
- tracer.setLevel(logging.CRITICAL) # or desired level
49
- # tracer.addHandler(logging.FileHandler('indexer.log'))
50
-
51
46
  @abstractmethod
52
47
  def connect_to_elasticsearch(self):
53
48
  pass
54
49
 
55
- def set_list_of_docs(self, list_of_docs: list[dict], request_timeout: int = 600, quick: bool = True) -> None:
50
+ @staticmethod
51
+ def _change_elasticsearch_logger(debug_level: int = logging.CRITICAL) -> None:
52
+ tracer = get_logger('elasticsearch')
53
+ tracer.setLevel(debug_level)
54
+
55
+ def _set_list_of_docs_quick(self, list_of_docs: list[dict], request_timeout: int = 600) -> None:
56
+ if failed_response := helpers.streaming_bulk(
57
+ self.elk_client,
58
+ list_of_docs,
59
+ raise_on_error=False,
60
+ request_timeout=request_timeout,
61
+ chunk_size=1000
62
+ ):
63
+ for item in failed_response:
64
+ if item[1]['index']['status'] != 201 or item[1]['index']['_shards']['failed'] > 0:
65
+ self.logger.debug(f'Failed document: \n{item}')
66
+
67
+ def _set_list_of_docs_safe(self, list_of_docs: list[dict]) -> None:
68
+ success_response, failed_response = helpers.bulk(self.elk_client, list_of_docs)
69
+ if success_response == len(list_of_docs):
70
+ self.logger.info('All documents were reported successfully')
71
+ else:
72
+ self.logger.error(
73
+ f'Not all documents were reported successfully.\n'
74
+ f'The documents that were not reported successfully: {failed_response}'
75
+ )
76
+
77
+ def post_list_of_docs(self, list_of_docs: list[dict], request_timeout: int = 600, quick: bool = False) -> None:
56
78
  self.logger.info('Start to report the documents to ELK')
57
79
 
58
80
  if quick:
59
- if failed_response := helpers.streaming_bulk(
60
- self.elk_client,
61
- list_of_docs,
62
- raise_on_error=False,
63
- request_timeout=request_timeout,
64
- chunk_size=1000
65
- ):
66
- for item in failed_response:
67
- if item[1]['index']['status'] != 201 or item[1]['index']['_shards']['failed'] > 0:
68
- self.logger.debug(f'Failed document: \n{item}')
81
+ self._set_list_of_docs_quick(list_of_docs=list_of_docs, request_timeout=request_timeout)
69
82
  else:
70
- success_response, failed_response = helpers.bulk(self.elk_client, list_of_docs)
71
- if success_response == len(list_of_docs):
72
- self.logger.info('All documents were reported successfully')
73
- else:
74
- self.logger.error(
75
- f'Not all documents were reported successfully.\n'
76
- f'The documents that were not reported successfully: {failed_response}'
77
- )
83
+ self._set_list_of_docs_safe(list_of_docs=list_of_docs)
78
84
  self.logger.info('Finish to report the documents to ELK')
79
85
 
80
- def fill_list_of_docs( # noqa
81
- self,
82
- list_of_docs: list[dict],
83
- index_name: str,
84
- doc: dict[str, Any]
85
- ) -> None:
86
- list_of_docs.append(
87
- {
88
- "_index": index_name,
89
- "_source": deepcopy(doc)
90
- }
91
- )
86
+ @staticmethod
87
+ def _get_doc_with_basic_info() -> dict:
88
+ doc = {}
89
+
90
+ timestamp = datetime.now(tz=timezone.utc)
91
+ date_and_time_str = timestamp.isoformat()
92
+ date = date_and_time_str.split('T')[0]
93
+ time_part = date_and_time_str.split('T')[1].split('+')[0]
92
94
 
93
- def _add_basic_info(
94
- self,
95
- doc: dict,
96
- doc_id: str,
97
- timestamp: datetime,
98
- date_and_time: str,
99
- username: str = None
100
- ) -> dict:
101
95
  doc.update(
102
96
  {
103
- 'doc_id': doc_id,
97
+ 'doc_id': timestamp.strftime('%Y%m%dT%H%M%S%f'),
104
98
  'timestamp': timestamp,
105
- 'date_str': f'{date_and_time}'.replace('-', '_')
99
+ 'date_and_time_str': date_and_time_str,
100
+ 'date_str': date,
101
+ 'time_str': time_part,
106
102
  }
107
103
  )
108
- if username:
109
- doc['username'] = username
110
104
  return doc
111
105
 
112
- def _add_list_values_as_str(self, doc: dict, value: Any, column: str) -> None:
106
+ @staticmethod
107
+ def _add_list_values_as_str(doc: dict, column: str, value: Any) -> None:
113
108
  """Add list values as both list and string representation to the provided doc"""
114
109
  if type(value) is list and all(type(item) is str for item in value):
115
110
  filtered_value = list(filter(lambda item: item is not None, value))
116
111
  doc[f'{column}'] = filtered_value
117
112
  doc[f'{column}_str'] = ', '.join(filtered_value)
118
113
 
119
- def _build_document(
120
- self,
121
- row: dict,
122
- doc_id: str,
123
- timestamp: datetime,
124
- date_and_time: str,
125
- username: str = None
126
- ) -> dict:
127
- doc = self._add_basic_info(
128
- doc={},
129
- doc_id=doc_id,
130
- timestamp=timestamp,
131
- date_and_time=date_and_time,
132
- username=username
133
- )
134
-
135
- # Add custom fields
114
+ def _build_document(self, row: dict) -> dict:
115
+ doc = self._get_doc_with_basic_info()
136
116
  for custom_field, value in row.items():
137
117
  doc[custom_field] = value
118
+
138
119
  self._add_list_values_as_str(
139
120
  doc=doc,
121
+ column=custom_field,
140
122
  value=value,
141
- column=custom_field
142
123
  )
143
-
144
124
  return doc
145
125
 
146
- def _prepare_documents_for_bulk(
147
- self,
148
- data: list[dict],
149
- doc_id: str,
150
- doc_index_name: str,
151
- timestamp: datetime,
152
- date_and_time: str,
153
- username: str = None
154
- ) -> list[dict]:
126
+ @staticmethod
127
+ def _fill_list_of_docs(index_name: str, doc: dict[str, Any]) -> dict:
128
+ return {
129
+ "_index": index_name,
130
+ "_source": deepcopy(doc)
131
+ }
132
+
133
+ def _prepare_documents_for_bulk(self, data: list[dict], doc_index_name: str) -> list[dict]:
155
134
  list_of_docs = []
156
135
  for index, row in enumerate(data):
157
136
  self.logger.debug(f'index: {index + 1}/{len(data)}')
158
-
159
- doc = self._build_document(
160
- row=row,
161
- doc_id=doc_id,
162
- timestamp=timestamp,
163
- date_and_time=date_and_time,
164
- username=username
137
+ doc = self._build_document(row=row)
138
+ list_of_docs.append(
139
+ self._fill_list_of_docs(index_name=doc_index_name, doc=doc)
165
140
  )
166
-
167
- self.fill_list_of_docs(
168
- list_of_docs=list_of_docs,
169
- index_name=doc_index_name,
170
- doc=doc
171
- )
172
-
173
141
  return list_of_docs
174
142
 
175
- def fill_elk_index_as_bulk_chunk(self, list_of_docs: list[dict], chunk_size=1000, time_sleep: int = 60) -> None:
143
+ def post_list_of_docs_as_bulk_chunk(
144
+ self,
145
+ list_of_docs: list[dict],
146
+ chunk_size=1000,
147
+ time_sleep: int = 60,
148
+ quick: bool = False
149
+ ) -> None:
176
150
  for i in range(0, len(list_of_docs), chunk_size):
177
151
  self.logger.info(f'index: {i} - {i + chunk_size} / {len(list_of_docs)}')
178
152
  chunk = list_of_docs[i:i + chunk_size]
179
- self.set_list_of_docs(list_of_docs=chunk)
153
+ self.post_list_of_docs(list_of_docs=chunk, quick=quick)
180
154
  time.sleep(time_sleep)
181
155
 
182
- def fill_elk_index_as_bulk(
183
- self,
184
- data: list[dict],
185
- doc_index_name: str,
186
- doc_id: Optional[str] = None,
187
- timestamp: datetime = None,
188
- date_and_time: Optional[str] = None,
189
- username: Optional[str] = None,
190
- chunk_size=1000,
191
- time_sleep: int = 1
156
+ def fill_elk_index_as_bulk( # post_list_of_docs_as_bulk
157
+ self,
158
+ data: list[dict],
159
+ doc_index_name: str,
160
+ chunk_size=1000,
161
+ time_sleep: int = 1,
162
+ quick: bool = True
192
163
  ) -> None: # use as the main function to send data to the elastic search
193
- timestamp = timestamp or datetime.now(tz=timezone.utc)
194
164
  list_of_docs = self._prepare_documents_for_bulk(
195
165
  data=data,
196
166
  doc_index_name=doc_index_name,
197
- timestamp=timestamp,
198
- doc_id=doc_id or timestamp.strftime('%Y_%m_%dT%H_%M_%S_%f'),
199
- date_and_time=date_and_time or timestamp.isoformat(),
200
- username=username
201
167
  )
202
168
 
203
- self.fill_elk_index_as_bulk_chunk(
169
+ self.post_list_of_docs_as_bulk_chunk(
204
170
  list_of_docs=list_of_docs,
205
171
  chunk_size=chunk_size,
206
- time_sleep=time_sleep
172
+ time_sleep=time_sleep,
173
+ quick=quick,
207
174
  )
208
175
 
209
- def convert_dataframes_to_list_of_docs(self, dataframe: pd.DataFrame) -> list:
210
- data = []
211
- dataframe_values = dataframe.values.tolist()
212
- for index, value in enumerate(dataframe_values):
213
- self.logger.debug(f'index: {index}/{len(dataframe_values)}')
214
- data.append(value)
215
- return data
216
-
217
176
  def check_if_index_exists(self, index: str) -> bool:
218
177
  if self.elk_client.indices.exists(index=index):
219
178
  self.logger.info(f"The index '{index}' exists.")
@@ -231,7 +190,6 @@ class ElasticSearch(ABC):
231
190
  self.delete_index(index=index)
232
191
  return True
233
192
  else:
234
- self.logger.info(f"The index '{index}' does not exist.")
235
193
  return False
236
194
 
237
195
  def get_documents(self, index: str, query: dict) -> list[dict]:
@@ -241,16 +199,24 @@ class ElasticSearch(ABC):
241
199
  except Exception as e:
242
200
  raise Exception(f"Failed to get documents from index '{index}': {str(e)}")
243
201
 
202
+ def convert_dataframes_to_list_of_docs(self, dataframe: pd.DataFrame) -> list:
203
+ data = []
204
+ dataframe_values = dataframe.values.tolist()
205
+ for index, value in enumerate(dataframe_values):
206
+ self.logger.debug(f'index: {index}/{len(dataframe_values)}')
207
+ data.append(value)
208
+ return data
209
+
244
210
 
245
211
  class ElasticSearchOnPrem(ElasticSearch):
246
212
  def __init__(
247
- self,
248
- elk_hostname: str,
249
- elasticsearch_port: Optional[int] = 9200,
250
- kibana_port: Optional[int] = 5602,
251
- protocol: UrlProtocol = UrlProtocol.HTTPS,
252
- username: Optional[str] = None,
253
- password: Optional[str] = None
213
+ self,
214
+ elk_hostname: str,
215
+ elasticsearch_port: Optional[int] = 9200,
216
+ kibana_port: Optional[int] = 5602,
217
+ protocol: UrlProtocol = UrlProtocol.HTTPS,
218
+ username: Optional[str] = None,
219
+ password: Optional[str] = None
254
220
  ):
255
221
  super().__init__(
256
222
  elk_hostname=elk_hostname,
@@ -272,10 +238,10 @@ class ElasticSearchOnPrem(ElasticSearch):
272
238
  hosts=[self.elasticsearch_url],
273
239
  http_auth=(self.username, self.password),
274
240
  # ca_certs="/etc/elasticsearch/certs/http_ca.crt",
275
- verify_certs=False, timeout=60
241
+ verify_certs=False
276
242
  )
277
243
  else:
278
- self.elk_client = Elasticsearch(hosts=[f'{self.elasticsearch_url}'], timeout=20)
244
+ self.elk_client = Elasticsearch(hosts=[f'{self.elasticsearch_url}'])
279
245
 
280
246
  if self.elk_client.ping():
281
247
  self.logger.info("Elasticsearch on-prem Connection Successful")
@@ -287,13 +253,13 @@ class ElasticSearchOnPrem(ElasticSearch):
287
253
 
288
254
  class ElasticSearchCloud(ElasticSearch):
289
255
  def __init__(
290
- self,
291
- elk_hostname: str,
292
- elasticsearch_port: Optional[int] = 9200,
293
- kibana_port: Optional[int] = 5602,
294
- protocol: UrlProtocol = UrlProtocol.HTTPS,
295
- username: Optional[str] = None,
296
- password: Optional[str] = None
256
+ self,
257
+ elk_hostname: str,
258
+ elasticsearch_port: Optional[int] = 9200,
259
+ kibana_port: Optional[int] = 5602,
260
+ protocol: UrlProtocol = UrlProtocol.HTTPS,
261
+ username: Optional[str] = None,
262
+ password: Optional[str] = None
297
263
  ):
298
264
  super().__init__(
299
265
  elk_hostname=elk_hostname,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-databases
3
- Version: 0.1.4
3
+ Version: 0.1.5
4
4
  Summary: A Python library for managing and interacting with multiple databases seamlessly.
5
5
  Home-page: https://github.com/aviz92/python-databases
6
6
  Author: Avi Zaguri
@@ -1,10 +1,10 @@
1
1
  python_databases/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- python_databases/elasticsearch_infrastructure/__init__.py,sha256=zLL3GJwgu5a55TgRhyl_mDbjc-qQBWOskRc0y9diqyw,296
3
- python_databases/elasticsearch_infrastructure/elasticsearch.py,sha256=WWlYi7icu3mJLEFDL98XbrG2bhAyBx1u_Xv8EPEgQt8,11176
2
+ python_databases/elasticsearch_infrastructure/__init__.py,sha256=cjs95C1yBZpc3kEdkcXEamDvQGsO6ob5r5ttFCJfDaA,314
3
+ python_databases/elasticsearch_infrastructure/elasticsearch.py,sha256=A5NwzqMIcAyuMLhVIlbpoIYO18jQFqWmtaZ7XyeLzzw,10259
4
4
  python_databases/postgresql_infrastructure/__init__.py,sha256=h1eJx0ogbtcF6UnN2c7uW5iCuadDab3So1VI15y3kWA,150
5
5
  python_databases/postgresql_infrastructure/postgresql.py,sha256=zH8y5w2k2djhHLefDJhlGwa6MtoZKONhJ5vMxIxDdlc,2856
6
- python_databases-0.1.4.dist-info/licenses/LICENSE,sha256=cSikHY6SZFsPZSBizCDAJ0-Bjjzxt-JtX6TVbKxwimo,1067
7
- python_databases-0.1.4.dist-info/METADATA,sha256=i0_YUTvulCbHXObsil2bPLbHsXv3XDh_IxpHsx6QWrg,1792
8
- python_databases-0.1.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
- python_databases-0.1.4.dist-info/top_level.txt,sha256=fXcMUntaYJfKeFS7FjQ6DY4WVXYBsgdrDmhOudYs-vg,17
10
- python_databases-0.1.4.dist-info/RECORD,,
6
+ python_databases-0.1.5.dist-info/licenses/LICENSE,sha256=cSikHY6SZFsPZSBizCDAJ0-Bjjzxt-JtX6TVbKxwimo,1067
7
+ python_databases-0.1.5.dist-info/METADATA,sha256=SccVOpAUU_gPpKNiHgZO2DANGUgjFw7pGcpf59hgUiA,1792
8
+ python_databases-0.1.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
9
+ python_databases-0.1.5.dist-info/top_level.txt,sha256=fXcMUntaYJfKeFS7FjQ6DY4WVXYBsgdrDmhOudYs-vg,17
10
+ python_databases-0.1.5.dist-info/RECORD,,