-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Class attributes starting with underscore do not support assignment #288
Comments
(I've updated the issue title to better reflect the issue). Personally, I don't think it's that important, but if you'd like the feature, I'd be happy to accept a PR. |
I'm having this same problem in a non-workaroundable context. Can someone maybe give some context into why there is this restriction and if there's any way to hack around it, even if ugly? |
Have you tried using field aliases? eg, class MyModel(BaseModel):
foobar: str
class Config:
fields = {'foobar': '_foobar'} |
That works in a small example
I continue to have problems from the context of FastAPI. That's not the beginning of a new question, it's for the next person to arrive here from Google. Thanks for your help! |
👋 Oh hi! |
Yeah sorry. As I had said, field aliases worked in small toys examples but didn't seem to work in a larger app context, which made me suspect the problem was at FastAPI itself and therefore not pertinent to this issue. After further rounds of research and bug squashing, minor and subtle errors on my end (my code on top of FastAPI) surfaced. By then I had forgotten I had gone to Pydantic support for it. Thanks everyone! |
(On my end I would "vote this issue closed" or its equivalent in the github issue tracker. I suspect I can't because I didn't actually open it.) |
I'm pretty sure aliases work fine in fastapi. Closing, but if someone really wants this, let me know and I can reopen it. |
@asemic-horizon field aliases work normally in FastAPI and are even documented/recommended in several places. If you still have issues with them, feel free to open an issue in FastAPI with the minimum sample to reproduce your issue: https://github.com/tiangolo/fastapi/issues/new/choose |
I read the above but saw no explanation for this limitation. From brief study of the source code I could not determine any danger in sunder fields. This check is introduced in the first commit ever a8e844d and a check added in f9cf6b4 but no explanation given in commit messages. Python itself by design does not enforce any special meaning to sunder and dunder fields - the "we are all consenting adults here" approach. I am used to a convention of marking fields private-like with sunder. Using aliases is clumsy. I use Pydantic to save code lines and make code more readable. I would rather not add this magic. Also this prevents me from doing arguably dirty trick of having both I would very much like to know the motivation or have this restriction lifted. Still, I adore Pydantic, thanks! |
Hello there, I use pydantic in FastAPI as well, and I use mongodb as database. Mongo sets Identifiers as the field
class Foo(BaseModel):
id: str = Field(..., alias='_id')
...
query = db.collection.find(...)
validated = Foo(**query).dict()
# where validated = {'id': MONGO_HASH, ...}
# send validated as response Note: if I'd want to send the id like mongo does (if my API are used by another service that deals with queries and mongo for example) I need to use validated = Foo(**query).dict(by_alias=True)
# where validated = {'_id': MONGO_HASH, ...}
data = read_from_source(...)
# data = {'id': EXISTING_MONGO_HASH, ...}
db.collection.update(Foo(**data).dict(by_alias=True)) but if some other service is sending me raw data I would get the class Foo(BaseModel):
id: str = Field(..., alias='_id')
...
class Config:
allow_population_by_field_name = True otherwise I get a validation error There are other situations that I can't recall well enough to show with code, Hope this helps to reason on it, but thanks for this awesome library! |
Hello @samuelcolvin 👋 I just started using |
I still think most python users would not expect "private" attributes to be exposed as fields. I know python doesn't formally prevent attributes who's names start with an underscore from being accessed externally, but it's a pretty solid convention that you're hacking if you have to access attributes with leading underscores. (Yes I know there are some exceptions, but in the grand scheme they're rare). In the example quoted above regarding mongo, I would personally feel it's inelegant design to keep the mongo attribute Still, if people really want this, I'd accept a PR to allow field names with underscores. I guess the best solution would be to add a new method to Then in v2 we can completely remove None of my business, but: @gpakosz, unless i'm missing something, you're using a synchronous library for io (pymongo) while using an async web framework (starlette+fastapi). I would worry that your entire process will hang during every database call, thereby negating the point in asyncio. |
I'm agree that, in python, underscore variables are considered private, so I understand your point What about a config attribute like always_by_alias = True? |
You can just implement your own custom base model and overwrite I don't want to add more config options unless absolutely necessary. Also it would be weird since I think Perhaps we can change the default to True in V2 once we have load and dump aliases. |
Allowing fields with underscores by default would also conflict with #1139. |
I'm very new to pydantic, however i'm really impressed and really like it, however with the current implementation filtering out any field starting with an underscore which is completely unconfigurable main.py 162 |
Same thing happens in connexion framework. I want to use default mongo _id as the id. class FileMetadata(BaseModel):
_id: str
created_date: str Set id data = FileMetadata.parse_obj(info.to_dict())
data._id = my_custom_id Error
Change summary explained here solved the problem. class FileMetadata(BaseModel):
_id: str
created_date: str
class Config:
extra = Extra.allow |
Better to use an alias for that field. |
For the pymongo issue specifically I moved to mongoengine, which exposes |
The solution for me was using aliases and class FileMetadata(BaseModel):
id: str = Field(alias="_id") # can be done using the Config class
created_date: str
input_data = {"_id": "some id", "created_date": today()}
FileMetadata(**input_data).dict(by_alias=True) That should return I am using Pydantic with pymongo therefore having access to |
+1 on this, would be a nice feature. Ill use alias in the meantime, but this for sure caught me off-guard. |
I earlier thought I needed that feature, and also found a quite easy way to have it via monkey patching:
However I have to say that using an alias works very well now for me! |
#1679 would probably be a good solution |
Can we support this features. Our database table have leading underscore field for metadata (_created, _updated,...). I am using fastapi and pydantic as data layer to passing thing from user to database and vice versa. But the |
I had the same problem with pydantic. In my case that is the MUST. Looks like this problem is still ignored and the only solution is to use different library such as Marshmallow, Attrs or Dataclass |
Yeah, second all the to me, it seems like a It seems that the justification for why it's not supported is because Python conventionally (not enforced) uses underscores for privacy problems, but I would argue that Python metaclasses being a way of having the language rewrite itself throws that issue out the window, particularly in their use in Additionally, I would ask what is the core responsibility or value proposition of pydantic? which I believe is as a model parsing library with applications in messaging applications. (e.g. there's nothing stopping a protobuf loader in the Model Config),a and if that IS the value proposition, then the BaseModel declaration should look as close as possible to the model being parsed. so really, any restrictions on identifiers that aren't explicitly prohibited by the python language (like Just my two cents. If there's anything I can do to help push these changes along, I'm dying to get involved in OSS. |
Yea we also really need this for use with ArangoDB. |
So I've been working on this. What I needed is to easily cast between ObjectId and str to save to MongoDB or return data to frondend without "$oid" nested object. To keep things simple on the frontend, in data models in frontend, object ids are strings; not Objects with "$oid" like So I implement my data models like this: # to cast strings to object id for incoming data, especially from frontend
class MongoObjectId(str):
"Cast string to ObjectId."""
@classmethod
def __get_validators__(cls):
yield cls.validate
@classmethod
def validate(cls, v):
if bson.ObjectId.is_valid(v):
return bson.ObjectId(v)
raise ValueError(f'{v} is not a valid ObjectId for type {cls}')
def oidstr(d):
"""Recursively search for values that objectids and convert them to strings."""
if isinstance(d, dict):
return {k: oidstr(v) for k, v in d.items()}
if isinstance(d, list):
return [oidstr(v) for v in d]
if isinstance(d, bson.ObjectId):
return str(d)
return d
class MyModel:
def dict_with_str(self, *args, **kwargs):
d = BaseModel.dict(self, *args, **kwargs)
return oidstr(d)
class Person(MyModel, BaseModel):
person_id: MongoObjectId
id = '622076858cdc0a82c37f1f56'
print(model) # person_id=ObjectId('622076858cdc0a82c37f1f56')
print(model.dict(by_alias=True)) # {'person_id': ObjectId('622076858cdc0a82c37f1f56')}
print(model.dict_with_str(by_alias=True)) # {'person_id': '622076858cdc0a82c37f1f56'} I don't know if I'm overcomplicating things while trying to keep it simple for front end. Please share your thoughts. Thank you. |
As @frndlytm said:
Regardless of the underlying database, Pydantic should not enforce what is only a convention. This is definitely not an expected behavior. |
These aren't allowed with pydantic: pydantic/pydantic#288 and https://stackoverflow.com/q/59562997/11477374 The leading underscore was auto-generated by datamodel-codegen and only caught now. *However*, just 'schema' is also a bad idea, it shadows the built-in method: https://pydantic-docs.helpmanual.io/usage/schema/
I also need a possibility to use underscore attributes. |
Maybe this helps: from typing import Any
from pydantic import BaseModel
class PrivateInitBaseModel(BaseModel):
"Workaround for initializing models by specifying their private attributes"
def __init__(self, **data: Any) -> None:
for private_key in self.__class__.__private_attributes__.keys():
try:
value = data.pop(private_key)
except KeyError:
...
finally:
setattr(self, private_key, value)
super().__init__(**data) |
I just got bitten by this issue while migration from class CollectionAggregateId(BaseModel):
field1: str
field2: str
field3: str
class CollectionAggregateModel(BaseModel):
_id: CollectionAggregateId
count: int AttributeError: 'CollectionAggregateModel' object has no attribute '_id' Using alias is not an option for me because I have data with both |
Can the original question be solved in any way? I tried using alias, but still |
I have just started to use Pydantic in a couple of applications I am working on at the moment. The issue I am facing, which is probably related to the design I am using is the following:
The problem I am facing is that no matter how I call the
Now I might be thinking too complicated, and surely I could use composition and pass a logger instance to the constructor of the Market, but that would cause to add a lot of boiler plate code that I want to avoid at least at the high level APIs. Any idea on how I can still inherit from a Pydantic |
I'm thinking it might be possible to do something with More explicitly, you should be able to do something like: class MyModel:
a: int
b: str # whatever, random fields
logger = MyLogger()
class Config:
... # whatever other values you want to set on the Config
keep_untouched = (MyLogger,) Any chance this approach might work for you? |
logger is not seems to be public variable. use __logger instead |
Thanks for your suggestions, It appears I have managed to solve this problem at least at the moment, by changing the design and going for composition instead of inheritance. So I have now my model classes which have a class attribute called schema_class which is type bound to my schema superclass (a Pydantic BaseModel extension) and they have a from_data and to_data methods, which allows to read the serialized structure, validate the model using the schema_class, and store it as data storage as self.data into the model class. With the composition design I have also managed to create a PersistenceEnabled mixin which allow me to extend the main Model with SQLAlchemy persistence, using the same pattern, by declaring a db_class class attribute, and providing saving, loading and searching features on the model. This of course requires to have 3 classes implemented:
I didn't really solve the problem, but I went around it. Every piece is smaller, has clearer responsibilities and doesn't cause interference between the different frameworks. |
Sorry if I am being naive here, but after reading this thread, I came across this: https://docs.pydantic.dev/latest/usage/models/#private-model-attributes This seems to solve this problem?
|
@tommyjcarpenter I believe pydantic v2 was just released, and it looks like the diff in that code block is: # v1.1:
_secret_value: str = PrivateAttr()
# v2:
_secret_value: str so maybe it's that |
@rreusser sorry I don't follow - just saying, I used PrivateAttr and it seemed to solve all the commotion in this thread. |
@tommyjcarpenter Oh, sorry, I misread! Disregard. I've done the same, but I'm on v1.1 still, and when I clicked the above link I noticed that maybe this has changed slightly in v2. |
Bug
For bugs/questions:
import sys; print(sys.version)
: 3.6.6import pydantic; print(pydantic.VERSION)
: 0.14In #184 it was suggested to use variables starting with the underscore, however this does not work.
The last comment in #184 referred to the same problem, but it is technically a separate issue.
The text was updated successfully, but these errors were encountered: