python-databases 0.1.5__tar.gz → 0.1.7__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.
Files changed (21) hide show
  1. {python_databases-0.1.5/python_databases.egg-info → python_databases-0.1.7}/PKG-INFO +19 -19
  2. python_databases-0.1.7/pyproject.toml +44 -0
  3. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases/elasticsearch_infrastructure/__init__.py +3 -8
  4. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases/elasticsearch_infrastructure/elasticsearch.py +64 -85
  5. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases/postgresql_infrastructure/__init__.py +1 -1
  6. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases/postgresql_infrastructure/postgresql.py +23 -18
  7. {python_databases-0.1.5 → python_databases-0.1.7/python_databases.egg-info}/PKG-INFO +19 -19
  8. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases.egg-info/SOURCES.txt +0 -2
  9. python_databases-0.1.7/python_databases.egg-info/requires.txt +10 -0
  10. {python_databases-0.1.5 → python_databases-0.1.7}/tests/test_postgres.py +8 -6
  11. python_databases-0.1.5/pyproject.toml +0 -11
  12. python_databases-0.1.5/python_databases.egg-info/requires.txt +0 -9
  13. python_databases-0.1.5/requirements.txt +0 -9
  14. python_databases-0.1.5/setup.py +0 -37
  15. {python_databases-0.1.5 → python_databases-0.1.7}/LICENSE +0 -0
  16. {python_databases-0.1.5 → python_databases-0.1.7}/MANIFEST.in +0 -0
  17. {python_databases-0.1.5 → python_databases-0.1.7}/README.md +0 -0
  18. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases/__init__.py +0 -0
  19. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases.egg-info/dependency_links.txt +0 -0
  20. {python_databases-0.1.5 → python_databases-0.1.7}/python_databases.egg-info/top_level.txt +0 -0
  21. {python_databases-0.1.5 → python_databases-0.1.7}/setup.cfg +0 -0
@@ -1,32 +1,32 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-databases
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: A Python library for managing and interacting with multiple databases seamlessly.
5
- Home-page: https://github.com/aviz92/python-databases
6
5
  Author: Avi Zaguri
7
- Author-email:
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aviz92/python-databases
8
8
  Project-URL: Repository, https://github.com/aviz92/python-databases
9
+ Project-URL: Issues, https://github.com/aviz92/python-databases/issues
10
+ Keywords: development,databases,ORM,SQL,NoSQL,database management
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: System :: Logging
9
16
  Requires-Python: >=3.11
10
17
  Description-Content-Type: text/markdown
11
18
  License-File: LICENSE
12
- Requires-Dist: setuptools
13
- Requires-Dist: wheel
14
- Requires-Dist: dotenv
15
- Requires-Dist: custom-python-logger
16
- Requires-Dist: retrying
19
+ Requires-Dist: custom-python-logger>=2.0.10
17
20
  Requires-Dist: elasticsearch<9
18
- Requires-Dist: pandas
19
- Requires-Dist: psycopg2-binary
20
- Requires-Dist: pytest
21
- Dynamic: author
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
21
+ Requires-Dist: pandas>=2.3.3
22
+ Requires-Dist: pre-commit>=4.5.0
23
+ Requires-Dist: psycopg2-binary>=2.9.11
24
+ Requires-Dist: pytest>=9.0.1
25
+ Requires-Dist: python-dotenv>=1.2.1
26
+ Requires-Dist: retrying>=1.4.2
27
+ Requires-Dist: setuptools>=80.9.0
28
+ Requires-Dist: wheel>=0.45.1
25
29
  Dynamic: license-file
26
- Dynamic: project-url
27
- Dynamic: requires-dist
28
- Dynamic: requires-python
29
- Dynamic: summary
30
30
 
31
31
  # Python Databases
32
32
  A flexible and extensible Python library that provides a unified interface for interacting with multiple types of databases.
