bump version, update realease, convert enums to vals

This commit is contained in:
collerek
2021-02-03 14:26:40 +01:00
parent a028e96f3e
commit 867d480728
7 changed files with 89 additions and 25 deletions

View File

@ -16,7 +16,11 @@ jobs:
- name: Install dependencies - name: Install dependencies
run: | run: |
python -m pip install --upgrade pip python -m pip install --upgrade pip
pip install mkdocs-material pip install mkdocs-material pydoc-markdown
- name: Build Api docs
run: pydoc-markdown -- build --site-dir=api
- name: Copy APi docs
run: cp -Tavr ./build/docs/content/ ./docs/api/
- name: Deploy - name: Deploy
run: | run: |
mkdocs gh-deploy --force mkdocs gh-deploy --force

View File

@ -145,6 +145,49 @@ Sample:
When loaded it's always python UUID so you can compare it and compare two formats values between each other. When loaded it's always python UUID so you can compare it and compare two formats values between each other.
### Enum
Although there is no dedicated field type for Enums in `ormar` you can change any
field into `Enum` like field by passing a `choices` list that is accepted by all Field types.
It will add both: validation in `pydantic` model and will display available options in schema,
therefore it will be available in docs of `fastapi`.
If you still want to use `Enum` in your application you can do this by passing a `Enum` into choices
and later pass value of given option to a given field (note tha Enum is not JsonSerializable).
```python
# not that imports and endpoints declaration
# is skipped here for brevity
from enum import Enum
class TestEnum(Enum):
val1 = 'Val1'
val2 = 'Val2'
class TestModel(ormar.Model):
class Meta:
tablename = "org"
metadata = metadata
database = database
id: int = ormar.Integer(primary_key=True)
# pass list(Enum) to choices
enum_string: str = ormar.String(max_length=100, choices=list(TestEnum))
# sample payload coming to fastapi
response = client.post(
"/test_models/",
json={
"id": 1,
# you need to refer to the value of the `Enum` option
# if called like this, alternatively just use value
# string "Val1" in this case
"enum_string": TestEnum.val1.value
},
)
```
[relations]: ../relations/index.md [relations]: ../relations/index.md
[queries]: ../queries.md [queries]: ../queries.md
[pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types [pydantic]: https://pydantic-docs.helpmanual.io/usage/types/#constrained-types

View File

@ -187,6 +187,7 @@ Available Model Fields (with required args - optional ones in docs):
* `BigInteger()` * `BigInteger()`
* `Decimal(scale, precision)` * `Decimal(scale, precision)`
* `UUID()` * `UUID()`
* `EnumField` - by passing `choices` to any other Field type
* `ForeignKey(to)` * `ForeignKey(to)`
* `ManyToMany(to, through)` * `ManyToMany(to, through)`

View File

@ -1,3 +1,11 @@
# 0.9.1
## Features
* Add choices values to `OpenAPI` specs, so it looks like native `Enum` field in the result schema.
## Fixes
* Fix `choices` behavior with `fastapi` usage when special fields can be not initialized yet but passed as strings etc.
# 0.9.0 # 0.9.0
## Important ## Important

View File

@ -65,7 +65,7 @@ class UndefinedType: # pragma no cover
Undefined = UndefinedType() Undefined = UndefinedType()
__version__ = "0.9.0" __version__ = "0.9.1"
__all__ = [ __all__ = [
"Integer", "Integer",
"BigInteger", "BigInteger",

View File

@ -1,6 +1,7 @@
import datetime import datetime
import decimal import decimal
import uuid import uuid
from enum import Enum
from typing import ( from typing import (
Any, Any,
Dict, Dict,
@ -88,13 +89,19 @@ def check_if_field_has_choices(field: Type[BaseField]) -> bool:
return hasattr(field, "choices") and bool(field.choices) return hasattr(field, "choices") and bool(field.choices)
def convert_choices_if_needed( def convert_choices_if_needed( # noqa: CCR001
field: Type["BaseField"], values: Dict field: Type["BaseField"], values: Dict
) -> Tuple[Any, List]: ) -> Tuple[Any, List]:
""" """
Converts dates to isoformat as fastapi can check this condition in routes Converts dates to isoformat as fastapi can check this condition in routes
and the fields are not yet parsed. and the fields are not yet parsed.
Converts enums to list of it's values.
Converts uuids to strings.
Converts decimal to float with given scale.
:param field: ormar field to check with choices :param field: ormar field to check with choices
:type field: Type[BaseField] :type field: Type[BaseField]
:param values: current values of the model to verify :param values: current values of the model to verify
@ -103,7 +110,8 @@ def convert_choices_if_needed(
:rtype: Tuple[Any, List] :rtype: Tuple[Any, List]
""" """
value = values.get(field.name, ormar.Undefined) value = values.get(field.name, ormar.Undefined)
choices = list(field.choices) choices = [o.value if isinstance(o, Enum) else o for o in field.choices]
if field.__type__ in [datetime.datetime, datetime.date, datetime.time]: if field.__type__ in [datetime.datetime, datetime.date, datetime.time]:
value = value.isoformat() if not isinstance(value, str) else value value = value.isoformat() if not isinstance(value, str) else value
choices = [o.isoformat() for o in field.choices] choices = [o.isoformat() for o in field.choices]
@ -111,7 +119,7 @@ def convert_choices_if_needed(
value = str(value) if not isinstance(value, str) else value value = str(value) if not isinstance(value, str) else value
choices = [str(o) for o in field.choices] choices = [str(o) for o in field.choices]
elif field.__type__ == decimal.Decimal: elif field.__type__ == decimal.Decimal:
precision = field.precision # type: ignore precision = field.scale # type: ignore
value = ( value = (
round(float(value), precision) round(float(value), precision)
if isinstance(value, decimal.Decimal) if isinstance(value, decimal.Decimal)

View File

@ -1,6 +1,7 @@
import datetime import datetime
import decimal import decimal
import uuid import uuid
from enum import Enum
import databases import databases
import pydantic import pydantic
@ -21,6 +22,11 @@ uuid1 = uuid.uuid4()
uuid2 = uuid.uuid4() uuid2 = uuid.uuid4()
class TestEnum(Enum):
val1 = "Val1"
val2 = "Val2"
class Organisation(ormar.Model): class Organisation(ormar.Model):
class Meta: class Meta:
tablename = "org" tablename = "org"
@ -39,18 +45,18 @@ class Organisation(ormar.Model):
) )
expire_datetime: datetime.datetime = ormar.DateTime( expire_datetime: datetime.datetime = ormar.DateTime(
choices=[datetime.datetime(2021, 1, 1, 10, 0, 0), choices=[
datetime.datetime(2022, 5, 1, 12, 30)] datetime.datetime(2021, 1, 1, 10, 0, 0),
datetime.datetime(2022, 5, 1, 12, 30),
]
) )
random_val: float = ormar.Float(choices=[2.0, 3.5]) random_val: float = ormar.Float(choices=[2.0, 3.5])
random_decimal: decimal.Decimal = ormar.Decimal(scale=4, precision=2, random_decimal: decimal.Decimal = ormar.Decimal(
choices=[decimal.Decimal(12.4), decimal.Decimal(58.2)] scale=2, precision=4, choices=[decimal.Decimal(12.4), decimal.Decimal(58.2)]
) )
random_json: pydantic.Json = ormar.JSON( random_json: pydantic.Json = ormar.JSON(choices=["aa", '{"aa":"bb"}'])
choices=["aa", "{\"aa\":\"bb\"}"] random_uuid: uuid.UUID = ormar.UUID(choices=[uuid1, uuid2])
) enum_string: str = ormar.String(max_length=100, choices=list(TestEnum))
random_uuid: uuid.UUID = ormar.UUID(
choices=[uuid1, uuid2])
@app.on_event("startup") @app.on_event("startup")
@ -86,10 +92,7 @@ def test_all_endpoints():
with client as client: with client as client:
response = client.post( response = client.post(
"/items/", "/items/",
json={"id": 1, json={"id": 1, "ident": "", "priority": 4, "expire_date": "2022-05-01"},
"ident": "",
"priority": 4,
"expire_date": "2022-05-01"},
) )
assert response.status_code == 422 assert response.status_code == 422
@ -105,8 +108,9 @@ def test_all_endpoints():
"expire_datetime": "2022-05-01T12:30:00", "expire_datetime": "2022-05-01T12:30:00",
"random_val": 3.5, "random_val": 3.5,
"random_decimal": 12.4, "random_decimal": 12.4,
"random_json": "{\"aa\":\"bb\"}", "random_json": '{"aa":"bb"}',
"random_uuid": str(uuid1) "random_uuid": str(uuid1),
"enum_string": TestEnum.val1.value,
}, },
) )
@ -132,11 +136,7 @@ def test_schema_gen():
schema = app.openapi() schema = app.openapi()
assert "Organisation" in schema["components"]["schemas"] assert "Organisation" in schema["components"]["schemas"]
props = schema["components"]["schemas"]["Organisation"]["properties"] props = schema["components"]["schemas"]["Organisation"]["properties"]
for field in ["ident", "priority", "expire_date"]: for field in [k for k in Organisation.Meta.model_fields.keys() if k != "id"]:
assert "enum" in props.get(field) assert "enum" in props.get(field)
choices = Organisation.Meta.model_fields.get(field).choices
assert props.get(field).get("enum") == [
str(x) if isinstance(x, datetime.date) else x for x in choices
]
assert "description" in props.get(field) assert "description" in props.get(field)
assert "An enumeration." in props.get(field).get("description") assert "An enumeration." in props.get(field).get("description")