24

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

5th
  • 2,097
  • 3
  • 22
  • 41
  • I have tried using `__post_init__` or `__init__` - but pydantic seems to have a mind of its own – 5th Feb 19 '22 at 14:15

4 Answers4

35

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

miksus
  • 2,426
  • 1
  • 18
  • 34
  • And we can get using list(TestClass.__fields__.keys()) , a flat list of field names – Arindam Roychowdhury Apr 13 '23 at 13:33
  • 1
    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 @5th – dalonsoa Jun 16 '23 at 05:32
  • @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. – miksus Jun 16 '23 at 05:51
  • 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. – dalonsoa Jun 16 '23 at 07:30
10

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.

5th
  • 2,097
  • 3
  • 22
  • 41
  • 1
    classmethod has been around for a long time https://docs.python.org/2.7/library/functions.html#classmethod New in version 2.2. Changed in version 2.4: Function decorator syntax added. – Carl Jul 07 '22 at 23:28
1

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']
Jimothy
  • 25
  • 7
0

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'}
Galuoises
  • 2,630
  • 24
  • 30