@@ -0,0 +1,44 @@
1
+ [build-system]
2
+ requires = ["setuptools", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "python-databases"
7
+ version = "0.1.7"
8
+ description = "A Python library for managing and interacting with multiple databases seamlessly."
9
+ readme = "README.md"
10
+ requires-python = ">=3.11"
11
+ license = { text = "MIT" }
12
+ authors = [{ name = "Avi Zaguri" },]
13
+ keywords = ["development", "databases", "ORM", "SQL", "NoSQL", "database management"]
14
+
15
+ classifiers = [
16
+ "Development Status :: 4 - Beta",
17
+ "Intended Audience :: Developers",
18
+ "Programming Language :: Python :: 3.11",
19
+ "Operating System :: OS Independent",
20
+ "Topic :: System :: Logging",
21
+ ]
22
+
23
+ dependencies = [
24
+ "custom-python-logger>=2.0.10",
25
+ "elasticsearch<9",
26
+ "pandas>=2.3.3",
27
+ "pre-commit>=4.5.0",
28
+ "psycopg2-binary>=2.9.11",
29
+ "pytest>=9.0.1",
30
+ "python-dotenv>=1.2.1",
31
+ "retrying>=1.4.2",
32
+ "setuptools>=80.9.0",
33
+ "wheel>=0.45.1",
34
+ ]
35
+
36
+ [project.urls]
37
+ Homepage = "https://github.com/aviz92/python-databases"
38
+ Repository = "https://github.com/aviz92/python-databases"
39
+ Issues = "https://github.com/aviz92/python-databases/issues"
40
+
41
+ [tool.setuptools.packages.find]
42
+ where = ["."]
43
+ include = ["python_databases*"]
44
+ namespaces = false
@@ -1,17 +1,12 @@
1
1
  from dotenv import load_dotenv
2
2
 
3
3
  from python_databases.elasticsearch_infrastructure.elasticsearch import (
4
- UrlProtocol,
5
4
  ElasticSearch,
5
+ ElasticSearchCloud,
6
6
  ElasticSearchOnPrem,
7
- ElasticSearchCloud
7
+ UrlProtocol,
8
8
  )
9
9
 
10
10
  load_dotenv()
11
11
 
12
- __all__ = [
13
- 'UrlProtocol',
14
- 'ElasticSearch',
15
- 'ElasticSearchOnPrem',
16
- 'ElasticSearchCloud'
17
- ]
12
+ __all__ = ["UrlProtocol", "ElasticSearch", "ElasticSearchOnPrem", "ElasticSearchCloud"]
@@ -2,9 +2,10 @@ import logging
2
2
  import time
3
3
  from abc import ABC, abstractmethod
4
4
  from copy import deepcopy
5
- from datetime import datetime, timezone
5
+ from datetime import UTC, datetime
6
6
  from enum import Enum
7
- from typing import Optional, Any
7
+ from typing import Any
8
+
8
9
  import pandas as pd
9
10
  from custom_python_logger import get_logger
10
11
  from elasticsearch import Elasticsearch, helpers
@@ -12,20 +13,20 @@ from retrying import retry
12
13
 
13
14
 
14
15
  class UrlProtocol(Enum):
15
- HTTP = 'http'
16
- HTTPS = 'https'
16
+ HTTP = "http"
17
+ HTTPS = "https"
17
18
 
18
19
 
19
20
  class ElasticSearch(ABC):
20
21
  def __init__(
21
22
  self,
22
23
  elk_hostname: str,
23
- elasticsearch_port: Optional[int],
24
- kibana_port: Optional[int],
24
+ elasticsearch_port: int | None,
25
+ kibana_port: int | None,
25
26
  protocol: UrlProtocol,
26
- username: Optional[str],
27
- password: Optional[str]
28
- ):
27
+ username: str | None,
28
+ password: str | None,
29
+ ) -> None:
29
30
  self.logger = get_logger(self.__class__.__name__)
30
31
  self._change_elasticsearch_logger()
31
32
 
@@ -36,69 +37,65 @@ class ElasticSearch(ABC):
36
37
 
37
38
  self.elasticsearch_port = elasticsearch_port
38
39
  if not self.elasticsearch_port:
39
- self.elasticsearch_url = f'{self.protocol.value}://{self.elk_hostname}'
40
+ self.elasticsearch_url = f"{self.protocol.value}://{self.elk_hostname}"
40
41
  else:
