dbhydra 0.3.0__tar.gz → 2.2.0__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.
- {dbhydra-0.3.0 → dbhydra-2.2.0}/PKG-INFO +3 -6
- {dbhydra-0.3.0 → dbhydra-2.2.0}/README.md +2 -1
- dbhydra-2.2.0/dbhydra/__init__.py +3 -0
- dbhydra-2.2.0/dbhydra/dbhydra_core.py +63 -0
- dbhydra-2.2.0/dbhydra/src/abstract_db.py +206 -0
- dbhydra-2.2.0/dbhydra/src/abstract_table.py +435 -0
- dbhydra-2.2.0/dbhydra/src/bigquery_db.py +63 -0
- dbhydra-2.2.0/dbhydra/src/errors/__init__.py +0 -0
- dbhydra-2.2.0/dbhydra/src/errors/exceptions.py +2 -0
- dbhydra-2.2.0/dbhydra/src/migrator.py +395 -0
- dbhydra-2.2.0/dbhydra/src/mongo_db.py +57 -0
- dbhydra-2.2.0/dbhydra/src/mysql_db.py +77 -0
- dbhydra-2.2.0/dbhydra/src/postgres_db.py +55 -0
- dbhydra-2.2.0/dbhydra/src/sqlserver_db.py +91 -0
- dbhydra-2.2.0/dbhydra/src/tables.py +1142 -0
- dbhydra-2.2.0/dbhydra/src/xlsx_db.py +112 -0
- dbhydra-2.2.0/dbhydra/tests/__init__.py +0 -0
- dbhydra-2.2.0/dbhydra/tests/test_cases.py +20 -0
- dbhydra-2.2.0/dbhydra/tests/test_mongo.py +64 -0
- dbhydra-2.2.0/dbhydra/tests/test_sql.py +101 -0
- {dbhydra-0.3.0 → dbhydra-2.2.0}/dbhydra.egg-info/PKG-INFO +3 -6
- dbhydra-2.2.0/dbhydra.egg-info/SOURCES.txt +27 -0
- {dbhydra-0.3.0 → dbhydra-2.2.0}/dbhydra.egg-info/requires.txt +1 -0
- {dbhydra-0.3.0 → dbhydra-2.2.0}/setup.py +2 -2
- dbhydra-0.3.0/dbhydra/dbhydra_builder.py +0 -57
- dbhydra-0.3.0/dbhydra/dbhydra_core.py +0 -1472
- dbhydra-0.3.0/dbhydra/dbhydra_liquibase.py +0 -56
- dbhydra-0.3.0/dbhydra/dbhydra_model.py +0 -42
- dbhydra-0.3.0/dbhydra/dbhydra_mysql_example - kopie.py +0 -19
- dbhydra-0.3.0/dbhydra/dbhydra_mysql_example.py +0 -132
- dbhydra-0.3.0/dbhydra/dbhydra_pandas_framework.py +0 -19
- dbhydra-0.3.0/dbhydra/migrator_example.py +0 -101
- dbhydra-0.3.0/dbhydra/mongo.py +0 -41
- dbhydra-0.3.0/dbhydra/test.py +0 -45
- dbhydra-0.3.0/dbhydra.egg-info/SOURCES.txt +0 -19
- {dbhydra-0.3.0 → dbhydra-2.2.0}/LICENSE +0 -0
- {dbhydra-0.3.0/dbhydra → dbhydra-2.2.0/dbhydra/src}/__init__.py +0 -0
- {dbhydra-0.3.0 → dbhydra-2.2.0}/dbhydra.egg-info/dependency_links.txt +0 -0
- {dbhydra-0.3.0 → dbhydra-2.2.0}/dbhydra.egg-info/top_level.txt +0 -0
- {dbhydra-0.3.0 → dbhydra-2.2.0}/setup.cfg +0 -0
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: dbhydra
|
|
3
|
-
Version:
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Data science friendly ORM combining Python
|
|
5
5
|
Home-page: https://github.com/DovaX/dbhydra
|
|
6
6
|
Author: DovaX
|
|
7
7
|
Author-email: dovax.ai@gmail.com
|
|
8
|
-
License: UNKNOWN
|
|
9
|
-
Platform: UNKNOWN
|
|
10
8
|
Classifier: Programming Language :: Python :: 3
|
|
11
9
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
10
|
Classifier: Operating System :: OS Independent
|
|
@@ -16,7 +14,7 @@ License-File: LICENSE
|
|
|
16
14
|
|
|
17
15
|
# dbhydra
|
|
18
16
|
Data science friendly ORM (Object Relational Mapping) library combining Python, Pandas, and various SQL dialects
|
|
19
|
-
For full documentation see official [documentation](http://app.forloop.ai/dbhydra/documentation)
|
|
17
|
+
For full documentation see official [documentation](http://app.forloop.ai/dbhydra/documentation) - currently unavailable but we're working on it!
|
|
20
18
|
|
|
21
19
|
## Installation
|
|
22
20
|
|
|
@@ -62,6 +60,7 @@ print(table_test.columns)
|
|
|
62
60
|
|
|
63
61
|
table2 = dh.Table(db1,"test_new",["id","test2"],["int","nvarchar(20)"])
|
|
64
62
|
#table2.create()
|
|
63
|
+
#table2.drop()
|
|
65
64
|
```
|
|
66
65
|
## Current scope
|
|
67
66
|
Aims: Easy integration with Pandas, SQL SERVER/MySQL database, and exports/imports to/from excel/CSV format
|
|
@@ -76,5 +75,3 @@ Pull requests are welcome. For major changes, please open an issue first to disc
|
|
|
76
75
|
|
|
77
76
|
## License
|
|
78
77
|
[MIT](https://choosealicense.com/licenses/mit/)
|
|
79
|
-
|
|
80
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# dbhydra
|
|
2
2
|
Data science friendly ORM (Object Relational Mapping) library combining Python, Pandas, and various SQL dialects
|
|
3
|
-
For full documentation see official [documentation](http://app.forloop.ai/dbhydra/documentation)
|
|
3
|
+
For full documentation see official [documentation](http://app.forloop.ai/dbhydra/documentation) - currently unavailable but we're working on it!
|
|
4
4
|
|
|
5
5
|
## Installation
|
|
6
6
|
|
|
@@ -46,6 +46,7 @@ print(table_test.columns)
|
|
|
46
46
|
|
|
47
47
|
table2 = dh.Table(db1,"test_new",["id","test2"],["int","nvarchar(20)"])
|
|
48
48
|
#table2.create()
|
|
49
|
+
#table2.drop()
|
|
49
50
|
```
|
|
50
51
|
## Current scope
|
|
51
52
|
Aims: Easy integration with Pandas, SQL SERVER/MySQL database, and exports/imports to/from excel/CSV format
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"""DB Hydra ORM"""
|
|
2
|
+
import abc
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
import pandas as pd
|
|
6
|
+
from pydantic import BaseModel
|
|
7
|
+
|
|
8
|
+
##### Do not remove imports - they are expored in the package
|
|
9
|
+
from dbhydra.src.sqlserver_db import SqlServerDb, db
|
|
10
|
+
from dbhydra.src.mysql_db import MysqlDb, Mysqldb
|
|
11
|
+
from dbhydra.src.bigquery_db import BigQueryDb
|
|
12
|
+
from dbhydra.src.mongo_db import MongoDb
|
|
13
|
+
from dbhydra.src.postgres_db import PostgresDb
|
|
14
|
+
from dbhydra.src.xlsx_db import XlsxDb, XlsxDB
|
|
15
|
+
from dbhydra.src.abstract_db import AbstractDb
|
|
16
|
+
from dbhydra.src.tables import SqlServerTable, PostgresTable, MysqlTable, XlsxTable, AbstractTable, MongoTable, BigQueryTable, Table, AbstractSelectable, AbstractJoinable
|
|
17
|
+
##### Do not remove imports - they are expored in the package
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Jsonable(str):
|
|
21
|
+
"""Is used as type in python_database_type_mapping"""
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
# dataframe - dictionary auxiliary functions
|
|
27
|
+
def df_to_dict(df, column1, column2):
|
|
28
|
+
dictionary = df.set_index(column1).to_dict()[column2]
|
|
29
|
+
return (dictionary)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def dict_to_df(dictionary, column1, column2):
|
|
33
|
+
df = pd.DataFrame(list(dictionary.items()), columns=[column1, column2])
|
|
34
|
+
return (df)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
from pydantic_core import CoreSchema, core_schema
|
|
39
|
+
from pydantic import GetCoreSchemaHandler
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class AbstractModel(abc.ABC, BaseModel):
|
|
43
|
+
@classmethod
|
|
44
|
+
def generate_dbhydra_table(cls, table_class: AbstractTable, db1, name, id_column_name="id"):
|
|
45
|
+
column_type_dict = create_table_structure_dict(cls, id_column_name=id_column_name)
|
|
46
|
+
dbhydra_table = table_class.init_from_column_type_dict(db1, name, column_type_dict, id_column_name=id_column_name)
|
|
47
|
+
return dbhydra_table
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def __get_pydantic_core_schema__(cls, source_type: Any, handler: GetCoreSchemaHandler) -> CoreSchema:
|
|
51
|
+
return core_schema.no_info_after_validator_function(cls, handler(str))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def create_table_structure_dict(api_class_instance, id_column_name="id"):
|
|
56
|
+
"""
|
|
57
|
+
Accepts instance of API data class (e.g. APIDatabase) and converts it to dictionary {attribute_name: attribute_type}
|
|
58
|
+
"""
|
|
59
|
+
table_structure_dict = {id_column_name: "int"}
|
|
60
|
+
table_structure_dict = {**table_structure_dict,
|
|
61
|
+
**{attribute_name: attribute_type.__name__ for attribute_name, attribute_type in api_class_instance.__annotations__.items()}}
|
|
62
|
+
|
|
63
|
+
return table_structure_dict
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import abc
|
|
2
|
+
import threading
|
|
3
|
+
from contextlib import contextmanager
|
|
4
|
+
from typing import Optional
|
|
5
|
+
|
|
6
|
+
from dbhydra.src.migrator import Migrator
|
|
7
|
+
from dbhydra.src.tables import AbstractTable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def read_connection_details(config_file):
|
|
11
|
+
def read_file(file):
|
|
12
|
+
"""Reads txt file -> list"""
|
|
13
|
+
with open(file, "r") as f:
|
|
14
|
+
rows = f.readlines()
|
|
15
|
+
for i, row in enumerate(rows):
|
|
16
|
+
rows[i] = row.replace("\n", "")
|
|
17
|
+
return (rows)
|
|
18
|
+
|
|
19
|
+
connection_details = read_file(config_file)
|
|
20
|
+
db_details = {}
|
|
21
|
+
for detail in connection_details:
|
|
22
|
+
key = detail.split("=")[0]
|
|
23
|
+
value = detail.split("=")[1]
|
|
24
|
+
db_details[key] = value
|
|
25
|
+
|
|
26
|
+
# Skip empty lines to avoid error when reading config file
|
|
27
|
+
if not detail:
|
|
28
|
+
continue
|
|
29
|
+
|
|
30
|
+
print(", ".join([db_details['DB_SERVER'], db_details['DB_DATABASE'], db_details['DB_USERNAME']]))
|
|
31
|
+
return (db_details)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class Transaction:
|
|
38
|
+
def __init__(self, connection):
|
|
39
|
+
self.connection = connection
|
|
40
|
+
|
|
41
|
+
def commit(self):
|
|
42
|
+
self.connection.commit()
|
|
43
|
+
|
|
44
|
+
def rollback(self):
|
|
45
|
+
self.connection.rollback()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class AbstractDb(abc.ABC):
|
|
51
|
+
matching_table_class = AbstractTable
|
|
52
|
+
|
|
53
|
+
typing_to_python_mapping = {
|
|
54
|
+
'List': 'list',
|
|
55
|
+
'Dict': 'dict',
|
|
56
|
+
'Tuple': 'tuple',
|
|
57
|
+
'Set': 'set',
|
|
58
|
+
'Union': 'object',
|
|
59
|
+
'Optional': 'object',
|
|
60
|
+
'Jsonable': 'Jsonable',
|
|
61
|
+
# 'FrozenSet': frozenset,
|
|
62
|
+
# 'Deque': list,
|
|
63
|
+
# 'Any': object,
|
|
64
|
+
#
|
|
65
|
+
# 'Callable': object,
|
|
66
|
+
# 'Type': type,
|
|
67
|
+
# 'TypeVar': object,
|
|
68
|
+
# 'Generic': object,
|
|
69
|
+
# 'Sequence': list,
|
|
70
|
+
# 'Iterable': list,
|
|
71
|
+
# 'Mapping': dict,
|
|
72
|
+
# 'AbstractSet': set,
|
|
73
|
+
}
|
|
74
|
+
python_database_type_mapping = {}
|
|
75
|
+
def __init__(self, config_file="config.ini", db_details=None):
|
|
76
|
+
if db_details is None:
|
|
77
|
+
db_details = read_connection_details(config_file)
|
|
78
|
+
|
|
79
|
+
self.locally = True
|
|
80
|
+
if db_details["LOCALLY"] == "False":
|
|
81
|
+
self.locally = False
|
|
82
|
+
|
|
83
|
+
self.DB_SERVER = db_details["DB_SERVER"]
|
|
84
|
+
self.DB_DATABASE = db_details["DB_DATABASE"]
|
|
85
|
+
self.DB_USERNAME = db_details["DB_USERNAME"]
|
|
86
|
+
self.DB_PASSWORD = db_details["DB_PASSWORD"]
|
|
87
|
+
|
|
88
|
+
self.is_autocommiting=True
|
|
89
|
+
|
|
90
|
+
if "DB_PORT" in db_details.keys():
|
|
91
|
+
self.DB_PORT = int(db_details["DB_PORT"])
|
|
92
|
+
else:
|
|
93
|
+
self.DB_PORT = None
|
|
94
|
+
|
|
95
|
+
if "DB_DRIVER" in db_details.keys():
|
|
96
|
+
self.DB_DRIVER = db_details["DB_DRIVER"]
|
|
97
|
+
else:
|
|
98
|
+
self.DB_DRIVER = "ODBC Driver 13 for SQL Server"
|
|
99
|
+
|
|
100
|
+
self.lock = threading.Lock()
|
|
101
|
+
|
|
102
|
+
# This call to `self.connect_to_db` is not doing anything as it returns a
|
|
103
|
+
# context manager object
|
|
104
|
+
# self.connect_to_db()
|
|
105
|
+
|
|
106
|
+
self.active_transactions=[]
|
|
107
|
+
self.last_table_inserted_into: Optional[str] = None
|
|
108
|
+
self.identifier_quote = ''
|
|
109
|
+
|
|
110
|
+
@abc.abstractmethod
|
|
111
|
+
def connect_locally(self):
|
|
112
|
+
pass
|
|
113
|
+
|
|
114
|
+
@abc.abstractmethod
|
|
115
|
+
def connect_remotely(self):
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
def _connect(self):
|
|
119
|
+
if self.locally:
|
|
120
|
+
self.connect_locally()
|
|
121
|
+
else:
|
|
122
|
+
self.connect_remotely()
|
|
123
|
+
|
|
124
|
+
def connect(self):
|
|
125
|
+
print("DEPRECATION WARNING: use `connect_to_db` context manager instead of `connect` method")
|
|
126
|
+
if self.locally:
|
|
127
|
+
self.connect_locally()
|
|
128
|
+
else:
|
|
129
|
+
self.connect_remotely()
|
|
130
|
+
|
|
131
|
+
#@abc.abstractmethod
|
|
132
|
+
def get_all_tables(self):
|
|
133
|
+
pass
|
|
134
|
+
|
|
135
|
+
#@abc.abstractmethod
|
|
136
|
+
def generate_table_dict(self):
|
|
137
|
+
pass
|
|
138
|
+
|
|
139
|
+
def get_table(self, table_name: str):
|
|
140
|
+
"""
|
|
141
|
+
Retrieves Table from DB using its name.
|
|
142
|
+
"""
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
table = self.generate_table_dict()[table_name]
|
|
146
|
+
except KeyError:
|
|
147
|
+
print(f'Table "{table_name}" was not found in the DB.')
|
|
148
|
+
raise KeyError
|
|
149
|
+
|
|
150
|
+
return table
|
|
151
|
+
|
|
152
|
+
@contextmanager
|
|
153
|
+
def connect_to_db(self):
|
|
154
|
+
try:
|
|
155
|
+
self._connect()
|
|
156
|
+
yield None
|
|
157
|
+
finally:
|
|
158
|
+
self.close_connection()
|
|
159
|
+
|
|
160
|
+
@contextmanager
|
|
161
|
+
def connect_to_table(self, table_name):
|
|
162
|
+
with self.connect_to_db():
|
|
163
|
+
table = self.generate_table_dict()[table_name]
|
|
164
|
+
|
|
165
|
+
yield table
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@contextmanager
|
|
169
|
+
def transaction(self):
|
|
170
|
+
self.is_autocommiting=False
|
|
171
|
+
transaction = Transaction(self.connection)
|
|
172
|
+
self.active_transactions.append(transaction)
|
|
173
|
+
try:
|
|
174
|
+
yield transaction
|
|
175
|
+
except Exception as e:
|
|
176
|
+
transaction.rollback()
|
|
177
|
+
raise e
|
|
178
|
+
else:
|
|
179
|
+
transaction.commit()
|
|
180
|
+
finally:
|
|
181
|
+
self.active_transactions.remove(transaction)
|
|
182
|
+
if len(self.active_transactions)==0:
|
|
183
|
+
self.is_autocommiting=True
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def execute(self, query):
|
|
187
|
+
result=self.cursor.execute(query)
|
|
188
|
+
if self.is_autocommitting:
|
|
189
|
+
self.cursor.commit()
|
|
190
|
+
return(result)
|
|
191
|
+
|
|
192
|
+
def close_connection(self):
|
|
193
|
+
self.connection.close()
|
|
194
|
+
print("DB connection closed")
|
|
195
|
+
|
|
196
|
+
def initialize_migrator(self):
|
|
197
|
+
self.migrator = Migrator(self)
|
|
198
|
+
|
|
199
|
+
def _convert_column_type_dict_from_python(self, column_type_dict):
|
|
200
|
+
"""
|
|
201
|
+
First apply mapping from python typing module to standard python.
|
|
202
|
+
Then apply mapping from python to database types
|
|
203
|
+
"""
|
|
204
|
+
typing_python_mapping = {column_name: self.typing_to_python_mapping.get(column_type,column_type) for column_name, column_type in column_type_dict.items()}
|
|
205
|
+
return {column_name: self.python_database_type_mapping[column_type] for column_name, column_type in typing_python_mapping.items()}
|
|
206
|
+
|