Skip to content
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

Add Title Generator to Support Programmatic Titles #4632

Open
Tracked by #9102
samuelcolvin opened this issue Oct 18, 2022 Discussed in #4044 · 6 comments · May be fixed by #9183
Open
Tracked by #9102

Add Title Generator to Support Programmatic Titles #4632

samuelcolvin opened this issue Oct 18, 2022 Discussed in #4044 · 6 comments · May be fixed by #9183
Assignees
Labels
feature request good first issue non-breaking-change Addressing this issue does not require a breaking change

Comments

@samuelcolvin
Copy link
Member

Discussed in #4044

Originally posted by zkulis May 4, 2022

Background

Currently, Pydantic allows specifying a string literal for the title value used in the exported JSON Schema. For BaseModel, this is accomplished via the Config class:

from pydantic import BaseModel

class MyDataClass(BaseModel):
   class Config:
      title = "My Data Class"

For BaseModel fields, an explicit title string may be specified with a Field instance:

from pydantic import BaseModel, Field
from typing import List

class MyDataClass(BaseModel):
   matchCriteria: List[str] = Field(default_factory=list, title="Match Criteria")
   class Config:
      title = "My Data Class"

Feature Request

It would be beneficial if Pydantic provided a programmatic way to set the title value for both models and fields, similar to the functionality provided by alias_generator here. Specifically:

  1. A title_generator class attribute in the Config class, and
  2. A title_generator parameter in the Field constructor.

In both cases, the title_generator would take a Python callable that receives:

  1. The BaseModel class name for the first case
  2. The field alias for the second case

This is demonstrated below:

import re
from pydantic import BaseModel

def make_title(name: str):
   def _capitalize(v: str):
      return v[0].upper() + v[1:]
   return re.sub(r"(?<=[a-z])([A-Z])", r" \1", _capitalize(name))

class MyDataClass(BaseModel):
   matchCriteria: List[str] = Field(default_factory=list, title_generator=make_title)
   class Config:
      title_generator=make_title

The resulting JSON Schema would be:

import json
print(json.dumps(MyDataClass.schema(), indent=2))

{
  "title": "My Data Class",
  "type": "object",
  "properties": {
    "matchCriteria": {
      "title": "Match Criteria",
      "type": "array",
      "items": {
        "type": "string"
      }
    }
  }
}
@samuelcolvin
Copy link
Member Author

I agree this is a good idea, should be fairly straightforward in v2.

@dmontagu
Copy link
Contributor

dmontagu commented May 17, 2023

I'll just repeat some of my comment from #1361, (with some updates):

I have no problems with this, but I think that we should give the model-config-key and field-info-key different names — rather than using title_generator for both, we should use class_title_generator or similar for BaseModels/dataclasses/etc., and field_title_generator for field titles, otherwise I think it will lead to confusion.

I'll also note that this will require some care around the use of something like field_title_priority with similar behavior to alias_priority, and perhaps similar for model_title_priority.

@dmontagu dmontagu added the non-breaking-change Addressing this issue does not require a breaking change label May 18, 2023
@tibbe
Copy link

tibbe commented Jul 18, 2023

Is there any workaround for this today? I've tried using __init_subclass__ to give all my models a custom title generated from cls.__module__ and cls.__name__.

Using just the class name, like the default behavior does, doesn't work in large projects. If you use e.g. FastAPI to generate an OpenAPI spec (which uses JSON schema) then we can't generate a reasonable OpenAPI client library from the OpenAPI spec. The reason is that we get a huge flat namespace of all model names, which we can't map back into e.g. namespaces. In other words the current approach is lossy.

@tibbe
Copy link

tibbe commented Jul 18, 2023

In v1 the following workaround seems to work:

class _Base(pydantic.BaseModel):
    class Config:
        @staticmethod
        def schema_extra(schema: dict[str, Any], model: type['_Base']) -> None:
            schema['title'] = f'{model.__module__}.{model.__name__}'

@tibbe
Copy link

tibbe commented Jul 18, 2023

Thinking about this some more it's not actually the title I want to control over but the ID generated as the key in the components map. The title is just for human consumption, I want to preserve the model's module name in the generate OpenAPI spec so I can generate an OpenAPI client with a similar structure.

@Martin19037
Copy link

Is there any news for this title generation feature?

When creating model json schemas, the current default title generation often does not play nicely when using aliases or alias generators. Consider the following toy example:

from pydantic import BaseModel, ConfigDict
from pydantic.alias_generators import to_camel


class MyModel(BaseModel):
    my_field: str
    my_other_field: int

    model_config = ConfigDict(alias_generator=to_camel)

When calling MyModel.model_json_schema(), the fields are converted to camel case as expected, but the titles come back as Myfield and Myotherfield. My guess is that the title generation in pydantic.json_schema.GenerateJsonSchema.get_title_from_name assumes a certain naming style to generate sensible titles.

As a workaround I am currently using a custom GenerateJsonSchema class that overrides get_title_from_name, which is then passed to model_json_schema(schema_generator=...) to better suit my case.

Having a title generator on the model config would be a convenient solution to this problem. The definitions for both the alias generator and title generator would be closer to each other, and it is more obvious what kind of values the title generator should mostly expect.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request good first issue non-breaking-change Addressing this issue does not require a breaking change
Projects
None yet
Development

Successfully merging a pull request may close this issue.

4 participants