41
- self.elasticsearch_url = f'{self.protocol.value}://{self.elk_hostname}:{self.elasticsearch_port}'
42
+ self.elasticsearch_url = f"{self.protocol.value}://{self.elk_hostname}:{self.elasticsearch_port}"
42
43
  self.kibana_port = kibana_port
43
44
 
44
45
  self.elk_client = None
45
46
 
46
47
  @abstractmethod
47
- def connect_to_elasticsearch(self):
48
+ def connect_to_elasticsearch(self) -> None:
48
49
  pass
49
50
 
50
51
  @staticmethod
51
52
  def _change_elasticsearch_logger(debug_level: int = logging.CRITICAL) -> None:
52
- tracer = get_logger('elasticsearch')
53
+ tracer = get_logger("elasticsearch")
53
54
  tracer.setLevel(debug_level)
54
55
 
55
56
  def _set_list_of_docs_quick(self, list_of_docs: list[dict], request_timeout: int = 600) -> None:
56
57
  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
58
+ self.elk_client, list_of_docs, raise_on_error=False, request_timeout=request_timeout, chunk_size=1000
62
59
  ):
63
60
  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}')
61
+ if item[1]["index"]["status"] != 201 or item[1]["index"]["_shards"]["failed"] > 0:
62
+ self.logger.debug(f"Failed document: \n{item}")
66
63
 
67
64
  def _set_list_of_docs_safe(self, list_of_docs: list[dict]) -> None:
68
65
  success_response, failed_response = helpers.bulk(self.elk_client, list_of_docs)
69
66
  if success_response == len(list_of_docs):
70
- self.logger.info('All documents were reported successfully')
67
+ self.logger.info("All documents were reported successfully")
71
68
  else:
72
69
  self.logger.error(
73
- f'Not all documents were reported successfully.\n'
74
- f'The documents that were not reported successfully: {failed_response}'
70
+ f"Not all documents were reported successfully.\n"
71
+ f"The documents that were not reported successfully: {failed_response}"
75
72
  )
76
73
 
77
74
  def post_list_of_docs(self, list_of_docs: list[dict], request_timeout: int = 600, quick: bool = False) -> None:
78
- self.logger.info('Start to report the documents to ELK')
75
+ self.logger.info("Start to report the documents to ELK")
79
76
 
80
77
  if quick:
81
78
  self._set_list_of_docs_quick(list_of_docs=list_of_docs, request_timeout=request_timeout)
82
79
  else:
83
80
  self._set_list_of_docs_safe(list_of_docs=list_of_docs)
84
- self.logger.info('Finish to report the documents to ELK')
81
+ self.logger.info("Finish to report the documents to ELK")
85
82
 
86
83
  @staticmethod
87
84
  def _get_doc_with_basic_info() -> dict:
88
85
  doc = {}
89
86
 
90
- timestamp = datetime.now(tz=timezone.utc)
87
+ timestamp = datetime.now(tz=UTC)
91
88
  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]
89
+ date = date_and_time_str.split("T")[0]
90
+ time_part = date_and_time_str.split("T")[1].split("+")[0]
94
91
 
95
92
  doc.update(
96
93
  {
97
- 'doc_id': timestamp.strftime('%Y%m%dT%H%M%S%f'),
98
- 'timestamp': timestamp,
99
- 'date_and_time_str': date_and_time_str,
100
- 'date_str': date,
101
- 'time_str': time_part,
94
+ "doc_id": timestamp.strftime("%Y%m%dT%H%M%S%f"),
95
+ "timestamp": timestamp,
96
+ "date_and_time_str": date_and_time_str,
97
+ "date_str": date,
98
+ "time_str": time_part,
102
99
  }
103
100
  )
104
101
  return doc
@@ -106,10 +103,10 @@ class ElasticSearch(ABC):
106
103
  @staticmethod
107
104
  def _add_list_values_as_str(doc: dict, column: str, value: Any) -> None:
108
105
  """Add list values as both list and string representation to the provided doc"""
