add large binary field, tests and docs

This commit is contained in:
collerek
2021-04-28 17:04:29 +02:00
parent 638af9ad4c
commit 11ed5fd322
13 changed files with 148 additions and 14 deletions

View File

@ -451,6 +451,16 @@ async def aggregations():
# to read more about aggregated functions
# visit: https://collerek.github.io/ormar/queries/aggregations/
async def with_connect(function):
# note that for any other backend than sqlite you actually need to
# connect to the database to perform db operations
async with database:
await function()
# note that if you use framework like `fastapi` you shouldn't connect
# in your endpoints but have a global connection pool
# check https://collerek.github.io/ormar/fastapi/ and section with db connection
# gather and execute all functions
# note - normally import should be at the beginning of the file
@ -462,7 +472,7 @@ for func in [create, read, update, delete, joins,
filter_and_sort, subset_of_columns,
pagination, aggregations]:
print(f"Executing: {func.__name__}")
asyncio.run(func())
asyncio.run(with_connect(func))
# drop the database tables
metadata.drop_all(engine)
@ -521,6 +531,7 @@ Available Model Fields (with required args - optional ones in docs):
* `BigInteger()`
* `Decimal(scale, precision)`
* `UUID()`
* `LargeBinary(max_length)`
* `EnumField` - by passing `choices` to any other Field type
* `EncryptedString` - by passing `encrypt_secret` and `encrypt_backend`
* `ForeignKey(to)`

View File

@ -127,6 +127,17 @@ You can use either `length` and `precision` parameters or `max_digits` and `deci
* Sqlalchemy column: `sqlalchemy.JSON`
* Type (used for pydantic): `pydantic.Json`
### LargeBinary
`LargeBinary(max_length)` has a required `max_length` parameter.
* Sqlalchemy column: `sqlalchemy.LargeBinary`
* Type (used for pydantic): `bytes`
LargeBinary length is used in some backend (i.e. mysql) to determine the size of the field,
in other backends it's simply ignored yet in ormar it's always required. It should be max
size of the file/bytes in bytes.
### UUID
`UUID(uuid_format: str = 'hex')` has no required parameters.

View File

@ -451,6 +451,16 @@ async def aggregations():
# to read more about aggregated functions
# visit: https://collerek.github.io/ormar/queries/aggregations/
async def with_connect(function):
# note that for any other backend than sqlite you actually need to
# connect to the database to perform db operations
async with database:
await function()
# note that if you use framework like `fastapi` you shouldn't connect
# in your endpoints but have a global connection pool
# check https://collerek.github.io/ormar/fastapi/ and section with db connection
# gather and execute all functions
# note - normally import should be at the beginning of the file
@ -462,7 +472,7 @@ for func in [create, read, update, delete, joins,
filter_and_sort, subset_of_columns,
pagination, aggregations]:
print(f"Executing: {func.__name__}")
asyncio.run(func())
asyncio.run(with_connect(func))
# drop the database tables
metadata.drop_all(engine)
@ -521,6 +531,7 @@ Available Model Fields (with required args - optional ones in docs):
* `BigInteger()`
* `Decimal(scale, precision)`
* `UUID()`
* `LargeBinary(max_length)`
* `EnumField` - by passing `choices` to any other Field type
* `EncryptedString` - by passing `encrypt_secret` and `encrypt_backend`
* `ForeignKey(to)`

View File

@ -1,3 +1,14 @@
# 0.10.6
## ✨ Features
* Add `LargeBinary(max_length)` field type [#166](https://github.com/collerek/ormar/issues/166)
## 💬 Other
* Add connecting the database in quickstart in readme [#180](https://github.com/collerek/ormar/issues/180)
# 0.10.5
## 🐛 Fixes

Binary file not shown.

View File

@ -319,6 +319,16 @@ async def aggregations():
# visit: https://collerek.github.io/ormar/queries/aggregations/
async def with_connect(function):
# note that for any other backend than sqlite you actually need to
# connect to the database to perform db operations
async with database:
await function()
# note that if you use framework like `fastapi` you shouldn't connect
# in your endpoints but have a global connection pool
# check https://collerek.github.io/ormar/fastapi/ and section with db connection
# gather and execute all functions
# note - normally import should be at the beginning of the file
import asyncio
@ -329,7 +339,7 @@ for func in [create, read, update, delete, joins,
filter_and_sort, subset_of_columns,
pagination, aggregations]:
print(f"Executing: {func.__name__}")
asyncio.run(func())
asyncio.run(with_connect(func))
# drop the database tables
metadata.drop_all(engine)

View File

@ -53,6 +53,7 @@ from ormar.fields import (
ForeignKeyField,
Integer,
JSON,
LargeBinary,
ManyToMany,
ManyToManyField,
String,
@ -124,4 +125,5 @@ __all__ = [
"EncryptBackends",
"ENCODERS_MAP",
"DECODERS_MAP",
"LargeBinary",
]

View File

@ -16,6 +16,7 @@ from ormar.fields.model_fields import (
Float,
Integer,
JSON,
LargeBinary,
String,
Text,
Time,
@ -50,4 +51,5 @@ __all__ = [
"EncryptBackend",
"DECODERS_MAP",
"ENCODERS_MAP",
"LargeBinary",
]

View File

@ -415,6 +415,53 @@ class JSON(ModelFieldFactory, pydantic.Json):
return sqlalchemy.JSON()
class LargeBinary(ModelFieldFactory, bytes):
"""
LargeBinary field factory that construct Field classes and populated their values.
"""
_type = bytes
def __new__( # type: ignore # noqa CFQ002
cls, *, max_length: int = None, **kwargs: Any
) -> BaseField: # type: ignore
kwargs = {
**kwargs,
**{
k: v
for k, v in locals().items()
if k not in ["cls", "__class__", "kwargs"]
},
}
return super().__new__(cls, **kwargs)
@classmethod
def get_column_type(cls, **kwargs: Any) -> Any:
"""
Return proper type of db column for given field type.
Accepts required and optional parameters that each column type accepts.
:param kwargs: key, value pairs of sqlalchemy options
:type kwargs: Any
:return: initialized column with proper options
:rtype: sqlalchemy Column
"""
return sqlalchemy.LargeBinary(length=kwargs.get("max_length"))
@classmethod
def validate(cls, **kwargs: Any) -> None:
"""
Used to validate if all required parameters on a given field type are set.
:param kwargs: all params passed during construction
:type kwargs: Any
"""
max_length = kwargs.get("max_length", None)
if max_length is None or max_length <= 0:
raise ModelDefinitionError(
"Parameter max_length is required for field LargeBinary"
)
class BigInteger(Integer, int):
"""
BigInteger field factory that construct Field classes and populated their values.

View File

@ -73,6 +73,8 @@ def convert_choices_if_needed( # noqa: CCR001
else value
)
choices = [round(float(o), precision) for o in choices]
elif field.__type__ == bytes:
value = value if isinstance(value, bytes) else value.encode("utf-8")
return value, choices

View File

@ -1,6 +1,7 @@
import datetime
import decimal
import uuid
from base64 import b64encode
from enum import Enum
import databases
@ -22,6 +23,10 @@ uuid1 = uuid.uuid4()
uuid2 = uuid.uuid4()
blob = b"test"
blob2 = b"test2icac89uc98"
class EnumTest(Enum):
val1 = "Val1"
val2 = "Val2"
@ -57,6 +62,7 @@ class Organisation(ormar.Model):
random_json: pydantic.Json = ormar.JSON(choices=["aa", '{"aa":"bb"}'])
random_uuid: uuid.UUID = ormar.UUID(choices=[uuid1, uuid2])
enum_string: str = ormar.String(max_length=100, choices=list(EnumTest))
blob_col: bytes = ormar.LargeBinary(max_length=100000, choices=[blob, blob2])
@app.on_event("startup")
@ -111,6 +117,7 @@ def test_all_endpoints():
"random_json": '{"aa":"bb"}',
"random_uuid": str(uuid1),
"enum_string": EnumTest.val1.value,
"blob_col": blob.decode("utf-8"),
},
)

View File

@ -1,6 +1,6 @@
import asyncio
import uuid
import datetime
import uuid
from typing import List
import databases
@ -9,7 +9,7 @@ import pytest
import sqlalchemy
import ormar
from ormar.exceptions import QueryDefinitionError, NoMatch, ModelError
from ormar.exceptions import ModelError, NoMatch, QueryDefinitionError
from tests.settings import DATABASE_URL
database = databases.Database(DATABASE_URL, force_rollback=True)
@ -26,6 +26,20 @@ class JsonSample(ormar.Model):
test_json = ormar.JSON(nullable=True)
blob = b"test"
blob2 = b"test2icac89uc98"
class LargeBinarySample(ormar.Model):
class Meta:
tablename = "my_bolbs"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True)
test_binary = ormar.LargeBinary(max_length=100000, choices=[blob, blob2])
class UUIDSample(ormar.Model):
class Meta:
tablename = "uuids"
@ -102,15 +116,8 @@ class Country(ormar.Model):
)
@pytest.fixture(scope="module")
def event_loop():
loop = asyncio.get_event_loop()
yield loop
loop.close()
@pytest.fixture(autouse=True, scope="module")
async def create_test_database():
def create_test_database():
engine = sqlalchemy.create_engine(DATABASE_URL)
metadata.drop_all(engine)
metadata.create_all(engine)
@ -151,6 +158,19 @@ async def test_json_column():
assert items[1].test_json == dict(aa=12)
@pytest.mark.asyncio
async def test_binary_column():
async with database:
async with database.transaction(force_rollback=True):
await LargeBinarySample.objects.create(test_binary=blob)
await LargeBinarySample.objects.create(test_binary=blob2)
items = await LargeBinarySample.objects.all()
assert len(items) == 2
assert items[0].test_binary == blob
assert items[1].test_binary == blob2
@pytest.mark.asyncio
async def test_uuid_column():
async with database:

View File

@ -57,7 +57,7 @@ def create_test_database():
def assert_type(book: Book):
print(book)
_ = str(book)
@pytest.mark.asyncio