Is it possible on a pydantic model to reference another class? For example below in the ReadRequestModel in point_type I am trying to figure out if its possible reference that only these "types of points" in a string format can be chosen:
# type-of-points
# just for reference
multiStateValue
multiStateInput
multiStateOutput
analogValue
analogInput
analogOutput
binaryValue
binaryInput
binaryOutput
And depending on what point_type is that depics what type of point_id can be chosen that I am trying to reference in the PointType class.
from typing import List, Literal, Optional
from pydantic import BaseModel
BOOLEAN_ACTION_MAPPING = Literal["active", "inactive"]
class ReadRequestModel(BaseModel):
device_address: str
point_type: PointType <--- not correct
point_id: PointType <--- not correct
class PointType(BaseModel):
multiStateValue: Optional[int]
multiStateInput: Optional[int]
multiStateOutput: Optional[int]
analogValue: Optional[int]
analogInput: Optional[int]
analogOutput: Optional[int]
binaryValue: Optional[BOOLEAN_ACTION_MAPPING]
binaryInput: Optional[BOOLEAN_ACTION_MAPPING]
binaryOutput: Optional[BOOLEAN_ACTION_MAPPING]
r = ReadRequestModel({'device_address': '12345:5',
'point_type': 'analogInput',
'point_id': 8})
print(r)
The idea for the request above r it should be valid because the point_type is correct (per type-of-points) and the point_id for an analogInput is an int type. Hopefully this makes sense not a lot of wisdom here but there is documentation for this on the pydantic website but I am having some difficulties trying to figure it out. Any tips appreciated.
The code above that has some major issues will just print that the point_type: PointType NameError: name 'PointType' is not defined
Try like this:
from typing import Literal
class ReadRequestModel(BaseModel):
device_address: str
point_type: Literal["multiStateValue", "multiStateInput", "multiStateOutput", "analogValue", "analogInput", "analogOutput", "binaryValue", "binaryInput", "binaryOutput"]
point_id: int
or
from enum import Enum
class PointTypeEnum(str, Enum):
multiStateValue = "multiStateValue"
multiStateInput = "multiStateInput"
multiStateOutput = "multiStateOutput"
analogValue = "analogValue"
analogInput = "analogInput"
analogOutput = "analogOutput"
binaryValue = "binaryValue"
binaryInput = "binaryInput"
binaryOutput = "binaryOutput"
class ReadRequestModel(BaseModel):
device_address: str
point_type: PointTypeEnum
point_id: int
This should allow you to use the PointTypeEnum class in a similar way to the Literal type, but with the added benefits of being able to iterate over the values, etc.
Python loads module content from top to bottom.
That means that you need to place your PointType definition before the ReadRequestModel and it will work
Related
I'm trying to have my Pydantic/ORM models "output" labels,but when using SqlAlchemy ORM I feel a bit locked/ stuck.
I want the fieldname "test1" ( below in the code) to return ( "Test left side") instead of test1 in the JSONResponse
As an explanation, In sql i would for example use “as”
select test1 as ‘Test left side’ from pretest
I know I can do the same in an ORM statement/query, but I want it to be reachable as a field/ attribute from the orm model class, or perhaps as some validation methods from my Pydantic model.
To explain I have added an example of two short models below
my model in Sqlalchemy:
class Pretest(Base):
__tablename__ = "pretest"
user_id = Column(Integer)
pretest_id = Column(Integer, primary_key=True)
timestamp_pretest = Column(DateTime(timezone=True), default=func.now())
test1 = Column(Integer)
my model in Pydantic: ( I use Optional because im testing at the moment)
class Pretest(BaseModel):
user_id: Optional[int] = None
pretest_id: Optional[int] = None
timestamp_pretest: Optional[datetime] = None
test1: Optional[int] = None
class Config:
orm_mode= True
So I’m wondering if Pydantic have a possibility to validate against a labelslist/array/ object that could contain a type of test1= “Test left side”
Or if the ORM models have some additional metadata that could be used like
test1 = Column(Integer, alias=“Test left side”)
I hope I make this question understandable?
My endpoint look a bit simplified something like this:
#router.post("/pretest", tags=["Medicaldata"], status_code=status.HTTP_200_OK)
def pretest(pretest: Pretest, token: str = Depends(oauth2_scheme)):
try:
query = db.query(models.Pretest).first()
except:
query = "query failed"
return JSONResponse(content=query)
Where I use the pretest-object which is type defined by the Pydantic model as query parameters( not shown here)
This response will create a json-object of the fields and values in the database.
The field/variable test1 will return as test1 instead of "Test left side", since I do not have a place to add labels or some sort of aliases.
I can add and map the json object manually in Python before I return it, but it’s a lot of complex queries spanning several tables, so it feels a bit “wrong” to do it that way.
The reason for all this is so that I can have model and label consistency and use the map function with spread operators in components in React as shown below.
get_backend(“/pretest”,data)
.then setPretestlist(response)
{pretestlist.map((item) => {
return <ShowPretest {...item} key={item.name} />;
})}
This will now show as test1 in the webpage instead of a more explanatory text like this "Test 1 left side"
#snakecharmerb, Thx, you put me on the right track.
The solution, if someone else wonder:
the Pydantic model needs to be changed from this:
class Pretest(BaseModel):
user_id: Optional[int] = None
pretest_id: Optional[int] = None
timestamp_pretest: Optional[datetime] = None
test1: Optional[int] = None
class Config:
orm_mode = True
To this:
class Pretest(BaseModel):
user_id: Optional[int] = None
pretest_id: Optional[int] = None
timestamp_pretest: Optional[datetime] = None
test1: Optional[int] = None
class Config:
fields = {
"test1": "Test left side",
"timestamp_pretest": "Time tested",
}
orm_mode = True
The endpoint needed to change in the way it does its response to this:
#router.post("/pretest", tags=["Medicaldata"], status_code=status.HTTP_200_OK)
def pretest(pretest: Pretest, token: str = Depends(oauth2_scheme)):
try:
query = db.query(models.Pretest).first()
query = Pretest.from_orm(query)
except:
query = "query failed"
return query.dict(by_alias=True)
I have a Base class, to get the values from the frontend. The user_id should comes from the Depends(deps.get_current_user) function.
How can i initialize the new class CommentCreate now, with the values from CommentBase?
And is this the common use?
I got it worked with the workaround extra=Extra.allow and setattr(message,...)
I think it should be something like: commentCreate = schemas.CommentCreate(**comment, user_id=current_user.id)
class CommentBase(BaseModel, extra=Extra.allow):
blog_id: int
message: str
class CommentCreate(CommentBase):
user_id: int
#router.post("/post/comment")
def post_comment_reply(
message: schemas.CommentBase,
current_user: models.User = Depends(deps.get_current_active_user),
db: Session = Depends(deps.get_db),
):
setattr(message, "user_id", current_user.id)
print(message)
You can use the dict method to achieve this.
something like :
CommentCreate(user_id=current_user , **message.dict())
There is probably an stupidly obvious solution to this but I'm new to python and can't find it. I'm working out a few of the systems for a practice project I'm working on and I can't seem to get this to work:
class Item:
def __init__(self, name, description, type, mindamage, maxdamage):
self.name = name
self.desc = description
self.type = type
self.mindmg = mindamage
self.maxdmg = maxdamage
woodsman = Item("'Woodsman'", "An automatic chambered in .22lr", "gun", 4, 10)
inspect = input("inspect:").lower()
print(inspect.name)
print(inspect.desc)
print(inspect.type)
I can't find a solution to this for some reason.
Use dataclasses and items dict:
from dataclasses import dataclass
#dataclass
class Item:
name: str
description: str
item_type: str # don't use 'type' for variables name, it's reserved name
min_damage: int
max_damage: int
woodsman = Item(
name="'Woodsman'",
description="An automatic chambered in .22lr",
item_type="gun",
min_damage=4,
max_damage=10
)
# other items...
items = {
"woodsman": woodsman,
# other items...
}
inspect = items.get(input("inspect:").lower())
print(inspect.name)
print(inspect.description)
print(inspect.item_type)
This might be closer to what you're trying to do:
inventory = {
"woodsman": Item("'Woodsman'","An automatic chambered in .22lr","gun",4,10)
}
inspect = inventory[input("inspect:").lower()]
print(inspect.name)
print(inspect.desc)
print(inspect.type)
Note that you will probably want to have some kind of error handling in case the user enters an item that doesn't exist in the inventory.
I was fiddling around and found another solution that works for me:
inspect = input("inspect:").lower()
exec("print(" + inspect + ".name)")
I had namedtuple variable which represents version of application (its number and type). But i want and some restriction to values:
Version = namedtuple("Version", ["app_type", "number"])
version = Version("desktop") # i want only "desktop" and "web" are valid app types
version = Version("deskpop") # i want to protect from such mistakes
My solution for now is primitive class with no methods:
class Version:
def __init__(self, app_type, number):
assert app_type in ('desktop', 'web')
self.app_type = app_type
self.number = number
Is it pythonic? Is it overkill?
You could use enum.Enum, and typing.NamedTuple instead of collections.namedtuple:
Maybe something like this:
from typing import NamedTuple
import enum
class AppType(enum.Enum):
desktop = 0
web = 1
class Version(NamedTuple):
app: AppType
v0 = Version(app=AppType.desktop)
v1 = Version(app=AppType.web)
print(v0, v1)
output:
Version(app=<AppType.desktop: 0>) Version(app=<AppType.web: 1>)
A undefined AppType raises an AttributeError:
v2 = Version(app=AppType.deskpoop)
output:
AttributeError: deskpoop
I am using the amazing attrs library to define a lot of object attributes in a very elegant way and it has been working like a charm so far.
The only problem that I am currently having is that I sometimes want to define default values by referencing other attr.ib() attributes. Here is some code that would run if the default for name were a static string:
import attr
from attr.validators import instance_of
import datetime
#attr.s
class Something:
some_date = attr.ib(validator=instance_of(datetime.date))
some_number = attr.ib(convert=float)
name = attr.ib(validator=instance_of(str),
default="Generic Name {0} - {1}%".format(
some_date.strftime("%d-%b-%Y"),
some_number * 100)
)
something_instance = Something(some_date=datetime.date.today(), some_number=0.375)
The problem is that name doesn't see a float and a date, but a _CountingAttr object, hence I get an AttributeError (and a TypeError for some_number * 100). Since I can't reference self either, how do I do this?
So this seems not possible with the default keyword at the moment. However, to achieve the same effect, it's possible to use the __attrs_post_init__ method, which can used to execute arbitrary calculations after instance initialization: http://attrs.readthedocs.io/en/stable/examples.html?highlight=attrs_post_init#other-goodies
In my example it would basically come down to adding
def __attrs_post_init__(self):
if self.name is None:
self.name = "Generic Name {0} - {1}%".format(
self.some_date.strftime("%d-%b-%Y"),
self.some_number * 100)
Credit goes to the attrs github issue tracker for pointing me in the right direction.
You can also do it without __attrs_post_init__.
Just use default = attr.Factory(lambda self: ..., takes_self=True)
import attr
from attr.validators import instance_of
import datetime
#attr.s
class Something:
some_date = attr.ib(validator=instance_of(datetime.date))
some_number = attr.ib(convert=float)
name = attr.ib(validator=instance_of(str),
default=attr.Factory(lambda self: "Generic Name {0} - {1}%".format(
self.some_date.strftime("%d-%b-%Y"),
self.some_number * 100)
), takes_self=True)
something_instance = Something(some_date=datetime.date.today(), some_number=0.375)