109
- if type(value) is list and all(type(item) is str for item in value):
110
- filtered_value = list(filter(lambda item: item is not None, value))
111
- doc[f'{column}'] = filtered_value
112
- doc[f'{column}_str'] = ', '.join(filtered_value)
106
+ if isinstance(value, list) and all(isinstance(item, str) for item in value):
107
+ filtered_value = [item for item in value if item is not None]
108
+ doc[f"{column}"] = filtered_value
109
+ doc[f"{column}_str"] = ", ".join(filtered_value)
113
110
 
114
111
  def _build_document(self, row: dict) -> dict:
115
112
  doc = self._get_doc_with_basic_info()
@@ -125,41 +122,27 @@ class ElasticSearch(ABC):
125
122
 
126
123
  @staticmethod
127
124
  def _fill_list_of_docs(index_name: str, doc: dict[str, Any]) -> dict:
128
- return {
129
- "_index": index_name,
130
- "_source": deepcopy(doc)
131
- }
125
+ return {"_index": index_name, "_source": deepcopy(doc)}
132
126
 
133
127
  def _prepare_documents_for_bulk(self, data: list[dict], doc_index_name: str) -> list[dict]:
134
128
  list_of_docs = []
135
129
  for index, row in enumerate(data):
136
- self.logger.debug(f'index: {index + 1}/{len(data)}')
130
+ self.logger.debug(f"index: {index + 1}/{len(data)}")
137
131
  doc = self._build_document(row=row)
138
- list_of_docs.append(
139
- self._fill_list_of_docs(index_name=doc_index_name, doc=doc)
140
- )
132
+ list_of_docs.append(self._fill_list_of_docs(index_name=doc_index_name, doc=doc))
141
133
  return list_of_docs
142
134
 
143
135
  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
136
+ self, list_of_docs: list[dict], chunk_size: int = 1000, time_sleep: int = 60, quick: bool = False
149
137
  ) -> None:
150
138
  for i in range(0, len(list_of_docs), chunk_size):
151
- self.logger.info(f'index: {i} - {i + chunk_size} / {len(list_of_docs)}')
152
- chunk = list_of_docs[i:i + chunk_size]
139
+ self.logger.info(f"index: {i} - {i + chunk_size} / {len(list_of_docs)}")
140
+ chunk = list_of_docs[i : i + chunk_size]
153
141
  self.post_list_of_docs(list_of_docs=chunk, quick=quick)
154
142
  time.sleep(time_sleep)
155
143
 
156
144
  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
145
+ self, data: list[dict], doc_index_name: str, chunk_size: int = 1000, time_sleep: int = 1, quick: bool = True
163
146
  ) -> None: # use as the main function to send data to the elastic search
164
147
  list_of_docs = self._prepare_documents_for_bulk(
165
148
  data=data,
@@ -177,9 +160,8 @@ class ElasticSearch(ABC):
177
160
  if self.elk_client.indices.exists(index=index):
178
161
  self.logger.info(f"The index '{index}' exists.")
179
162
  return True
180
- else:
181
- self.logger.info(f"The index '{index}' does not exist.")
182
- return False
163
+ self.logger.info(f"The index '{index}' does not exist.")
164
+ return False
183
165
 
184
166
  def delete_index(self, index: str) -> None:
185
167
  self.elk_client.indices.delete(index=index)
@@ -189,21 +171,20 @@ class ElasticSearch(ABC):
189
171
  if self.check_if_index_exists(index=index):
190
172
  self.delete_index(index=index)
191
173
  return True
192
- else:
193
- return False
174
+ return False
194
175
 
195
176
  def get_documents(self, index: str, query: dict) -> list[dict]:
196
177
  try:
197
178
  res = self.elk_client.search(index=index, body=query)["hits"]["hits"]
198
179
  return [doc["_source"] for doc in res]
199
180
  except Exception as e:
200
- raise Exception(f"Failed to get documents from index '{index}': {str(e)}")
181
+ raise Exception(f"Failed to get documents from index '{index}': {str(e)}") from e
201
182
 
202
183
  def convert_dataframes_to_list_of_docs(self, dataframe: pd.DataFrame) -> list:
203
184
  data = []
204
185
  dataframe_values = dataframe.values.tolist()
205
186
  for index, value in enumerate(dataframe_values):
206
- self.logger.debug(f'index: {index}/{len(dataframe_values)}')
187
+ self.logger.debug(f"index: {index}/{len(dataframe_values)}")
207
188
  data.append(value)
208
189
  return data
209
190
 
@@ -212,19 +193,19 @@ class ElasticSearchOnPrem(ElasticSearch):
212
193
  def __init__(
213
194
  self,
214
195
  elk_hostname: str,
215
- elasticsearch_port: Optional[int] = 9200,
216
- kibana_port: Optional[int] = 5602,
196
+ elasticsearch_port: int | None = 9200,
197
+ kibana_port: int | None = 5602,
217
198
  protocol: UrlProtocol = UrlProtocol.HTTPS,
218
- username: Optional[str] = None,
219
- password: Optional[str] = None
220
- ):
199
+ username: str | None = None,
200
+ password: str | None = None,
201
+ ) -> None:
221
202
  super().__init__(
222
203
  elk_hostname=elk_hostname,
223
204
  elasticsearch_port=elasticsearch_port,
224
205
  kibana_port=kibana_port,
225
206
  protocol=protocol,
226
207
  username=username,
227
- password=password
208
+ password=password,
228
209
  )
