mypy - Item "None" of "Optional[CustomAttrsModel]" has no attribute "country" - python

When I run mypy checkings I am getting an error. I am no able to ignore it or turn it off the strict optional checking. It there a way to solve this.
Here is the line that is throwing the error:
if tree.data.attributes.custom != JAPAN:
where attributes is declared as:
class TreeAttributesModel(BaseModel):
id: Optional[TreeId]
name: Optional[str] = None
status: StatusEnum
custom: Optional[CustomAttrsModel] = None
and CustomAttrsModel is declared as it follows:
class CustomAttrsModel(BaseModel):
seller: Optional[str]
buyed_at: Optional[datetime]
country: Optional[Union[CountryEnum, str]]
Could you please help me with this?

I had to tweak your snippets a bit to get a MWE, but here we go:
import enum
import dataclasses
from datetime import datetime
from typing import Optional, Union
class StatusEnum(enum.Enum):
OK = enum.auto()
NOK = enum.auto()
class CountryEnum(enum.Enum):
JAPAN = enum.auto()
RAPTURE = enum.auto()
#dataclasses.dataclass
class TreeAttributesModel:
id: Optional[str]
name: Optional[str] # = None had to remove default, attribs w/o default cannot follow attribs w/ one
status: StatusEnum
custom: Optional[CustomAttrsModel] = None
#dataclasses.dataclass
class CustomAttrsModel:
seller: Optional[str]
buyed_at: Optional[datetime]
country: Optional[Union[CountryEnum, str]]
custom = CustomAttrsModel(seller="test", buyed_at=None, country=CountryEnum.JAPAN)
attribs = TreeAttributesModel(id="test", name="test", status=StatusEnum.OK, custom=custom)
assert attribs.custom is not None # this is typed as being optional, so make sure it isn't None
assert attribs.custom.country is not None # same as above
result = attribs.custom.country != CountryEnum.JAPAN
The message is: just use assert something is not None whenever something is Optional ;)

Related

Fastapi - How to ignore optional arguments passed to my function?

I'm creating an API (FastAPI) that can create database in my catalog. The python function that creates the db takes few arguments. Some are optional (like Description, LocationUri, Parameters) and some are mandatory (CatalogId, etc). I created a Pydantic model that defines these arguments.
class createGlueDatabaseDatabaseInput(BaseModel):
Name: str
Description: Optional[str] = None
LocationUri: Optional[str] = None
Parameters: Optional[dict] = None
class Config:
orm_mode = True
class createGlueDatabase(BaseModel):
CatalogId: str
DB_input: createGlueDatabaseDatabaseInput
class Config:
orm_mode = True
In the above, the catalog id is the only mandatory argument, and the rest are optional. So when the optional parameters are ignored or not provided in the swagger, those values are coming in as "None" to the function. This results in the function failing.
I tried doing the following in my code:
Added response_model_exclude_none=True to my router function that receives the input argument, but this didnt help.
Tried to create everything (other than the catalog id) as optional, still no success.
Can someone help me understand, how to ignore None being sent to my python function? Please let me know if you need any other details. Thanks in advance.
Tried using Pydantic models:
class createGlueDatabaseDatabaseInput(BaseModel):
Name: str
Description: Optional[str] = None
LocationUri: Optional[str] = None
Parameters: Optional[dict] = None
class Config:
orm_mode = True
class createGlueDatabase(BaseModel):
CatalogId: str
DB_input: createGlueDatabaseDatabaseInput
class Config:
orm_mode = True
Tried adding additional args like response_model_exclude_none=True, but didnt work.
Can you provide more code for a better answer?
I think that you need to ignore None in your my python function. This is normal practice. See for example https://fastapi.tiangolo.com/tutorial/body/#use-the-model.
class createGlueDatabaseDatabaseInput(BaseModel):
Name: str
Description: Optional[str] = None
LocationUri: Optional[str] = None
Parameters: Optional[dict] = None
class Config:
orm_mode = True
app = FastAPI()
#app.post("/my_route")
async def create_item(input_data: createGlueDatabaseDatabaseInput):
# here
...
If you pass the input to the POST with the following body
{
"Name": "MyName"
}
then in the place where the comment # here we have
createGlueDatabaseDatabaseInput(Name='MyName', Description=None, LocationUri=None, Parameters=None)
It is serialization of pydantic.
For the Parameters field you can to add the following initial value
class createGlueDatabaseDatabaseInput(BaseModel):
Name: str
Description: Optional[str] = None
LocationUri: Optional[str] = None
Parameters: Optional[dict] = Field(default_factory=dict)
class Config:
orm_mode = True
Then we will have
createGlueDatabaseDatabaseInput(Name='MyName', Description=None, LocationUri=None, Parameters={})
where createGlueDatabaseDatabaseInput.Parameters is empty dict.

