Short way to get all field names of a pydantic class
Asked Answered
R

5

35

Minimal example of the class:

from pydantic import BaseModel

class AdaptedModel(BaseModel):
    def get_all_fields(self, alias=False):
        return list(self.schema(by_alias=alias).get("properties").keys())

class TestClass(AdaptedModel):
    test: str

The way it works:

dm.TestClass.get_all_fields(dm.TestClass)

Is there a way to make it work without giving the class again?

Desired way to get all field names:

dm.TestClass.get_all_fields()

It would also work if the field names are assigned to an attribute. Just any way to make it make it more readable

Riding answered 19/2, 2022 at 10:11 Comment(1)
I have tried using __post_init__ or __init__ - but pydantic seems to have a mind of its ownRiding
W
56

What about just using __fields__:

from pydantic import BaseModel

class AdaptedModel(BaseModel):
    parent_attr: str

class TestClass(AdaptedModel):
    child_attr: str
        
TestClass.__fields__

Output:

{'parent_attr': ModelField(name='parent_attr', type=str, required=True),
 'child_attr': ModelField(name='child_attr', type=str, required=True)}

This is just a dict and you could get only the field names simply by: TestClass.__fields__.keys()

See model properties: https://pydantic-docs.helpmanual.io/usage/models/#model-properties

Whiffle answered 2/6, 2022 at 18:24 Comment(5)
And we can get using list(TestClass.__fields__.keys()) , a flat list of field namesHistoplasmosis
This certainly works, but I don't feel that using a restricted attribute like __fields__ is the pythonic way of doing this. If it is restricted, it is for a reason. I'd go for the classmethod solution suggested by @RidingSmollett
@dalonsoa, I wouldn't say magic attributes (such as __fields__) are necessarily meant to be restricted in terms of reading (magic attributes are a bit different than private attributes). It just means they have some special purpose and they probably shouldn't be overridden accidentally. The docs describe this attribute and do not say you shouldn't use it so I don't think this is meant to be a restricted attribute in terms of reading.Whiffle
Sure, point taken - I'm not against it. But I still thing it is a bit misleading to use magic attributes as a public interface for a python class. As, in general and in most cases, they are meant to be for internal use only.Smollett
The __fields__ attribute is deprecated, use model_fields instead.Barclay
R
15

Update January 2024: This answer was my method to get the fields before pydantic v1. In v1 the __fields__ was added. This was then depreced with the introduction of model_fields in v2.


Okay the solution is to use a class-method instead of an instance method:

from pydantic import BaseModel, Field

class AdaptedModel(BaseModel):
    @classmethod
    def get_field_names(cls,alias=False):
        return list(cls.schema(alias).get("properties").keys())

class TestClass(AdaptedModel):
    test: str = Field(alias="TEST")

We are using Python 3.6.8 and apparently it was already introduce. For completeness sake can somebody comment since when @classmethod exists? I assume since 3.6.

Riding answered 19/2, 2022 at 15:55 Comment(1)
classmethod has been around for a long time docs.python.org/2.7/library/functions.html#classmethod New in version 2.2. Changed in version 2.4: Function decorator syntax added.Asleyaslope
O
7

You can do simply

TestClass.model_fields
Onesided answered 28/12, 2023 at 13:52 Comment(2)
Just for context: I asked this Question with Pydantic before it even reached v1. Getting a list of model fields was a pain. The model_fields is a new property since v2Riding
@Riding in v1.x i was able to use dict(TestClass).keys() to get a list of fieldsHighstepper
B
3

Here's a solution that combines the answers from miksus and 5th to support listing field names by their alias:

from pydantic import BaseModel
from pydantic.fields import ModelField, Field

class AdaptedModel(BaseModel):
    base_field_1: str = Field(alias="base_field_1_alias")

    @classmethod
    def get_field_names(cls, by_alias=False) -> list[str]:
        field_names = []
        for k, v in cls.__fields__.items():
            if by_alias and isinstance(v, ModelField):
                field_names.append(v.alias)
            else:
                field_names.append(k)

        return field_names