229
210
 
230
211
  self.logger = get_logger(self.__class__.__name__)
@@ -232,42 +213,41 @@ class ElasticSearchOnPrem(ElasticSearch):
232
213
  self.connect_to_elasticsearch()
233
214
 
234
215
  @retry(stop_max_attempt_number=3, wait_fixed=180000)
235
- def connect_to_elasticsearch(self):
216
+ def connect_to_elasticsearch(self) -> None:
236
217
  if self.username and self.password:
237
218
  self.elk_client = Elasticsearch(
238
219
  hosts=[self.elasticsearch_url],
239
220
  http_auth=(self.username, self.password),
240
221
  # ca_certs="/etc/elasticsearch/certs/http_ca.crt",
241
- verify_certs=False
222
+ verify_certs=False,
242
223
  )
243
224
  else:
244
- self.elk_client = Elasticsearch(hosts=[f'{self.elasticsearch_url}'])
225
+ self.elk_client = Elasticsearch(hosts=[f"{self.elasticsearch_url}"])
245
226
 
246
227
  if self.elk_client.ping():
247
228
  self.logger.info("Elasticsearch on-prem Connection Successful")
248
229
  else:
249
230
  self.logger.error("Elasticsearch on-prem Connection Failed")
250
- raise Exception(
251
- f'Failed to connect to Elasticsearch on-prem on {self.elasticsearch_url}')
231
+ raise Exception(f"Failed to connect to Elasticsearch on-prem on {self.elasticsearch_url}")
252
232
 
253
233
 
254
234
  class ElasticSearchCloud(ElasticSearch):
255
235
  def __init__(
256
236
  self,
257
237
  elk_hostname: str,
258
- elasticsearch_port: Optional[int] = 9200,
259
- kibana_port: Optional[int] = 5602,
238
+ elasticsearch_port: int | None = 9200,
239
+ kibana_port: int | None = 5602,
260
240
  protocol: UrlProtocol = UrlProtocol.HTTPS,
261
- username: Optional[str] = None,
262
- password: Optional[str] = None
263
- ):
241
+ username: str | None = None,
242
+ password: str | None = None,
243
+ ) -> None:
264
244
  super().__init__(
265
245
  elk_hostname=elk_hostname,
266
246
  elasticsearch_port=elasticsearch_port,
267
247
  kibana_port=kibana_port,
268
248
  protocol=protocol,
269
249
  username=username,
270
- password=password
250
+ password=password,
271
251
  )
272
252
 
273
253
  self.logger = get_logger(self.__class__.__name__)
@@ -275,12 +255,11 @@ class ElasticSearchCloud(ElasticSearch):
275
255
  self.connect_to_elasticsearch()
276
256
 
277
257
  @retry(stop_max_attempt_number=3, wait_fixed=180000)
278
- def connect_to_elasticsearch(self):
258
+ def connect_to_elasticsearch(self) -> None:
279
259
  self.elk_client = Elasticsearch(cloud_id=self.elk_hostname, http_auth=(self.username, self.password))