How to set the default model to a variable if None was passed?

Can I make a default model in Pydantic if None is passed in the field?
I am new to pydantic and searched for relevant answers but could not find any. I have the following code,
from typing import Optional,List
from pydantic import BaseModel
class FieldInfo(BaseModel):
value: Optional[str] = ""
confidence: Optional[float] = -1.0
class UserInfo(BaseModel):
first_name: Optional[FieldInfo]
last_name: Optional[FieldInfo]
user_info = {"user":{"first_name":["john",0.98], "last_name":["doe",0.98]}}
user = UserInfo(**{k:{"value":v[0],"confidence":v[1]} for k,v in user_info["user"].items()})
print(user)
gives me
first_name=FieldInfo(value='john', confidence=0.98) last_name=FieldInfo(value='doe', confidence=0.98)
but if last_name is None i.e.
user_info = {"user":{"first_name":["john",0.98]}}
user = UserInfo(**{k:{"value":v[0],"confidence":v[1]} for k,v in user_info["user"].items()})
print(user)
I get
first_name=FieldInfo(value='john', confidence=0.98) last_name=None
How can i get it take the default value if there is no values is passed, like below
first_name=FieldInfo(value='john', confidence=0.98) last_name=FieldInfo(value='', confidence=-1.0)
It should just include adding a default value in UserInfo.last_name like so:
from typing import Optional
from pydantic import BaseModel
class FieldInfo(BaseModel):
value: Optional[str] = ""
confidence: Optional[float] = -1.0
class UserInfo(BaseModel):
first_name: Optional[FieldInfo]
# Note I've added `= FieldInfo(...)` below
last_name: Optional[FieldInfo] = FieldInfo(value='', confidence=-1.0)

Get List Of Class's Attributes Including Attributes Of Sub Objects - Python

