How do I define a not required Pydantic Field in a ModelBase that needs other fields to be defined without my Lintern complaining?

So, I am working in creating a kind of ORM for Azure Tables for my job, the problem is, I need to not pre-define the field called table_client because that doesn't make sense, I need to build the table_client after all validations because it needs self.connection_string and self.table_name (example below). Also, I need that attribute to be persistent between all the existence of the object, so I am not re-creating Table Clients every time I call for a method or something like that. So I need this attribute to be part of the model (i.e. I need self.table_client), the problem is, I cannot make it Optional or typed as table_client: TableClient | None = None because, below that, I receive a bunch of warnings from my linter saying "create_table" is not a known attribute of "None", or something like that.

What is the approach here? I cannot use default_factory because I need validated fields (connection_string and table_name) obviously (I think). Here is my current code (Assume all the imports are correct):

class AzureTable(BaseModel):
    connection_string: str
    table_name: str
    table_client: TableClient | None = None

    def __post_init__(self):
        self._create_connection()

    def _create_connection(self):
        self.table_client = TableServiceClient.from_connection_string( # Here is the freaking waning
            conn_str=self.connection_string
        ).get_table_client(self.table_name)

        return self

    def create_table(self) -> None:
        try:
            self.table_client.create_table(table_name=self.table_name)
        except HttpResponseError:
            raise TableAlreadyExistsError()

    def get_all(self) -> list:
        return list(self.table_client.list_entities())

More info:

Python: 3.12
Pydantic: 2.8.2
azure-data-tables: 12.5.0

Also tried:

class AzureTable(BaseModel):
    connection_string: str
    table_name: str

    def __post_init__(self):
        self._create_connection()

    def _create_connection(self):
        self.table_client = TableServiceClient.from_connection_string(
            conn_str=self.connection_string
        ).get_table_client(self.table_name)

        return self

    def create_table(self) -> None:
        try:
            self.table_client.create_table(table_name=self.table_name)
        except HttpResponseError:
            raise TableAlreadyExistsError()

    def get_all(self) -> list:
        return list(self.table_client.list_entities())

But gives me an error:

File "/home/azureuser/dev/LangBotAPI/venv/lib/python3.12/site-packages/pydantic/main.py", line 828, in __getattr__
    raise AttributeError(f'{type(self).__name__!r} object has no attribute {item!r}')
AttributeError: 'BotTable' object has no attribute 'table_client'

Upvotes: 0

Views: 143

Answers (1)

larsks
larsks

Reputation: 312390

Maybe you want a computed field? That would look something like this:

from functools import cached_property
from pydantic import BaseModel, computed_field

class AzureTable(BaseModel):
    connection_string: str
    table_name: str

    @computed_field
    @cached_propery
    def table_client(self) -> AppropriateReturnType:
        table_client = (
            TableServiceClient.from_connection_string(
                conn_str=self.connection_string
            ).get_table_client(self.table_name)
        )

        return table_client

I'm not at all familiar with the Azure modules (which is why the above example is annoted with AppropriateReturnType), but here's a runnable example using only native types to show how it works. Given the following:

from functools import cached_property
from pydantic import BaseModel, computed_field


class TableClient(BaseModel):
    pass


class AzureTable(BaseModel):
    connection_string: str
    table_name: str

    @computed_field
    @cached_property
    def table_client(self) -> str:
        return f"{self.table_name}@{self.connection_string}"

We can do this:

>>> x = AzureTable(connection_string='user@host', table_name='example')
>>> x
AzureTable(connection_string='user@host', table_name='example', table_client='example@user@host')
>>> x.table_client
'example@user@host'

Note that because we're using the cached_property decorator, table_client is only computed once.

Upvotes: 1

Related Questions