Skip to content

Commit

Permalink
Changed the behaviour of parameter marks for ModelBase instances
Browse files Browse the repository at this point in the history
  • Loading branch information
megahomyak committed Jan 5, 2022
1 parent 45f7395 commit 29dbf6d
Show file tree
Hide file tree
Showing 7 changed files with 62 additions and 30 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ fed it, maps it to some model and gives to u. Also it can create tables
class MyFirstModel(ModelBase):
_tablename = "obvious"
# You don't need to specify a _tablename if you will not create a table
# for this model using an SQL executor
# for this model using an SQL executor OR insert it using parameter
# marks from this mapper
# (sql_executor.execute("INSERT INTO ?", [MyFirstModel("a", "b")]))

first_field: str = "TEXT"
# Value of each field is its datatype that will be used when SQL table
Expand Down
25 changes: 19 additions & 6 deletions sql_mapper/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,22 +12,22 @@ def __str__(self):
)


class FieldAlreadyTaken(Exception):
class _FieldNameMixin:

def __init__(self, field_name: str):
self.field_name = field_name


class FieldAlreadyTaken(_FieldNameMixin, Exception):

def __str__(self):
return (
f"field '{self.field_name}' was already filled! Check your ordered "
f"fields, it may help"
)


class UnknownField(Exception):

def __init__(self, field_name: str):
self.field_name = field_name
class UnknownField(_FieldNameMixin, Exception):

def __str__(self):
return (
Expand All @@ -36,13 +36,26 @@ def __str__(self):
)


class TablenameNotSpecifiedOnTableCreation(Exception):
class _ModelNameMixin:

def __init__(self, model_name: str):
self.model_name = model_name


class TablenameNotSpecifiedOnTableCreation(_ModelNameMixin, Exception):

def __str__(self):
return (
f"tablename not specified for the model '{self.model_name}'! You "
f"cannot create an unnamed table!"
)


class TablenameNotSpecifiedOnInsertion(_ModelNameMixin, Exception):

def __str__(self):
return (
f"tablename not specified for the model '{self.model_name}'! SQL "
f"mapper doesn't know what tablename to use in query parameter "
f"mark substitution, so add the tablename to the model!"
)
12 changes: 8 additions & 4 deletions sql_mapper/model_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,16 +59,20 @@ def __getattr__(self, field_name: str):
def __eq__(self, other: "ModelBase"):
return self.instance_fields == other.instance_fields

@classmethod
def get_tablename(cls) -> Optional[str]:
try:
return cls._tablename
except AttributeError:
return None

@property
def instance_fields(self) -> Dict[str, str]:
return self._fields

@classmethod
def get_table_data(cls):
try:
name = cls._tablename
except AttributeError:
name = None
name = cls.get_tablename()
fields = cls._fields
try:
additional_table_lines = cls._additional_table_lines
Expand Down
28 changes: 17 additions & 11 deletions sql_mapper/string_dump.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from abc import ABC, abstractmethod
from typing import Union, List, Iterable

from sql_mapper import exceptions
from sql_mapper.model_base import ModelBase

QUESTION_MARK_PATTERN = re.compile(r"\?")
Expand All @@ -28,16 +29,16 @@ def reformat_query(
"""
Makes a query that can be passed to the database connector, replacing
every BaseModel instance with its values and its corresponding
question mark with something like "(field1, field2, field3) VALUES
(?,?,?)" (depends on the database connector).
question mark with something like "tablename (field1, field2, field3)
VALUES (?,?,?)" (depends on the database connector).
So,
old query:
"INSERT INTO a ?", instance_of_a
"INSERT INTO ?", instance_of_model
new query:
"INSERT INTO a (field1, field2, ...) VALUES (?, ?, ...)",
values_from_instance_of_a
"INSERT INTO tablename (field1, field2, ...) VALUES (?, ?, ...)",
values_from_instance_of_model
"""
pass

Expand All @@ -55,9 +56,14 @@ def _new_query_parameter_marks_generator(
for argument in old_arguments:
if isinstance(argument, ModelBase):
new_arguments.extend(argument.instance_fields.values())
tablename = argument.get_tablename()
if not tablename:
raise exceptions.TablenameNotSpecifiedOnInsertion(
model_name=argument.__class__.__name__
)
yield (
"(" + ",".join(argument.instance_fields.keys()) + ")VALUES("
+ ",".join(
tablename + "(" + ",".join(argument.instance_fields.keys())
+ ")VALUES(" + ",".join(
new_parameter_mark
for _ in range(len(argument.instance_fields))
) + ")"
Expand Down Expand Up @@ -123,13 +129,13 @@ def reformat_collected_query(
) -> NewQueryStringWithArguments:
"""
old query:
INSERT INTO a ?
INSERT INTO ?
old arguments:
instance_of_a
instance_of_model
new query:
INSERT INTO a (field1, field2, ...) VALUES (?, ?, ...)
INSERT INTO tablename (field1, field2, ...) VALUES (?, ?, ...)
new arguments:
values_from_instance_of_a
values_from_instance_of_model
"""
new_arguments = []
new_query = "".join(
Expand Down
11 changes: 5 additions & 6 deletions tests/docs_example_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,16 @@ def test_example_from_docs():
book_ids = []
for book_title in book_names:
sql_executor.execute(
"INSERT INTO books ?", [Book(title=book_title)]
"INSERT INTO ?", [Book(title=book_title)]
)
book_ids.append(sql_executor.cursor.lastrowid)
sql_executor.execute(
"INSERT INTO authors ?", [Author(name=author_name)]
"INSERT INTO ?", [Author(name=author_name)]
)
author_id = sql_executor.cursor.lastrowid
sql_executor.execute_many(
"INSERT INTO books_to_authors ?", [
[BookToAuthor(
book_id=book_id, author_id=sql_executor.cursor.lastrowid
)]
"INSERT INTO ?", [
[BookToAuthor(book_id, author_id)]
for book_id in book_ids
]
)
Expand Down
8 changes: 8 additions & 0 deletions tests/exceptions_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,3 +36,11 @@ def test_exceptions():
str(e.value) ==
"unknown field 'c'! (It is not declared in the model)"
)
with pytest.raises(exceptions.TablenameNotSpecifiedOnInsertion) as e:
sql_executor.execute("INSERT INTO ?", [A(1, "abc")])
assert (
str(e.value) ==
"tablename not specified for the model 'A'! SQL mapper "
"doesn't know what tablename to use in query parameter mark "
"substitution, so add the tablename to the model!"
)
4 changes: 2 additions & 2 deletions tests/sql_mapper_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class A(ModelBase):
sql_executor = SQLiteSQLExecutor.new(":memory:")
sql_executor.create_tables(A)

sql_executor.execute_many("INSERT INTO a ?", (
sql_executor.execute_many("INSERT INTO ?", (
[A(1, "a")],
[A(c="b")],
[A(c="c")]
Expand All @@ -23,7 +23,7 @@ class A(ModelBase):
sql_executor.execute(
"INSERT INTO a VALUES (?, ?)",
[*A(4, "d").instance_fields.values()]
) # Identical to sql_executor.execute("INSERT INTO a ?", [A(7, 8)])
) # Identical to sql_executor.execute("INSERT INTO ?", [A(7, 8)])

sql_executor.execute("INSERT INTO a VALUES (?, " + raw("'?'") + ")", [5])

Expand Down

0 comments on commit 29dbf6d

Please sign in to comment.