Skip to content

Commit 04991e5

Browse files
authored
Merge pull request #2 from miksyr/handle_nan_better
handing nan better and config update
2 parents 40f9c9b + 6c6e68d commit 04991e5

File tree

10 files changed

+103
-60
lines changed

10 files changed

+103
-60
lines changed

.flake8

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[flake8]
2+
ignore = E501, E203, W503
3+
exclude =
4+
.git,
5+
__pycache__,
6+
build,
7+
dist,
8+
scripts,
9+
tests
10+
max-complexity = 10

.pre-commit-config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
2+
repos:
3+
- repo: https://github.com/pre-commit/pre-commit-hooks
4+
rev: v3.2.0
5+
hooks:
6+
- id: trailing-whitespace
7+
- id: end-of-file-fixer
8+
- id: check-yaml
9+
- id: check-added-large-files
10+
- id: detect-private-key
11+
- id: requirements-txt-fixer
12+
- repo: https://github.com/psf/black
13+
rev: 21.5b2
14+
hooks:
15+
- id: black

easy_postgres_engine/postgres_engine.py

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import logging
2+
from typing import Any, Dict, Optional, Union
23

34
import pandas as pd
45
import psycopg2
56
import psycopg2.extras
67

78
from .retry_decorator import retry
9+
from .utils.dataframe_functions import replace_nan_with_none_in_dataframe
810

911