class TestClass(AdaptedModel):
    test_field_1: str = Field(alias="test_field_1_alias")
    test_field_2: str

To be used in the following manner:

print(TestClass.get_field_names(by_alias=True))

which outputs

['base_field_1_alias', 'test_field_1_alias', 'test_field_2']

Or you can get a list of non-aliased names with by_alias=False (the default):

print(TestClass.get_field_names(by_alias=False))

which outputs:

['base_field_1', 'test_field_1', 'test_field_2']

Edit

Here's a pydantic v2 equivalent:

from pydantic import BaseModel
from pydantic.fields import Field


class AdaptedModel(BaseModel):
    base_field_1: str = Field(alias="base_field_1_alias")

    @classmethod
    def get_field_names(cls, by_alias=False) -> list[str]:
        field_names = []
        for k, v in cls.model_fields.items():
            if by_alias and v.alias:
                field_names.append(v.alias)
            else:
                field_names.append(k)

        return field_names

class TestClass(AdaptedModel):
    test_field_1: str = Field(alias="test_field_1_alias")
    test_field_2: str
Bestiary answered 21/6, 2023 at 15:57 Comment(2)
As of 21/03/2024, ModelField seems to be throwing an error when using Pydantic version 2.x, here's an open discussion: github.com/pydantic/pydantic/discussions/7185Linea
@Linea I added the pydantic v2 equivalentBestiary
Z
1

If you need also the type of each field you might just use jsonref:

import jsonref
from pprint import pprint
from enum import Enum

class Values(Enum):
    a = 'a'
    b = 'b'


class Mdl(BaseModel):
    par: Values = Field(
        title="par",
        description="description of my parameter"
    )
    par2: str = Field(
        title="par2",
        description="description of my parameter"
    )
    par3: int = Field(
        title="par3",
        description="description of my parameter"
    )

    class Config:
        """ Automatically convert enum to values """
        use_enum_values = True


pprint(jsonref.loads(Mdl.schema_json()))

produces

{'definitions': {'Values': {'description': 'An enumeration.',
                            'enum': ['a', 'b'],
                            'title': 'Values'}},
 'properties': {'par': {'allOf': [{'title': 'Values', 'description': 'An enumeration.', 'enum': ['a', 'b']}],
                        'description': 'description of my parameter',
                        'title': 'MyParameter'},
                'par2': {'description': 'description of my parameter',
                         'title': 'MyParameter',
                         'type': 'string'},
                'par3': {'description': 'description of my parameter',
                         'title': 'MyParameter',
                         'type': 'integer'}},
 'required': ['par', 'par2', 'par3'],
 'title': 'Mdl',
 'type': 'object'}

Latter might further cleaned with

sch = jsonref.loads(Mdl.schema_json())
    for par in sch['properties']:
        if 'allOf' in sch['properties']['par']:
            if 'enum' in sch['properties']['par']['allOf'][0]:
                sch['properties']['par']['title'] = sch['properties']['par']['allOf'][0]['title']
                sch['properties']['par']['allowed_values'] = sch['properties']['par']['allOf'][0]['enum']
                sch['properties']['par'].pop('allOf')

that returns

{'definitions': {'Values': {'description': 'An enumeration.',
                            'enum': ['a', 'b'],
                            'title': 'Values'}},
 'properties': {'par': {'allowed_values': ['a', 'b'],
                        'description': 'description of my parameter',
                        'title': 'Values'},
                'par2': {'description': 'description of my parameter',
                         'title': 'MyParameter',
                         'type': 'string'},
                'par3': {'description': 'description of my parameter',
                         'minimum': 0,
                         'title': 'MyParameter',
                         'type': 'integer'}},
 'required': ['par', 'par2', 'par3'],
 'title': 'Mdl',
 'type': 'object'}
Zymometer answered 7/3, 2023 at 14:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.