python-databases 0.1.3__tar.gz → 0.1.5__tar.gz
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.
- {python_databases-0.1.3/python_databases.egg-info → python_databases-0.1.5}/PKG-INFO +1 -1
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases/elasticsearch_infrastructure/__init__.py +6 -1
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases/elasticsearch_infrastructure/elasticsearch.py +112 -145
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases/postgresql_infrastructure/postgresql.py +2 -1
- {python_databases-0.1.3 → python_databases-0.1.5/python_databases.egg-info}/PKG-INFO +1 -1
- {python_databases-0.1.3 → python_databases-0.1.5}/setup.py +1 -1
- {python_databases-0.1.3 → python_databases-0.1.5}/LICENSE +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/MANIFEST.in +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/README.md +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/pyproject.toml +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases/__init__.py +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases/postgresql_infrastructure/__init__.py +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases.egg-info/SOURCES.txt +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases.egg-info/dependency_links.txt +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases.egg-info/requires.txt +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/python_databases.egg-info/top_level.txt +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/requirements.txt +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/setup.cfg +0 -0
- {python_databases-0.1.3 → python_databases-0.1.5}/tests/test_postgres.py +0 -0
|
@@ -9,4 +9,9 @@ from python_databases.elasticsearch_infrastructure.elasticsearch import (
|
|
|
9
9
|
|
|
10
10
|
load_dotenv()
|
|
11
11
|
|
|
12
|
-
__all__ = [
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
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':
|
|
97
|
+
'doc_id': timestamp.strftime('%Y%m%dT%H%M%S%f'),
|
|
103
98
|
'timestamp': timestamp,
|
|
104
|
-
'
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
-
|
|
159
|
-
|
|
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
|
|
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.
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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.
|
|
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
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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 =
|
|
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
|
|
241
|
+
verify_certs=False
|
|
275
242
|
)
|
|
276
243
|
else:
|
|
277
|
-
self.elk_client = Elasticsearch(hosts=[f'{self.elasticsearch_url}']
|
|
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
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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 =
|
|
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 =
|
|
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")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{python_databases-0.1.3 → python_databases-0.1.5}/python_databases.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|