diff --git a/Changelog.md b/Changelog.md index 984c380..992b33f 100644 --- a/Changelog.md +++ b/Changelog.md @@ -1,3 +1,19 @@ +=== +Dataloom **`2.1.0`** +=== + +### Release Notes - `dataloom` + +We have release the new `dataloom` Version `2.1.0` (`2024-02-12`) + +##### Features + +- Connecting to databases using connection `uri` for all the supported dialects + + ```py + + ``` + === Dataloom **`2.0.0`** === diff --git a/dataloom/conn/__init__.py b/dataloom/conn/__init__.py index fa98c28..15d1c26 100644 --- a/dataloom/conn/__init__.py +++ b/dataloom/conn/__init__.py @@ -4,7 +4,7 @@ from os import PathLike from typing_extensions import TypeAlias from typing import Optional - +from urllib.parse import urlparse, parse_qs from dataloom.constants import instances @@ -87,3 +87,21 @@ def get_connection_options(**kwargs): raise UnsupportedDialectException( "The dialect passed is not supported the supported dialects are: {'postgres', 'mysql', 'sqlite'}" ) + + @staticmethod + def get_mysql_uri_connection_options(uri: str) -> dict: + components = urlparse(uri) + user = components.username + password = components.password + hostname = components.hostname + port = components.port + db = components.path.lstrip("/") + + return { + "user": user, + "password": password, + "host": hostname, + "port": port, + "database": db, + **parse_qs(components.query), + } diff --git a/dataloom/keys.py b/dataloom/keys.py index 6129f81..4e2537c 100644 --- a/dataloom/keys.py +++ b/dataloom/keys.py @@ -1,3 +1,6 @@ +# Configuration file for unit testing. + + push = True @@ -6,10 +9,18 @@ class PgConfig: password = "postgres" database = "postgres" user = "postgres" + host = "0.0.0.0" + port = 5432 + db = "hi" + connection_uri = f"postgresql://{user}:{password}@{host}:{port}/{db}" else: database = "postgres" user = "postgres" password = "root" + host = "localhost" + port = 5432 + db = "hi" + connection_uri = f"postgresql://{user}:{password}@{host}:{port}/{db}" class MySQLConfig: @@ -17,7 +28,22 @@ class MySQLConfig: password = "testrootpass" database = "testdb" user = "root" + host = "0.0.0.0" + port = 3306 + db = "hi" + connection_uri = f"mysql://{user}:{password}@{host}:{port}/{db}" else: database = "hi" user = "root" password = "root" + host = "localhost" + port = 3306 + db = "hi" + connection_uri = f"mysql://{user}:{password}@{host}:{port}/{db}" + + +class SQLiteConfig: + if push: + connection_uri = "sqlite:///database.db" + else: + connection_uri = r"C:\\Users\\RGaridzirai\\OneDrive - Walter Sisulu University\Desktop\\TINASHE CRISPEN G\\orm\\dataloom-py\\hi.db" diff --git a/dataloom/loom/__init__.py b/dataloom/loom/__init__.py index 7e86f0a..4de455c 100644 --- a/dataloom/loom/__init__.py +++ b/dataloom/loom/__init__.py @@ -69,8 +69,9 @@ class Loom(ILoom): def __init__( self, - database: str, dialect: DIALECT_LITERAL, + connection_uri: Optional[str] = None, + database: Optional[str] = None, user: Optional[str] = None, host: Optional[str] = None, port: Optional[int] = None, @@ -83,6 +84,7 @@ def __init__( self.sql_logger = sql_logger self.dialect = dialect self.logs_filename = logs_filename + self.connection_uri = connection_uri try: config = instances[dialect] @@ -1194,27 +1196,41 @@ def connect( """ if self.dialect == "postgres": - options = ConnectionOptionsFactory.get_connection_options( - **self.connection_options - ) - with psycopg2.connect(**options) as conn: - self.conn = conn + if self.connection_uri is None: + options = ConnectionOptionsFactory.get_connection_options( + **self.connection_options + ) + with psycopg2.connect(**options) as conn: + self.conn = conn + else: + with psycopg2.connect(self.connection_uri) as conn: + self.conn = conn elif self.dialect == "mysql": - options = ConnectionOptionsFactory.get_connection_options( - **self.connection_options + options = ( + ConnectionOptionsFactory.get_connection_options( + **self.connection_options + ) + if self.connection_uri is None + else ConnectionOptionsFactory.get_mysql_uri_connection_options( + self.connection_uri + ) ) self.conn = connector.connect(**options) elif self.dialect == "sqlite": - options = ConnectionOptionsFactory.get_connection_options( - **self.connection_options - ) - if "database" in options: - with sqlite3.connect(options.get("database")) as conn: - self.conn = conn + if self.connection_uri is None: + options = ConnectionOptionsFactory.get_connection_options( + **self.connection_options + ) + if "database" in options: + with sqlite3.connect(options.get("database")) as conn: + self.conn = conn + else: + with sqlite3.connect(**options) as conn: + self.conn = conn else: - with sqlite3.connect(**options) as conn: + with sqlite3.connect(self.connection_uri) as conn: self.conn = conn else: raise UnsupportedDialectException( @@ -1285,34 +1301,8 @@ def connect_and_sync( ... conn.close() """ - try: - if self.dialect == "postgres": - options = ConnectionOptionsFactory.get_connection_options( - **self.connection_options - ) - with psycopg2.connect(**options) as conn: - self.conn = conn - elif self.dialect == "mysql": - options = ConnectionOptionsFactory.get_connection_options( - **self.connection_options - ) - self.conn = connector.connect(**options) - - elif self.dialect == "sqlite": - options = ConnectionOptionsFactory.get_connection_options( - **self.connection_options - ) - if "database" in options: - with sqlite3.connect(options.get("database")) as conn: - self.conn = conn - else: - with sqlite3.connect(**options) as conn: - self.conn = conn - else: - raise UnsupportedDialectException( - "The dialect passed is not supported the supported dialects are: {'postgres', 'mysql', 'sqlite'}" - ) + self.conn = self.connect() self.sql_obj = SQL( conn=self.conn, dialect=self.dialect, diff --git a/dataloom/tests/mysql/test_connection_mysql.py b/dataloom/tests/mysql/test_connection_options_mysql.py similarity index 98% rename from dataloom/tests/mysql/test_connection_mysql.py rename to dataloom/tests/mysql/test_connection_options_mysql.py index 3a8026c..a5de136 100644 --- a/dataloom/tests/mysql/test_connection_mysql.py +++ b/dataloom/tests/mysql/test_connection_options_mysql.py @@ -2,7 +2,7 @@ from mysql import connector -class TestConnectionMySQL: +class TestConnectionOptionsMySQL: def test_connect_with_non_existing_database(self): from dataloom import Loom from dataloom.keys import MySQLConfig diff --git a/dataloom/tests/mysql/test_connection_uri_mysql.py b/dataloom/tests/mysql/test_connection_uri_mysql.py new file mode 100644 index 0000000..55e2e90 --- /dev/null +++ b/dataloom/tests/mysql/test_connection_uri_mysql.py @@ -0,0 +1,81 @@ +import pytest +from mysql import connector + + +class TestConnectionURIMySQL: + def test_connect_with_non_existing_database(self): + from dataloom import Loom + from dataloom.keys import MySQLConfig + + mysql_loom = Loom( + dialect="mysql", + connection_uri=f"mysql://{MySQLConfig.user}:{MySQLConfig.password}@{MySQLConfig.host}:{MySQLConfig.port}/non-exists", + ) + with pytest.raises(connector.errors.ProgrammingError) as exc_info: + conn = mysql_loom.connect() + conn.close() + assert exc_info.value.msg == "Unknown database 'non-exists'" + assert exc_info.value.errno == 1049 + + def test_connect_with_wrong_password(self): + from dataloom import Loom + from dataloom.keys import MySQLConfig + + mysql_loom = Loom( + dialect="mysql", + connection_uri=f"mysql://{MySQLConfig.user}:{MySQLConfig.password+'me'}@{MySQLConfig.host}:{MySQLConfig.port}/hi", + ) + with pytest.raises(connector.errors.ProgrammingError) as exc_info: + conn = mysql_loom.connect() + conn.close() + + assert str(exc_info.value.msg).startswith( + "Access denied for user 'root'@'" + ) and str(exc_info.value.msg).endswith("(using password: YES)") + assert exc_info.value.errno == 1045 + + def test_connect_with_wrong_user(self): + from dataloom import Loom + from dataloom.keys import MySQLConfig + + mysql_loom = Loom( + dialect="mysql", + connection_uri=f"mysql://hey:{MySQLConfig.password}@{MySQLConfig.host}:{MySQLConfig.port}/hi", + ) + with pytest.raises(connector.errors.ProgrammingError) as exc_info: + conn = mysql_loom.connect() + conn.close() + assert str(exc_info.value.msg).startswith( + "Access denied for user 'hey'@" + ) and str(exc_info.value.msg).endswith("(using password: YES)") + assert exc_info.value.errno == 1045 + + def test_connect_with_wrong_dialect(self): + from dataloom import Loom + from dataloom.keys import MySQLConfig + from dataloom.exceptions import UnsupportedDialectException + + with pytest.raises(UnsupportedDialectException) as exc_info: + mysql_loom = Loom( + dialect="dialect", + connection_uri=f"mysql://{MySQLConfig.user}:{MySQLConfig.password}@{MySQLConfig.host}:{MySQLConfig.port}/hi", + ) + conn = mysql_loom.connect() + conn.close() + + assert ( + str(exc_info.value) + == "The dialect passed is not supported the supported dialects are: {'postgres', 'mysql', 'sqlite'}" + ) + + def test_connect_correct_connection(self): + from dataloom import Loom + from dataloom.keys import MySQLConfig + + mysql_loom = Loom( + dialect="mysql", + connection_uri=f"mysql://{MySQLConfig.user}:{MySQLConfig.password}@{MySQLConfig.host}:{MySQLConfig.port}/hi", + ) + conn = mysql_loom.connect() + conn.close() + assert conn is not None diff --git a/dataloom/tests/postgres/test_connection_pg.py b/dataloom/tests/postgres/test_connection_options_pg.py similarity index 98% rename from dataloom/tests/postgres/test_connection_pg.py rename to dataloom/tests/postgres/test_connection_options_pg.py index 87ae43f..23062bc 100644 --- a/dataloom/tests/postgres/test_connection_pg.py +++ b/dataloom/tests/postgres/test_connection_options_pg.py @@ -1,7 +1,7 @@ import pytest -class TestConnectionPG: +class TestConnectionOptionsPG: def test_connect_with_non_existing_database(self): from dataloom import Loom from dataloom.keys import PgConfig diff --git a/dataloom/tests/postgres/test_connection_uri_pg.py b/dataloom/tests/postgres/test_connection_uri_pg.py new file mode 100644 index 0000000..fbe0940 --- /dev/null +++ b/dataloom/tests/postgres/test_connection_uri_pg.py @@ -0,0 +1,83 @@ +import pytest + + +class TestConnectionURIPG: + def test_connect_with_non_existing_database(self): + from dataloom import Loom + from dataloom.keys import PgConfig + + pg_loom = Loom( + dialect="postgres", + connection_uri=f"postgresql://{PgConfig.user}:{PgConfig.password}@{PgConfig.host}:{PgConfig.port}/mew", + ) + with pytest.raises(Exception) as exc_info: + conn = pg_loom.connect() + conn.close() + assert ( + str(exc_info.value.args[0]).strip() + == 'connection to server at "localhost" (::1), port 5432 failed: FATAL: database "mew" does not exist' + ) + + def test_connect_with_wrong_password(self): + from dataloom import Loom + from dataloom.keys import PgConfig + + pg_loom = Loom( + dialect="postgres", + connection_uri=f"postgresql://{PgConfig.user}:{PgConfig.password+'-'}@{PgConfig.host}:{PgConfig.port}/hi", + ) + with pytest.raises(Exception) as exc_info: + conn = pg_loom.connect() + conn.close() + + assert ( + str(exc_info.value.args[0]).strip() + == 'connection to server at "localhost" (::1), port 5432 failed: FATAL: password authentication failed for user "postgres"' + ) + + def test_connect_with_wrong_user(self): + from dataloom import Loom + from dataloom.keys import PgConfig + + pg_loom = Loom( + dialect="postgres", + connection_uri=f"postgresql://postgre-u:{PgConfig.password}@{PgConfig.host}:{PgConfig.port}/hi", + ) + with pytest.raises(Exception) as exc_info: + conn = pg_loom.connect() + conn.close() + + assert ( + str(exc_info.value.args[0]).strip() + == 'connection to server at "localhost" (::1), port 5432 failed: FATAL: password authentication failed for user "postgre-u"' + ) + + def test_connect_with_wrong_dialect(self): + from dataloom import Loom + from dataloom.exceptions import UnsupportedDialectException + from dataloom.keys import PgConfig + + with pytest.raises(UnsupportedDialectException) as exc_info: + pg_loom = Loom( + dialect="peew", + connection_uri=f"postgresql://{PgConfig.user}:{PgConfig.password}@{PgConfig.host}:{PgConfig.port}/hi", + ) + conn = pg_loom.connect() + conn.close() + assert ( + str(exc_info.value) + == "The dialect passed is not supported the supported dialects are: {'postgres', 'mysql', 'sqlite'}" + ) + + def test_connect_correct_connection(self): + from dataloom import Loom + from dataloom.keys import PgConfig + + pg_loom = Loom( + dialect="postgres", + connection_uri=f"postgresql://{PgConfig.user}:{PgConfig.password}@{PgConfig.host}:{PgConfig.port}/hi", + ) + conn = pg_loom.connect() + conn.close() + + assert conn is not None diff --git a/dataloom/tests/sqlite3/test_connection_sqlite.py b/dataloom/tests/sqlite3/test_connection_options_sqlite.py similarity index 95% rename from dataloom/tests/sqlite3/test_connection_sqlite.py rename to dataloom/tests/sqlite3/test_connection_options_sqlite.py index a848842..a6894b5 100644 --- a/dataloom/tests/sqlite3/test_connection_sqlite.py +++ b/dataloom/tests/sqlite3/test_connection_options_sqlite.py @@ -1,7 +1,7 @@ import pytest -class TestConnectionSQLite: +class TestConnectionOptionsSQLite: def test_connect_with_wrong_dialect(self): from dataloom import Loom from dataloom.exceptions import UnsupportedDialectException diff --git a/dataloom/tests/sqlite3/test_connection_uri_sqlite.py b/dataloom/tests/sqlite3/test_connection_uri_sqlite.py new file mode 100644 index 0000000..6535299 --- /dev/null +++ b/dataloom/tests/sqlite3/test_connection_uri_sqlite.py @@ -0,0 +1,29 @@ +import pytest + + +class TestConnectionURISQLite: + def test_connect_with_wrong_dialect(self): + from dataloom import Loom + from dataloom.exceptions import UnsupportedDialectException + from dataloom.keys import SQLiteConfig + + with pytest.raises(UnsupportedDialectException) as exc_info: + sqlite_loom = Loom( + dialect="hay", connection_uri=SQLiteConfig.connection_uri + ) + conn = sqlite_loom.connect() + conn.close() + + assert ( + str(exc_info.value) + == "The dialect passed is not supported the supported dialects are: {'postgres', 'mysql', 'sqlite'}" + ) + + def test_connect_correct_connection(self): + from dataloom import Loom + from dataloom.keys import SQLiteConfig + + sqlite_loom = Loom(dialect="sqlite", connection_uri=SQLiteConfig.connection_uri) + conn = sqlite_loom.connect() + conn.close() + assert conn is not None diff --git a/hi.db b/hi.db index f3e1069..f258229 100644 Binary files a/hi.db and b/hi.db differ diff --git a/playground.py b/playground.py index e62538b..8604e1a 100644 --- a/playground.py +++ b/playground.py @@ -19,29 +19,26 @@ from typing import Optional from dataclasses import dataclass -sqlite_loom = Loom( - dialect="sqlite", - database="hi.db", - logs_filename="sqlite-logs.sql", - sql_logger="console", -) +# sqlite_loom = Loom( +# connection_uri="sqlite:///database.db", +# dialect="sqlite", +# database="hi.db", +# logs_filename="sqlite-logs.sql", +# sql_logger="console", +# ) + +# conn = sqlite_loom.connect() + pg_loom = Loom( + connection_uri="postgresql://postgres:root@localhost:5432/hi", dialect="postgres", - database="hi", - password="root", - user="postgres", sql_logger="console", ) mysql_loom = Loom( + connection_uri="mysql://root:root@localhost:3306/hi", dialect="mysql", - database="hi", - password="root", - user="root", - host="localhost", - logs_filename="logs.sql", - port=3306, sql_logger="console", ) @@ -107,59 +104,59 @@ class Category(Model): ) -userId = mysql_loom.insert_one( - instance=User, - values=ColumnValue(name="username", value="@miller"), -) - -aff = mysql_loom.delete_bulk( - instance=User, - filters=Filter(column="id", value=1), -) -print(aff) - - -userId2 = mysql_loom.insert_one( - instance=User, - values=ColumnValue(name="username", value="bob"), -) - -profileId = mysql_loom.insert_one( - instance=Profile, - values=[ - ColumnValue(name="userId", value=userId), - ColumnValue(name="avatar", value="hello.jpg"), - ], -) -for title in ["Hello", "Hello", "What are you doing", "Coding"]: - mysql_loom.insert_one( - instance=Post, - values=[ - ColumnValue(name="userId", value=userId), - ColumnValue(name="title", value=title), - ], - ) - - -for cat in ["general", "education", "tech", "sport"]: - mysql_loom.insert_one( - instance=Category, - values=[ - ColumnValue(name="postId", value=1), - ColumnValue(name="type", value=cat), - ], - ) - -posts = mysql_loom.find_many( - Post, - select="id", - filters=Filter(column="id", operator="gt", value=1), - group=Group( - column="id", - function="MAX", - having=Having(column="id", operator="in", value=(2, 3, 4)), - return_aggregation_column=False, - ), -) - -print(posts) +# userId = mysql_loom.insert_one( +# instance=User, +# values=ColumnValue(name="username", value="@miller"), +# ) + +# aff = mysql_loom.delete_bulk( +# instance=User, +# filters=Filter(column="id", value=1), +# ) +# print(aff) + + +# userId2 = mysql_loom.insert_one( +# instance=User, +# values=ColumnValue(name="username", value="bob"), +# ) + +# profileId = mysql_loom.insert_one( +# instance=Profile, +# values=[ +# ColumnValue(name="userId", value=userId), +# ColumnValue(name="avatar", value="hello.jpg"), +# ], +# ) +# for title in ["Hello", "Hello", "What are you doing", "Coding"]: +# mysql_loom.insert_one( +# instance=Post, +# values=[ +# ColumnValue(name="userId", value=userId), +# ColumnValue(name="title", value=title), +# ], +# ) + + +# for cat in ["general", "education", "tech", "sport"]: +# mysql_loom.insert_one( +# instance=Category, +# values=[ +# ColumnValue(name="postId", value=1), +# ColumnValue(name="type", value=cat), +# ], +# ) + +# posts = mysql_loom.find_many( +# Post, +# select="id", +# filters=Filter(column="id", operator="gt", value=1), +# group=Group( +# column="id", +# function="MAX", +# having=Having(column="id", operator="in", value=(2, 3, 4)), +# return_aggregation_column=False, +# ), +# ) + +# print(posts)