280
260
 
281
261
  if self.elk_client.ping():
282
262
  self.logger.info("Elasticsearch cloud Connection Successful")
283
263
  else:
284
264
  self.logger.error("Elasticsearch cloud Connection Failed")
285
- raise Exception(
286
- f'Failed to connect to Elasticsearch cloud on {self.elk_hostname}')
265
+ raise Exception(f"Failed to connect to Elasticsearch cloud on {self.elk_hostname}")
@@ -4,4 +4,4 @@ from python_databases.postgresql_infrastructure.postgresql import PostgreSQL
4
4
 
5
5
  load_dotenv()
6
6
 
7
- __all__ = ['PostgreSQL']
7
+ __all__ = ["PostgreSQL"]
@@ -1,21 +1,21 @@
1
- import logging
2
1
  import os
3
- from typing import Optional, Union, Any
2
+ from types import TracebackType
3
+ from typing import Any
4
+
4
5
  import psycopg2
5
6
  from custom_python_logger import get_logger
6
7
  from psycopg2 import extras
7
- from retrying import retry
8
8
 
9
9
 
10
10
  class PostgreSQL:
11
11
  def __init__(
12
12
  self,
13
- name: Optional[str] = None,
14
- host: Optional[str] = None,
15
- port: Optional[str] = None,
16
- username: Optional[str] = None,
17
- password: Optional[str] = None
18
- ):
13
+ name: str | None = None,
14
+ host: str | None = None,
15
+ port: str | None = None,
16
+ username: str | None = None,
17
+ password: str | None = None,
18
+ ) -> None:
19
19
  self.logger = get_logger(self.__class__.__name__)
20
20
 
21
21
  self.name = name or os.getenv("POSTGRESQL_NAME")
@@ -30,21 +30,21 @@ class PostgreSQL:
30
30
  self.create_connection()
31
31
 
32
32
  @property
33
- def client(self):
33
+ def client(self) -> psycopg2.extensions.connection:
34
34
  if not self._client or self._client.closed:
35
35
  self.logger.warning("Re-establishing PostgreSQL connection")
36
36
  self.create_connection()
37
37
  return self._client
38
38
 
39
39
  @property
40
- def cursor(self):
40
+ def cursor(self) -> psycopg2.extensions.cursor:
41
41
  if not self._cursor or self._cursor.closed:
42
42
  self.logger.warning("Re-creating PostgreSQL cursor")
43
43
  self.create_connection()
44
44
  return self._cursor
45
45
 
46
46
  # @retry(stop_max_attempt_number=3, wait_fixed=180000)
47
- def create_connection(self):
47
+ def create_connection(self) -> None:
48
48
  self.logger.info(f"Create connection to {self.host}:{self.port}")
49
49
 
50
50
  self._client = psycopg2.connect(
@@ -56,15 +56,15 @@ class PostgreSQL:
56
56
  )
57
57
  self._cursor = self._client.cursor(cursor_factory=extras.DictCursor)
58
58
 
59
- def execute_query(self, query: str, params: Optional[Union[tuple, dict[str, Any]]] = None) -> None:
59
+ def execute_query(self, query: str, params: tuple | dict[str, Any] | None = None) -> None:
60
60
  self.logger.debug(f"Fetch rows from DB by query {query} with params {params}")
61
61
  try:
62
62
  self.cursor.execute(query, params)
63
63
  except psycopg2.ProgrammingError as e:
64
64
  raise psycopg2.ProgrammingError(f"Error executing query: {query}") from e
65
- self.logger.debug(f"Finished to fetch rows by query")
65
+ self.logger.debug("Finished to fetch rows by query")
66
66
 
67
- def fetch_all(self, query: str, params: Optional[Union[tuple, dict[str, Any]]] = None) -> list[dict[str, Any]]:
67
+ def fetch_all(self, query: str, params: tuple | dict[str, Any] | None = None) -> list[dict[str, Any]]:
68
68
  self.execute_query(query, params)
69
69
  try:
70
70
  results = self.cursor.fetchall()
