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_
Related
I generated a Pydantic model and would like to import it into SQLModel. Since said model does not inherit from the SQLModel class, it is not registered in the metadata which is why
SQLModel.metadata.create_all(engine)
just ignores it.
In this discussion I found a way to manually add models:
SQLModel.metadata.tables["hero"].create(engine)
But doing so throws a KeyError for me.
SQLModel.metadata.tables["sopro"].create(engine)
KeyError: 'sopro'
My motivation for tackling the problem this way is that I want to generate an SQLModel from a simple dictionary like this:
model_dict = {"feature_a": int, "feature_b": str}
And in this SO answer, I found a working approach. Thank you very much in advance for your help!
As far as I know, it is not possible to simply convert an existing Pydantic model to an SQLModel at runtime. (At least as of now.)
There are a lot of things that happen during model definition. There is a custom meta class involved, so there is no way that you can simply substitute a regular Pydantic model class for a real SQLModel class, short of manually monkeypatching all the missing pieces.
That being said, you clarified that your actual motivation was to be able to dynamically create an SQLModel class at runtime from a dictionary of field definitions. Luckily, this is in fact possible. All you need to do is utilize the Pydantic create_model function and pass the correct __base__ and __cls_kwargs__ arguments:
from pydantic import create_model
from sqlmodel import SQLModel
field_definitions = {
# your field definitions here
}
Hero = create_model(
"Hero",
__base__=SQLModel,
__cls_kwargs__={"table": True},
**field_definitions,
)
With that, SQLModel.metadata.create_all(engine) should create the corresponding database table according to your field definitions.
See this question for more details.
Be sure to use correct form for the field definitions, as the example you gave would not be valid. As the documentation says, you need to define fields in the form of 2-tuples (or just a default value):
model_dict = {
"feature_a": (int, ...),
"feature_b": (str, ...),
"feature_c": 3.14,
}
Hope this helps.
class Settings(BaseSettings):
SITE_URL: str
CONFIG = Settings()
>>> CONFIG.SITE_URL
returns str, and that's expected
Is it possible somehow to get access to dotted string representation of field?
CONFIG.SITE_URL.__some_magic_attr_ == 'CONFIG.SITE_URL'
Once initialized, the attribute of a Pydantic model is simply of the type that was defined for it. In this case SITE_URL is just a string. Thus, there is no special magic method, to get its field name.
Depending on your actual use case though, the __fields__ attribute of the model might be useful. It is a dictionary mapping field names to the ModelField objects. For example
from pydantic import BaseSettings
class Settings(BaseSettings):
SITE_URL: str
print(Settings.__fields__['SITE_URL'])
gives
name='SITE_URL' type=str required=True
If you have your settings object, you can for example do this:
for name in CONFIG.__fields__.keys():
print(f'{CONFIG.__class__.__name__}.{name}')
giving you
Settings.SITE_URL
...
If you want the name of the variable storing your settings object, I assume you can just write it as a string manually, i.e.
for name in CONFIG.__fields__.keys():
print(f'CONFIG.{name}')
I have this dataclass:
from dataclasses import dataclass, field
from typing import List
#dataclass
class Person:
name: str
dob: str
friends: List['Person'] = field(default_factory=list, init=False)
name and dob are immutable and friends is mutable. I want to generate a hash of each person object. Can I somehow specify which field to be included and excluded for generating the __hash__ method? In this case, name and dob should be included in generating the hash and friends shouldn't. This is my attempt but it doesn't work
#dataclass
class Person:
name: str = field(hash=True)
dob: str = field(hash=True)
friends: List['Person'] = field(default_factory=list, init=False, hash=False)
>>> hash(Person("Mike", "01/01/1900"))
Traceback (most recent call last):
File "<pyshell#43>", line 1, in <module>
hash(Person("Mike", "01/01/1900"))
TypeError: unhashable type: 'Person'
I also can't find a way to set name and dob to be frozen. And I'd refrain from setting unsafe_hash to True, just by the sound of it. Any suggestions?
Also, is what I'm doing considered good practice? If not, can you suggest some alternatives?
Thank you
Edit: This is just a toy example and we can assume that the name and dob fields are unique.
Edit: I gave an example to demonstrate the error.
Just indicate that the friends field should not be taken in account when comparing instances with __eq__, and pass hash=True to field instances on the desired fields.
Then, pass the unsafe_hash=True argument to the dataclass decorator itself - it will work as you intend (mostly):
In case of hash, the language restriction is that if one instance compares equal with another (__eq__), the hash of of both must be equal as well. The implication in this case is that if you have two instances of the "same" person with the same "name" and "dob" fields, they will be considered equal, even if they feature different friends lists.
Other than that, this should work:
from dataclasses import dataclass, field
from typing import List
#dataclass(unsafe_hash=True)
class Person:
name: str = field(hash=True)
dob: str = field(hash=True)
friends: List['Person'] = field(default_factory=list, init=False, compare=False, hash=False)
Then, remember to behave like a "consenting adult" and not change the "name" and "dob" fields of Person instances in any place, and you are set.
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.
I need to define a Django model field with the name in, which is a Python language keyword. This is a syntax error:
class MyModel(models.Model):
in = jsonfield.JSONField()
How can I make this work?
The reason I need this name is when I use django-rest-framework's ModelSerializer class, field name is used as the key for serialization output, and I thought it might be easier to manipulate django's Model class instead of ModelSerializer class to get the output I want.
Generally speaking, you don't. Avoid the use of keywords in your identifiers. The general Python convention is to add an underscore to such names; here that'd be in_:
class MyModel(models.Model):
in_ = jsonfield.JSONField()
However, Django prohibits names ending in an underscore because the underscore clashes with their filter naming conventions, so you have to come up with a different name still; pick one that still describes your case; I picked contained in rather than in, as a guess to what you want to do here:
class MyModel(models.Model):
contained_in = jsonfield.JSONField()
If you are trying to match an existing database schema, use the db_column attribute:
class MyModel(models.Model):
contained_in = jsonfield.JSONField(db_column='in')
If you want to be stubborn, in normal classes you could use setattr() after creating the class to use a string instead of an identifier:
class Foo:
pass
setattr(Foo, 'in', 'some value')
but you'll have to use setattr(), getattr(), delattr() and/or vars() everywhere in your code to be able to access this.
In Django you'll have the added complication that a models.Model subclass uses a metaclass to parse out your class members into others structures, and adding an extra field with setattr() doesn't work without (a lot of) extra work to re-do what the metaclass does. You could instead use the field.contribute_to() method, calling it after the class has been prepared by Django (technique taken from this blog post):
from django.db.models.signals import class_prepared
def add_field(sender, **kwargs):
if sender.__name__ == "MyModel":
field = jsonfield.JSONField('in')
field.contribute_to_class(sender, 'in')
class_prepared.connect(add_field)
but you have to make sure this hook is registered before you create your model class.
There is no way to make it work, and it's a bad idea anyway. Choose a different name.
If, for some reason, you want to have column name that matches some reserved keyword, use db_column argument for that field.
in_something = models.CharField(db_column='in', max_length=100)
You mentioned the use of django rest framework. Here's how to make it work on the serializer layer. The keyword used is from. to is just an example of a non-keyword if you want it mapped to a different name.
from django.db import models
from rest_framework import serializers
SP_FIELD_MAP = {
'from': 'sender'
}
# would be in models.py
class Transaction(models.Model):
recipient = models.CharField(max_length=16)
sender = models.CharField(max_length=64)
# would be in serializers.py
class TransactionSerializer(serializers.ModelSerializer):
to = serializers.CharField(source='recipient')
class Meta:
model = Transaction
fields = ('id', 'to', 'from')
# `from` is a python keyword hence this
extra_kwargs = {'from': {'source': 'sender'}}
def build_field(self, field_name, info, model_class, nested_depth):
# Catches python keywords like `from` and maps to its proper field
field_name = SP_FIELD_MAP.get(field_name, field_name)
return super(TransactionSerializer, self).build_field(
field_name, info, model_class, nested_depth)
Tested on CharField using POST and GET methods only but I don't see how it won't work on other methods. You might need special stuff for other field types though. I suggest going into the source. There's tons of fun stuff going on in DRF's source.
You should be giving all your variables descriptive names that clearly state what they are to be used for, and where possible it should be easy to assertain what type of variable it is.
in, to me, would appear at first glance to be a boolean so in order to use this variable in my own extension to the code I'd need to find other usages of it before I knew how I could use it.
Therefore, simply don't try to hack something together just so you can get this terrible variable name into your model, it offers no value to you to do so, its not really any quicker to type since intellisense is available in most places. Figure out what "in" relates to and then formulate a proper name that is descriptive.