python pydantic how to mark field as secret - python

How to mark pydantic model filed as secret so it will not shown in the repr str and will be excluded from dict and etc...
from pydantic import BaseModel
class User(BaseModel):
name: str
password_hash: str # I do not want this field to leak out.
I write my code with security in mind and I afraid that in the future someone else will write non secure code that will leak the 'password_hash' field outside to the logs etc...
Is there a way to mark the field as secret to be sure it not leak out?

Pydantic provides convenience Secret* classes for this exact purpose:
from pydantic import BaseModel, SecretStr
class User(BaseModel):
name: str
password_hash: SecretStr

Related

Pydantic field does not take value

While attempting to name a Pydantic field schema, I received the following error:
NameError: Field name "schema" shadows a BaseModel attribute; use a different field name with "alias='schema'".
Following the documentation, I attempted to use an alias to avoid the clash. See code below:
from pydantic import StrictStr, Field
from pydantic.main import BaseModel
class CreateStreamPayload(BaseModel):
name: StrictStr
_schema: dict[str: str] = Field(alias='schema')
Upon trying to instantiate CreateStreamPayload in the following way:
a = CreateStreamPayload(name= "joe",
_schema= {"name": "a name"})
The resulting instance has only a value for name, nothing else.
a.dict()
{'name': 'joe'}
This makes absolutely no sense to me, can someone please explain what is happening?
Many thanks
From the documentation:
Class variables which begin with an underscore and attributes annotated with typing.ClassVar will be automatically excluded from the model.
In general, append an underscore to avoid conflicts as leading underscores are seen as either dunder (magic) members or private members: _schema ➡ schema_

Pydantic constr vs Field args