@@ -73,12 +73,17 @@ class PostgreSQL:
73
73
  self.logger.debug(f"Fetched {len(results)} rows")
74
74
  return results
75
75
 
76
- def close(self):
76
+ def close(self) -> None:
77
77
  self.cursor.close()
78
78
  self.client.close()
79
79
 
80
- def __enter__(self):
80
+ def __enter__(self) -> "PostgreSQL":
81
81
  return self
82
82
 
83
- def __exit__(self, exc_type, exc_val, exc_tb):
83
+ def __exit__(
84
+ self,
85
+ exc_type: type[BaseException] | None,
86
+ exc_val: BaseException | None,
87
+ exc_tb: TracebackType | None,
88
+ ) -> None:
84
89
  self.close()
@@ -1,32 +1,32 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: python-databases
3
- Version: 0.1.5
3
+ Version: 0.1.7
4
4
  Summary: A Python library for managing and interacting with multiple databases seamlessly.
5
- Home-page: https://github.com/aviz92/python-databases
6
5
  Author: Avi Zaguri
7
- Author-email:
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/aviz92/python-databases
8
8
  Project-URL: Repository, https://github.com/aviz92/python-databases
9
+ Project-URL: Issues, https://github.com/aviz92/python-databases/issues
10
+ Keywords: development,databases,ORM,SQL,NoSQL,database management
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Topic :: System :: Logging
9
16
  Requires-Python: >=3.11
10
17
  Description-Content-Type: text/markdown
11
18
  License-File: LICENSE
12
- Requires-Dist: setuptools
13
- Requires-Dist: wheel
14
- Requires-Dist: dotenv
15
- Requires-Dist: custom-python-logger
16
- Requires-Dist: retrying
19
+ Requires-Dist: custom-python-logger>=2.0.10
17
20
  Requires-Dist: elasticsearch<9
18
- Requires-Dist: pandas
19
- Requires-Dist: psycopg2-binary
20
- Requires-Dist: pytest
21
- Dynamic: author
22
- Dynamic: description
23
- Dynamic: description-content-type
24
- Dynamic: home-page
21
+ Requires-Dist: pandas>=2.3.3
22
+ Requires-Dist: pre-commit>=4.5.0
23
+ Requires-Dist: psycopg2-binary>=2.9.11
24
+ Requires-Dist: pytest>=9.0.1
25
+ Requires-Dist: python-dotenv>=1.2.1
26
+ Requires-Dist: retrying>=1.4.2
27
+ Requires-Dist: setuptools>=80.9.0
28
+ Requires-Dist: wheel>=0.45.1
25
29
  Dynamic: license-file
26
- Dynamic: project-url
27
- Dynamic: requires-dist
28
- Dynamic: requires-python
29
- Dynamic: summary
30
30
 
31
31
  # Python Databases
32
32
  A flexible and extensible Python library that provides a unified interface for interacting with multiple types of databases.
@@ -2,8 +2,6 @@ LICENSE
2
2
  MANIFEST.in
3
3
  README.md
4
4
  pyproject.toml
5
- requirements.txt
6
- setup.py
7
5
  python_databases/__init__.py
8
6
  python_databases.egg-info/PKG-INFO
9
7
  python_databases.egg-info/SOURCES.txt
@@ -0,0 +1,10 @@
1
+ custom-python-logger>=2.0.10
2
+ elasticsearch<9
3
+ pandas>=2.3.3
4
+ pre-commit>=4.5.0
5
+ psycopg2-binary>=2.9.11
6
+ pytest>=9.0.1
7
+ python-dotenv>=1.2.1
8
+ retrying>=1.4.2
9
+ setuptools>=80.9.0
10
+ wheel>=0.45.1
@@ -1,7 +1,9 @@
1
1
  # test_postgresql.py
2
2
 
3
+ from unittest.mock import MagicMock, patch
4
+
3
5
  import pytest
4
- from unittest.mock import patch, MagicMock
6
+
5
7
  from python_databases.postgresql_infrastructure import PostgreSQL
6
8
 
7
9
 
@@ -15,21 +17,21 @@ def mock_connection():
15
17
  yield mock_conn, mock_cursor
16
18
 
