0

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'

1 Answer 1

1

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.

Sign up to request clarification or add additional context in comments.

1 Comment

Yeah, that make complete sense, thanks a lot, I thought maybe there would be a more pydantic way of doing this (maybe this is the pydantic way and I am not that pydantic), so if you or someone else find something else please let me know, but for now it seems this works just fine. Appreciate it. I used the cached_property decorator, but using computed_field also is a great added 🤗.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.