Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

connection class as parameter in database class #14

Merged
merged 2 commits into from
Mar 12, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ __pycache__
build
dist
*.pypirc

# Pycharm files
.idea
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
language: python
python:
- "3.6"
- "3.7"
- "3.8"
env:
global:
- PIPENV_VENV_IN_PROJECT=1
Expand Down
5 changes: 5 additions & 0 deletions HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,8 @@
- added handling of percentage signs
- added possibility of automated comment removal
- ensured the compatibility of raw SQL code with code sent via dbrequests

## Version 1.2
- changed static loading of the connection class to a flexible model, allowing to override connections for different SQL drivers


10 changes: 2 additions & 8 deletions Pipfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,11 @@ verify_ssl = true
name = "pypi"

[packages]
pymysql = "*"
e1839a8 = {path = ".",editable = true}
pydbtools = {path = ".",editable = true}
pandas = "*"
ipykernel = "*"
twine = "*"
dbrequests = {path = "."}
docker = "*"

[dev-packages]
pytest = "*"
pydbtools = {path = ".",editable = true}
docker = "*"
dbrequests = {path = "."}
pymysql = "*"

546 changes: 56 additions & 490 deletions Pipfile.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ Supported modes are:
- 'replace': Replace records with duplicate primary keys (sql replace into).
- 'update': Update records with duplicate primary keys (sql insert into duplicate key update).

### Uitilities
### Utilities

- Comments can be automatically removed from SQL code by adding `remove_comments=True` either to the Database defintion or send_query. This is especially useful if outcommenting code blocks including parametized variables and thus `{}`. The default of this behavior is `False`.
- Percentage signs can be transfered to a Python readable way by adding `escape_percentage=True` either to the Database definition or send_query. This means percentage signs dont have to be escaped manually when sending them via Python. The default is `False`.
Expand All @@ -85,3 +85,6 @@ The package can be installed via pip:
```
pip install dbrequests
```

## Extensibility
dbrequests is designed to easily accommodate different needs in the form of drivers / dialects. For examples of how to extend the capabilities of the Connection class, see connection_subclass.py under examples.
2 changes: 0 additions & 2 deletions dbrequests/connection.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
from sqlalchemy import text
from pandas import read_sql
import os
import inspect

class Connection(object):
Expand Down Expand Up @@ -38,7 +37,6 @@ def bulk_query(self, query, **params):
params = {k: v for k, v in params.items() if k in inspect.getfullargspec(self._conn.execute).args}
self._conn.execute(text(query), **params)


def send_data(self, df, table, mode='insert', **params):
"""Sends data to table in database. If the table already exists, different modes of
insertion are provided.
Expand Down
7 changes: 4 additions & 3 deletions dbrequests/database.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import os
from sqlalchemy import create_engine, inspect, exc
from contextlib import contextmanager
from .connection import Connection
from .connection import Connection as DefaultConnection
from .query import Query
from pandas import DataFrame

Expand All @@ -20,7 +20,7 @@ class Database(object):
- driver (defaults to pymysql)
"""

def __init__(self, db_url=None, creds=None, sql_dir=None,
def __init__(self, db_url=None, creds=None, sql_dir=None, connection_class=DefaultConnection,
escape_percentage=False, remove_comments=False, **kwargs):
# If no db_url was provided, fallback to $DATABASE_URL or creds.
self.db_url = db_url or os.environ.get('DATABASE_URL')
Expand All @@ -42,6 +42,7 @@ def __init__(self, db_url=None, creds=None, sql_dir=None,
self._escape_percentage = escape_percentage
self._remove_comments = remove_comments
self.open = True
self.connection_class = connection_class

def close(self):
"""Closes the Database."""
Expand Down Expand Up @@ -70,7 +71,7 @@ def get_connection(self):
if not self.open:
raise exc.ResourceClosedError('Database closed.')

return Connection(self._engine.connect())
return self.connection_class(self._engine.connect())
wahani marked this conversation as resolved.
Show resolved Hide resolved

def __get_query_text(self, query, escape_percentage, remove_comments, **params):
"""Private wrapper for accessing the text of the query."""
Expand Down
4 changes: 2 additions & 2 deletions dbrequests/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ def set_up_table(db):
CREATE TABLE cats
(
id INT unsigned NOT NULL AUTO_INCREMENT, # Unique ID for the record
name VARCHAR(150) NOT NULL, # Name of the cat
owner VARCHAR(150) NOT NULL, # Owner of the cat
name VARCHAR(150) NOT NULL DEFAULT '', # Name of the cat
owner VARCHAR(150) NOT NULL DEFAULT '', # Owner of the cat
birth DATE NOT NULL, # Birthday of the cat
PRIMARY KEY (id) # Make the id the primary key
);
Expand Down
52 changes: 52 additions & 0 deletions examples/connection_subclass.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
from dbrequests import Connection
from dbrequests import Database
from docker import from_env


class ExampleConnection(Connection):
"""
Within this example, we inherit everything from the Connection class, but overwrite bulk_query, which is accessed
via the Database method send_bulk_query.
"""

def bulk_query(self, query, **params):
print('You sent the query: {}'.format(query))


class ExampleDatabase(Database):
"""
This Database child class only overwrites the Database initialization by passing the ExampleConnection class.
If additional parameters are to be handed to the new Connection class, this could be done by additionally overwriting
get_connection.
"""

def __init__(self, db_url=None, creds=None, sql_dir=None,
escape_percentage=False, remove_comments=False, **kwargs):
super().__init__(db_url=db_url, creds=creds, sql_dir=sql_dir, connection_class=ExampleConnection,
escape_percentage=escape_percentage, remove_comments=remove_comments, **kwargs)


if __name__ == '__main__':
"""We test the example by setting up a mariadb database to run our new model against"""
creds = {
'user': 'root',
'password': 'root',
'host': '127.0.0.1',
'db': 'test',
'port': 3307
}
client = from_env()
container = client.containers.run('mariadb:10.3', name='test-mariadb-database',
ports={3306: creds['port']},
environment={'MYSQL_ROOT_PASSWORD': creds['password'],
'MYSQL_DATABASE': creds['db']},
detach=True)
url = ("mysql+pymysql://{}:{}@{}:{}/{}".format(creds['user'], creds['password'], creds['host'], creds['port'],
creds['db']))
db = ExampleDatabase(url)
db.send_bulk_query('select hi from hi') # only prints the query to stdout
db.close()
container.kill()
container.remove()
assert client.containers.list() == []
client.close()
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def run(self):

requires = ['SQLAlchemy;python_version>="3.0"',
'pandas']
version = '1.1.1'
version = '1.2.0'


def read(f):
Expand Down Expand Up @@ -82,6 +82,7 @@ def read(f):
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
),
cmdclass={
'publish': PublishCommand,
Expand Down