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