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'
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.