I want to get a list of all the attributes of the class including the attributes used in sub_objects of this class.
Example:
#dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
#dataclass
class People:
id: str
name: str
phones: Phones
I have People class and one of its attributes is of type Phones.
I want to return this list:
['id', 'name', 'mobile', 'work_phone']
I tried __dict__, __annotations__, dir() and more staff but I can't find a way to do it generic and dynamic. My solution is to do a convertor and return this list hardcoded which seems as a bad idea for maintenance.
I want all the attributes with primitive type. (For example I don't want to include phones.)
Recursion?
You dont need the sbNative thing, its just my module for clean logging.
from dataclasses import dataclass, is_dataclass
from typing import Optional
from sbNative.debugtools import log
#dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
#dataclass
class People:
id: str
name: str
phones: Phones
def find_dataclasses(cls):
classes = []
for obj in cls.__annotations__.values():
if is_dataclass(obj):
classes += find_dataclasses(obj)
classes.append(obj)
return classes
if __name__ == "__main__":
log(*find_dataclasses(People))
Thanks to https://stackoverflow.com/users/13526701/noblockhit
I managed to achieve what I wanted with the next code:
def list_attributes(entity: object) -> List[str]:
"""
#returns: List of all the primitive attributes
"""
attributes: List[str] = []
entity_attributes = entity.__annotations__.items()
for attribute_name, attribute_type in entity_attributes:
if is_dataclass(attribute_type):
attributes += list_attributes(attribute_type)
else:
attributes.append(attribute_name)
return attributes
This is good, but unfortunately it won't work if you have a more complex annotation, such as a container type like List[Phones]. For example:
#dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
#dataclass
class People:
id: str
name: str
phones: List[Phones]
The current output will be: ['id', 'name', 'phones']. But, note that we want to exclude the field phones, and include all the fields of the Phone class, mobile and work_phone.
To handle such types, you can use typing.get_args() and iterate over each of the subscripted types, checking if each type is a dataclass.
from dataclasses import dataclass, is_dataclass
from typing import Optional, List, get_args
#dataclass
class Phones:
mobile: Optional[str] = None
work_phone: Optional[str] = None
#dataclass
class People:
id: str
name: str
phones: List[Phones]
def list_attributes(entity: object) -> List[str]:
"""
#returns: List of all the primitive attributes
"""
attributes: List[str] = []
entity_attributes = entity.__annotations__.items()
for attribute_name, attribute_type in entity_attributes:
args = get_args(attribute_type)
if args:
found_class = False
for arg in args:
if is_dataclass(arg):
found_class = True
attributes += list_attributes(arg)
if not found_class:
attributes.append(attribute_name)
elif is_dataclass(attribute_type):
attributes += list_attributes(attribute_type)
else:
attributes.append(attribute_name)
return attributes
print(list_attributes(People))
The above modified version correctly outputs the desired result:
['id', 'name', 'mobile', 'work_phone']

How to pass the path parameter to the Pydantic model?

Is there some way to do the following?
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
id: str
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
app = FastAPI()
#app.post("/items/{item_id}")
async def create_item(item: Item):
return item
I want to have the item_id path parameter value inside the Item model.
Is it possible?
Pydantic in FastAPI is used to define data model. You can do as follows:
from typing import Optional
from fastapi import FastAPI
from pydantic import BaseModel
class Item(BaseModel):
id: Optional[str] = None
name: str
description: Optional[str] = None
price: float
tax: Optional[float] = None
app = FastAPI()
#app.post("/items/{item_id}")
async def create_item(item_id, item: Item):
item.id = item_id
return item
Please, note that id is declared as optional to avoid validation problems with body parameters in the request. Indeed, you are not going to pass the id in the body but in the path.
If necessary, you can also decouple the request- from the response-model (see here). It depends on what you need.
Replace your id: str with id: Optional [str] = None
Replace your create_item (item: Item) with create_item (item_id, item: Item):
Add item.id = item_id under your async. Obviously then you leave the ruo return item

How to extend a Pydantic object and change some fields' type?

There are two similar pydantic object like that. The only difference is some fields are optionally.
How can I just define the fields in one object and extend into another one?
class ProjectCreateObject(BaseModel):
project_id: str
project_name: str
project_type: ProjectTypeEnum
depot: str
system: str
...
class ProjectPatchObject(ProjectCreateObject):
project_id: str
project_name: Optional[str]
project_type: Optional[ProjectTypeEnum]
depot: Optional[str]
system: Optional[str]
...
I find a good and easy way by __init__subclass__.
The docs also can be generated successfully.
class ProjectCreateObject(BaseModel):
project_id: str
project_name: str
project_type: ProjectTypeEnum
depot: str
system: str
...
def __init_subclass__(cls, optional_fields=(), **kwargs):
"""
allow some fields of subclass turn into optional
"""
super().__init_subclass__(**kwargs)
for field in optional_fields:
cls.__fields__[field].outer_type_ = Optional
cls.__fields__[field].required = False
_patch_fields = ProjectCreateObject.__fields__.keys() - {'project_id'}
class ProjectPatchObject(ProjectCreateObject, optional_fields=_patch_fields):
pass
You've pretty much answered it yourself. Unless there's something more to the question.
from typing import Optional
from pydantic import BaseModel
class ProjectCreateObject(BaseModel):
project_id: str
project_name: str
project_type: str
depot: str
system: str
class ProjectPatchObject(ProjectCreateObject):
project_name: Optional[str]
project_type: Optional[str]
depot: Optional[str]
system: Optional[str]
if __name__ == "__main__":
p = ProjectCreateObject(
project_id="id",
project_name="name",
project_type="type",
depot="depot",
system="system",
)
print(p)
c = ProjectPatchObject(project_id="id", depot="newdepot")
print(c)
Running this gives:
project_id='id' project_name='name' project_type='type' depot='depot' system='system'
project_id='id' project_name=None project_type=None depot='newdepot' system=None
Another way to look at it is to define the base as optional and then create a validator to check when all required:
from pydantic import BaseModel, root_validator, MissingError
class ProjectPatchObject(BaseModel):
project_id: str
project_name: Optional[str]
project_type: Optional[str]
depot: Optional[str]
system: Optional[str]
class ProjectCreateObject(ProjectPatchObject):
#root_validator
def check(cls, values):
for k, v in values.items():
if v is None:
raise MissingError()
return values
Or use metaclass like in this thread: Make every fields as optional with Pydantic
class AllOptional(pydantic.main.ModelMetaclass):
def __new__(self, name, bases, namespaces, **kwargs):
annotations = namespaces.get('__annotations__', {})
for base in bases:
annotations.update(base.__annotations__)
for field in annotations:
if not field.startswith('__') and field != 'project_id':
annotations[field] = Optional[annotations[field]]
namespaces['__annotations__'] = annotations
return super().__new__(self, name, bases, namespaces, **kwargs)
And in your example...
class ProjectPatchObject(ProjectCreateObject, metaclass=AllOptional):
...

Categories

Resources