17
19
 
18
- def test_connection_created(mock_connection):
20
+ def test_connection_created(mock_connection: MagicMock): # pylint: disable=W0621
19
21
  conn, _ = mock_connection
20
22
  db = PostgreSQL(name="test_db", host="localhost", port="5432", username="user", password="pass")
21
23
  assert db.client == conn
22
24
  assert conn.cursor.call_count == 2
23
25
 
24
26
 
25
- def test_execute_query_success(mock_connection):
27
+ def test_execute_query_success(mock_connection: MagicMock): # pylint: disable=W0621
26
28
  _, cursor = mock_connection
27
29
  db = PostgreSQL(name="test_db", host="localhost", port="5432", username="user", password="pass")
28
30
  db.execute_query("SELECT 1", None)
29
31
  cursor.execute.assert_called_with("SELECT 1", None)
30
32
 
31
33
 
32
- def test_fetch_all_success(mock_connection):
34
+ def test_fetch_all_success(mock_connection: MagicMock): # pylint: disable=W0621
33
35
  _, cursor = mock_connection
34
36
  cursor.fetchall.return_value = [{"id": 1}]
35
37
  db = PostgreSQL(name="test_db", host="localhost", port="5432", username="user", password="pass")
@@ -38,9 +40,9 @@ def test_fetch_all_success(mock_connection):
38
40
  assert results == [{"id": 1}]
39
41
 
40
42
 
41
- def test_context_manager_closes_connection(mock_connection):
43
+ def test_context_manager_closes_connection(mock_connection: MagicMock): # pylint: disable=W0621
42
44
  conn, cursor = mock_connection
43
- with PostgreSQL(name="test_db", host="localhost", port="5432", username="user", password="pass") as db:
45
+ with PostgreSQL(name="test_db", host="localhost", port="5432", username="user", password="pass") as _:
44
46
  pass
45
47
  cursor.close.assert_called_once()
46
48
  conn.close.assert_called_once()
@@ -1,11 +0,0 @@
1
- [build-system]
2
- requires = ["setuptools", "wheel"]
3
- build-backend = "setuptools.build_meta"
4
-
5
- [tool.black]
6
- line-length = 100
7
- target-version = ["py39", "py310", "py311"]
8
-
9
- [tool.isort]
10
- profile = "black"
11
- line_length = 100
@@ -1,9 +0,0 @@
1
- setuptools
2
- wheel
3
- dotenv
4
- custom-python-logger
5
- retrying
6
- elasticsearch<9
7
- pandas
8
- psycopg2-binary
9
- pytest
@@ -1,9 +0,0 @@
1
- setuptools
2
- wheel
3
- dotenv
4
- custom-python-logger
5
- retrying
6
- elasticsearch<9
7
- pandas
8
- psycopg2-binary
9
- pytest
@@ -1,37 +0,0 @@
1
- from setuptools import setup, find_packages
2
-
3
- package_version = '0.1.5'
4
-
5
- package_name = 'python-databases'
6
- package_description = 'A Python library for managing and interacting with multiple databases seamlessly.'
7
-
8
- package_name_ = package_name.replace('-', '_')
9
- package_long_description_content_type = 'text/markdown'
10
- package_url = f'https://github.com/aviz92/{package_name}'
11
- package_python_requires = '>=3.11'
12
- package_author = 'Avi Zaguri'
13
-
14
- with open('requirements.txt', 'r') as file:
15
- package_install_requires = [
16
- line.strip() for line in file.readlines() if line.strip() and not line.startswith('#')
17
- ]
18
-
19
- with open('README.md', 'r') as file:
20
- package_long_description = file.read()
21
-
22
- setup(
23
- name=package_name,
24
- version=package_version,
25
- packages=find_packages(include=[package_name_, f'{package_name_}.*']),
26
- install_requires=package_install_requires,
27
- author=package_author,
28
- author_email='',
29
- description=package_description,
30
- long_description=package_long_description,
31
- long_description_content_type=package_long_description_content_type,
32
- url=package_url,
33
- project_urls={
34
- 'Repository': package_url,
35
- },
36
- python_requires=package_python_requires,
37
- )