1012
class PostgresEngine:
11-
12-
def __init__(self, databaseName: str, user: str, password: str, host: str = 'localhost', port: int = 5432):
13+
def __init__(self, databaseName: str, user: str, password: str, host: str = "localhost", port: int = 5432):
1314
"""
1415
Class for accessing Postgres databases more easily.
1516
@@ -27,72 +28,79 @@ def __init__(self, databaseName: str, user: str, password: str, host: str = 'loc
2728
self.connection = None
2829
self.cursor = None
2930

30-
def _get_connection(self):
31+
def _get_connection(self) -> None:
3132
try:
32-
self.connection = psycopg2.connect(user=self.user, password=self.password, host=self.host, port=self.port, database=self.databaseName)
33+
self.connection = psycopg2.connect(
34+
user=self.user, password=self.password, host=self.host, port=self.port, database=self.databaseName
35+
)
3336
except Exception as ex:
34-
logging.exception(f'Error connecting to PostgreSQL {ex}')
37+
logging.exception(f"Error connecting to PostgreSQL {ex}")
3538
raise ex
3639

37-
def _get_cursor(self, isInsertionQuery: bool):
40+
def _get_cursor(self, isInsertionQuery: bool) -> None:
3841
if isInsertionQuery:
3942
self.cursor = self.connection.cursor()
4043
else:
4144
self.cursor = self.connection.cursor(cursor_factory=psycopg2.extras.RealDictCursor)
4245

43-
def _close_connection(self):
46+
def _close_connection(self) -> None:
4447
self.connection.close()
4548

46-
def _close_cursor(self):
49+
def _close_cursor(self) -> None:
4750
self.cursor.close()
4851

49-
def close(self):
52+
def close(self) -> None:
5053
if self.connection is not None:
5154
self._close_connection()
5255
if self.cursor is not None:
5356
self._close_cursor()
5457

55-
def create_table(self, schema: str):
58+
def create_table(self, schema: str) -> None:
5659
self._get_connection()
5760
self._get_cursor(isInsertionQuery=True)
5861
self.cursor.execute(schema)
5962
try:
6063
self.connection.commit()
6164
except Exception as ex:
62-
logging.exception(f'error: {ex} \nschemaQuery: {schema}')
65+
logging.exception(f"error: {ex} \nschemaQuery: {schema}")
6366
raise ex
6467
finally:
6568
self.close()
6669

67-
def create_index(self, tableName: str, column: str):
70+
def create_index(self, tableName: str, column: str) -> None:
6871
self._get_connection()
6972
self._get_cursor(isInsertionQuery=True)
70-
indexQuery = f'CREATE INDEX IF NOT EXISTS {tableName}_{column} ON {tableName}({column});'
73+
indexQuery = f"CREATE INDEX IF NOT EXISTS {tableName}_{column} ON {tableName}({column});"
7174
self.cursor.execute(indexQuery)
7275
try:
7376
self.connection.commit()
7477
except Exception as ex:
75-
logging.exception(f'error: {ex} \nindexQuery: {indexQuery}')
78+
logging.exception(f"error: {ex} \nindexQuery: {indexQuery}")
7679
raise ex
7780
finally:
7881
self.close()
7982

8083
@retry(numRetries=5, retryDelaySeconds=3, backoffScalingFactor=2)
81-
def run_select_query(self, query: str, parameters: dict = None):
84+
def run_select_query_with_retry(self, query: str, parameters: Optional[Dict[str, Any]] = None) -> pd.DataFrame:
85+
return self.run_select_query(query=query, parameters=parameters)
86+
87+
def run_select_query(self, query: str, parameters: Optional[Dict[str, Any]] = None) -> pd.DataFrame:
8288
self._get_connection()
8389
self._get_cursor(isInsertionQuery=False)
8490
self.cursor.execute(query, parameters)
8591
outputs = self.cursor.fetchall()
8692
self.close()
8793
outputDataframe = pd.DataFrame(outputs)
88-
return outputDataframe.where(outputDataframe.notnull(), None).dropna(axis=0, how='all')
94+
return replace_nan_with_none_in_dataframe(dataframe=outputDataframe)
8995

9096
@retry(numRetries=5, retryDelaySeconds=3, backoffScalingFactor=2)
91-
def run_update_query(self, query: str, parameters: dict = None, returnId: bool = True):
97+
def run_update_query(
98+
self, query: str, parameters: Optional[Dict[str, Any]] = None, returnId: bool = True
99+
) -> Union[None, int]:
92100
self._get_connection()
93101
self._get_cursor(isInsertionQuery=True)
94102
if returnId:
95-
query = f'{query}\nRETURNING id'
103+
query = f"{query}\nRETURNING id"
96104
self.cursor.execute(query, parameters)
97105
if returnId:
98106
insertedId = self.cursor.fetchone()[0]
@@ -101,7 +109,7 @@ def run_update_query(self, query: str, parameters: dict = None, returnId: bool =
101109
try:
102110
self.connection.commit()
103111
except Exception as ex:
104-
logging.exception(f'error: {ex} \nquery: {query} \nparameters: {parameters}')
112+
logging.exception(f"error: {ex} \nquery: {query} \nparameters: {parameters}")
105113
raise ex
106114
finally:
107115
self.close()

easy_postgres_engine/retry_decorator.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44

55

66
def retry(numRetries: int = 5, retryDelaySeconds: int = 3, backoffScalingFactor: int = 2):
7-
87
def retry_decorator(func):
98
@wraps(func)
109
def retry_function(*args, **kwargs):
@@ -13,11 +12,13 @@ def retry_function(*args, **kwargs):
1312
try:
1413
return func(*args, **kwargs)
1514
except Exception as ex:
16-
exceptionMessage = f'{ex}, Retrying in {currentDelay} seconds...'
15+
exceptionMessage = f"{ex}, Retrying in {currentDelay} seconds..."
1716
logging.warning(exceptionMessage)
1817
sleep(currentDelay)
1918
numTries -= 1
2019
currentDelay *= backoffScalingFactor
2120
return func(*args, **kwargs)
21+
2222
return retry_function
23+
2324
return retry_decorator
Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,18 @@
11
import testing.postgresql
22
from unittest import TestCase
33

4-
from easy_postgres_engine.tests.test_table_schema import TEST_TABLE_SCHEMA
5-
from easy_postgres_engine.postgres_engine import PostgresEngine
4+
from .test_table_schema import TEST_TABLE_SCHEMA
5+
from ..postgres_engine import PostgresEngine
66

77

88
def create_table(postgresqlConnection):
99
config = postgresqlConnection.dsn()
1010
dbEngine = PostgresEngine(
11-
databaseName=config['database'],
12-
user=config['user'],
13-
password=config.get('password'),
14-
port=config['port'],
15-
host=config['host']
11+
databaseName=config["database"],
12+
user=config["user"],
13+
password=config.get("password"),
14+
port=config["port"],
15+
host=config["host"],
1616
)
1717
dbEngine.create_table(schema=TEST_TABLE_SCHEMA)
1818

@@ -27,24 +27,23 @@ def tearDownModule():
2727

2828

2929
class TestPostgresEngine(TestCase):
30-
31-
def __init__(self, methodName='runTest'):
30+
def __init__(self, methodName="runTest"):
3231
super(TestPostgresEngine, self).__init__(methodName=methodName)
3332
self.firstCustomerId = 10
34-
self.firstCustomerName = 'Mary'
33+
self.firstCustomerName = "Mary"
3534
self.secondCustomerId = 50
36-
self.secondCustomerName = 'John'
35+
self.secondCustomerName = "John"
3736

3837
def setUp(self):
3938
super().setUp()
4039
self.postgresql = Postgresql()
4140
config = self.postgresql.dsn()
4241
self.dbEngine = PostgresEngine(
43-
databaseName=config['database'],
44-
user=config['user'],
45-
password=config.get('password'),
46-
port=config['port'],
47-
host=config['host']
42+
databaseName=config["database"],
43+
user=config["user"],
44+
password=config.get("password"),
45+
port=config["port"],
46+
host=config["host"],
4847
)
4948

5049
def tearDown(self):
@@ -60,19 +59,18 @@ def test_engine(self):
6059
(%(customerId)s, %(customerName)s)
6160
"""
6261
insertedId1 = self.dbEngine.run_update_query(
63-
query=TEST_INSERTION_QUERY,
64-
parameters={'customerId': self.firstCustomerId, 'customerName': self.firstCustomerName}
62+
query=TEST_INSERTION_QUERY, parameters={"customerId": self.firstCustomerId, "customerName": self.firstCustomerName}
6563
)
6664
self.assertEqual(insertedId1, 1)
6765
insertedId2 = self.dbEngine.run_update_query(
6866
query=TEST_INSERTION_QUERY,
69-
parameters={'customerId': self.secondCustomerId, 'customerName': self.secondCustomerName}
67+
parameters={"customerId": self.secondCustomerId, "customerName": self.secondCustomerName},
7068
)
7169
self.assertEqual(insertedId2, 2)
7270