I wanted to know what is the difference between:
from pydantic import BaseModel, Field
class Person(BaseModel):
name: str = Field(..., min_length=1)
And:
from pydantic import BaseModel, constr
class Person(BaseModel):
name: constr(min_length=1)
Both seem to perform the same validation (even raise the exact same exception info when name is an empty string). Is it just a matter of code style? Is one of them preferred over the other?
Also, if I wanted to include a list of nonempty strings as an attribute, which of these ways do you think would be better?:
from typing import List
from pydantic import BaseModel, constr
class Person(BaseModel):
languages: List[constr(min_length=1)]
Or:
from typing import List
from pydantic import BaseModel, Field
class Person(BaseModel):
languages: List[str]
#validator('languages', each_item=True)
def check_nonempty_strings(cls, v):
if not v:
raise ValueError('Empty string is not a valid language.')
return v
EDIT:
FWIW, I am using this for a FastAPI app.
EDIT2:
For my 2nd question, I think the first alternative is better, as it includes the length requirement in the Schema (and so it's in the documentation)
constr and Fields don't serve the same purpose.
constr is a specific type that give validation rules regarding this specific type. You have equivalent for all classic python types.
arguments of constr:
strip_whitespace: bool = False: removes leading and trailing whitespace
to_lower: bool = False: turns all characters to lowercase
to_upper: bool = False: turns all characters to uppercase
strict: bool = False: controls type coercion
min_length: int = None: minimum length of the string
max_length: int = None: maximum length of the string
curtail_length: int = None: shrinks the string length to the set value when it is longer than the set value
regex: str = None: regex to validate the string against
As you can see thoses arguments allow you to manipulate the str itself not the behaviour of pydantic with this field.
Field doesn't serve the same purpose, it's a way of customizing fields, all fields not only str, it add 18 customization variables that you can find here.
Is it just a matter of code style? Is one of them preferred over the other?
for the specific case of str it is a matter of code style and what is preferred doesn't matter, only your usecase does.
In general it is better to don't mix different syntax toguether and since you often need Field(), you will find it often.
A classic use case would be api response that send json object in camelCase or PascalCase, you would use field alias to match thoses object and work with their variables in snake_case.
exemple:
class Voice(BaseModel):
name: str = Field(None, alias='ActorName')
language_code: str = None
mood: str = None
for your 2nd question you are right, using constr is surely the best approach since the validation rule will be added into the openapi doc.
If you want to learn more about limitation and field rules enforcement check this.
This link shows the methods that do and don't work for pydantic and mypy together: https://lyz-code.github.io/blue-book/coding/python/pydantic_types/#using-constrained-strings-in-list-attributes
The best option for my use case was to make a class that inherited from pydantic.ConstrainedStr as so:
import pydantic
from typing import List
...
class Regex(pydantic.ConstrainedStr):
regex = re.compile("^[0-9a-z_]*$")
class Data(pydantic.BaseModel):
regex: List[Regex]
# regex: list[Regex] if you are on 3.9+

Is it possible to inherit Python type annotations?

I'm learning a new tool called SQLModel, by Sebastian Ramirez (The FastAPI creator).
For basic CRUD operations in SQLModel, the docs teach you it's necessary to set up a model entity like this:
from typing import Optional, List
from sqlmodel import Field, SQLModel, Relationship
class RoomBase(SQLModel):
name: str
is_ensuite: bool
current_occupants: Optional[List[str]]
house_id: Optional[int] = Field(default=None, foreign_key="house.id")
class Room(RoomBase, table=True):
id: Optional[int] = Field(default=None, primary_key=True)
house: Optional["House"] = Relationship(back_populates="rooms")
class RoomCreate(RoomBase):
pass
class RoomRead(RoomBase):
id: int
class RoomUpdate(SQLModel):
name: Optional[str] = None
is_ensuite: Optional[bool] = None
current_occupants: Optional[List[str]] = None
house_id: Optional[int] = None
My example above will create a model called Room, which is part of a House. This would have to be repeated for every new model class, meaning I can forget about putting multiple models in the same file.
Lots of code for a little CRUD, right!?
Since it's likely that I will use the same CRUD setup 90% of the time (e.g. I will always want all the fields to be editable on an update, or I will always only need the ID for a read, etc.), it got me wondering whether the above could be abstracted, so that whole file didn't have to be repeated for EVERY SINGLE database entity.
Is it possible in Python to pass in fields and types by means of inheritance or otherwise, such that I would only need to write a generic version of the above code once, rather than having to write it all out for every model?
It appears you are using fastapi. If so, what about fastapi-crudrouter? It did the bulk of the work for me. While googling for the fastapi-crudrouter link, I found another project FastAPIQuickCrud. Just skimming, but it seems to solve the same problem.

What is the best way to handle conditionally required arguments in a FastAPI app?

I am developing a FastAPI application. I have with the following schema
class Address(BaseModel):
address_string: str = Field(None)
address_street: str = Field(None)
addres_number: str = Field(None)
I like to have the field address_string conditionally required if address_street and addres_number are not present, and vice-versa, address_street and address_number are required if address_street is not present.
Currently I manage this by making all fields optional and using a root_validator to check the consistency, and documenting this conditional requirement in the description of the involved fields.
Is there a cleaner way to manage this built-in on FastAPI?
Root validators, or validators on the optionally required fields are the beat solution.
Similar example on passwords here.

Required field with sensible default

Consider the following
from pydantic import BaseModel, Field
class Model(BaseModel):
required: str
This will make required a required field for Model, however, in the FastAPI autogenerated Swagger docs it will have an example value of "string".
How can I make a required field with a sensible default? If I make a model like
from pydantic import BaseModel, Field
class Model(BaseModel):
required: str = 'Sensible default'
Then the field required is no longer required, but it shows up with a sensible default in the docs. Is there an easy workaround for this?
You can use Field() to set up those options and check.
from pydantic import BaseModel, Field
class Model(BaseModel):
something: str # required, shows "string"
something: str = None # not required, shows "string"
something: str = Field(..., example="this is the default display") # required, shows example
something: str = Field(None, example="Foobar") #not required, show example
There are a multitude of different parameters that Field() can validate against.
I haven't looked into why the (pydantic) model representation within the openapi version that ships with FastAPI leaves the asterisk out, but the field is definitely still required (try putting a null value, or anything other than string). This might just be an UI inconsistency.

Categories

Resources