73-
queryResults = self.dbEngine.run_select_query(query='SELECT * FROM tbl_example')
74-
self.assertSequenceEqual(list(queryResults['customer_id'].values), [self.firstCustomerId, self.secondCustomerId])
75-
self.assertSequenceEqual(list(queryResults['customer_name'].values), [self.firstCustomerName, self.secondCustomerName])
71+
queryResults = self.dbEngine.run_select_query(query="SELECT * FROM tbl_example")
72+
self.assertSequenceEqual(list(queryResults["customer_id"].values), [self.firstCustomerId, self.secondCustomerId])
73+
self.assertSequenceEqual(list(queryResults["customer_name"].values), [self.firstCustomerName, self.secondCustomerName])
7674

7775
specificQueryResults = self.dbEngine.run_select_query(
7876
query="""
@@ -83,7 +81,7 @@ def test_engine(self):
8381
WHERE
8482
customer_id = %(customerId)s
8583
""",
86-
parameters={'customerId': self.firstCustomerId}
84+
parameters={"customerId": self.firstCustomerId},
8785
)
8886
self.assertEqual(len(specificQueryResults), 1)
89-
self.assertEqual(specificQueryResults['customer_name'].iloc[0], self.firstCustomerName)
87+
self.assertEqual(specificQueryResults["customer_name"].iloc[0], self.firstCustomerName)

easy_postgres_engine/utils/__init__.py

Whitespace-only changes.
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import numpy as np
2+
import pandas as pd
3+
4+
5+
def replace_nan_with_none_in_dataframe(dataframe: pd.DataFrame) -> pd.DataFrame:
6+
dataframe = dataframe.where(dataframe.notnull(), None).dropna(axis=0, how="all")
7+
return dataframe.replace({np.nan: None})

pyproject.toml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
[tool.black]
2+
line-length = 127
3+
target-version = ['py36', 'py37', 'py38']
4+
include = '\.pyi?$'

requirements.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
black==21.5b2
2+
flake8==3.9.2
3+
nose2[coverage_plugin]==0.10.0
4+
numpy==1.19.2
15
pandas==1.3.2
26
psycopg2-binary==2.9.1
37
testing.postgresql==1.3.0

setup.py

Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,20 @@
11
from distutils.core import setup
22

33
setup(
4-
name='easy_postgres_engine',
5-
packages=['easy_postgres_engine'],
6-
version='0.2',
7-
description='Engine class for easier connections to postgres databases',
8-
author='Michael Doran',
9-
author_email='mikrdoran@gmail.com',
10-
url='https://github.com/miksyr/easy_postgres_engine',
11-
download_url='https://github.com/miksyr/easy_postgres_engine/archive/v_02.tar.gz',
12-
keywords=['postgreSQL', 'postgres'],
13-
install_requires=[
14-
'pandas==1.3.2',
15-
'psycopg2-binary==2.9.1',
16-
'testing.postgresql==1.3.0'
17-
],
4+
name="easy_postgres_engine",
5+
packages=["easy_postgres_engine"],
6+
version="0.2",
7+
description="Engine class for easier connections to postgres databases",
8+
author="Michael Doran",
9+
author_email="mikrdoran@gmail.com",
10+
url="https://github.com/miksyr/easy_postgres_engine",
11+
download_url="https://github.com/miksyr/easy_postgres_engine/archive/v_02.tar.gz",
12+
keywords=["postgreSQL", "postgres"],
13+
install_requires=["pandas==1.3.2", "psycopg2-binary==2.9.1", "testing.postgresql==1.3.0"],
1814
classifiers=[
1915
"Programming Language :: Python :: 3",
2016
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
2117
"Operating System :: OS Independent",
2218
],
23-
python_requires='>=3.8'
19+
python_requires=">=3.8",
2420
)

0 commit comments

Comments
 (0)