diff --git a/pyairtable/api/api.py b/pyairtable/api/api.py index c8924890..ac1d8633 100644 --- a/pyairtable/api/api.py +++ b/pyairtable/api/api.py @@ -6,8 +6,10 @@ from typing_extensions import TypeAlias from pyairtable.api import retrying +from pyairtable.api.base import Base from pyairtable.api.enterprise import Enterprise from pyairtable.api.params import options_to_json_and_params, options_to_params +from pyairtable.api.table import Table from pyairtable.api.types import UserAndScopesDict, assert_typed_dict from pyairtable.api.workspace import Workspace from pyairtable.models.schema import Bases @@ -44,7 +46,7 @@ class Api: MAX_URL_LENGTH = 16000 # Cached metadata to reduce API calls - _bases: Optional[Dict[str, "pyairtable.api.base.Base"]] = None + _bases: Optional[Dict[str, "Base"]] = None endpoint_url: Url session: Session @@ -126,7 +128,7 @@ def base( *, validate: bool = False, force: bool = False, - ) -> "pyairtable.api.base.Base": + ) -> "Base": """ Return a new :class:`Base` instance that uses this instance of :class:`Api`. @@ -141,7 +143,7 @@ def base( if validate: info = self._base_info(force=force).base(base_id) return self._base_from_info(info) - return pyairtable.api.base.Base(self, base_id) + return Base(self, base_id) @cache_unless_forced def _base_info(self) -> Bases: @@ -158,15 +160,15 @@ def _base_info(self) -> Bases: } return Bases.from_api(data, self) - def _base_from_info(self, base_info: Bases.Info) -> "pyairtable.api.base.Base": - return pyairtable.api.base.Base( + def _base_from_info(self, base_info: Bases.Info) -> "Base": + return Base( self, base_info.id, name=base_info.name, permission_level=base_info.permission_level, ) - def bases(self, *, force: bool = False) -> List["pyairtable.api.base.Base"]: + def bases(self, *, force: bool = False) -> List["Base"]: """ Retrieve the base's schema and return a list of :class:`Base` instances. @@ -189,7 +191,7 @@ def create_base( workspace_id: str, name: str, tables: Sequence[Dict[str, Any]], - ) -> "pyairtable.api.base.Base": + ) -> "Base": """ Create a base in the given workspace. @@ -210,7 +212,7 @@ def table( *, validate: bool = False, force: bool = False, - ) -> "pyairtable.api.table.Table": + ) -> "Table": """ Build a new :class:`Table` instance that uses this instance of :class:`Api`. @@ -407,7 +409,3 @@ def enterprise(self, enterprise_account_id: str) -> Enterprise: Build an object representing an enterprise account. """ return Enterprise(self, enterprise_account_id) - - -import pyairtable.api.base # noqa -import pyairtable.api.table # noqa diff --git a/pyairtable/api/base.py b/pyairtable/api/base.py index 317bf365..2efd8ef2 100644 --- a/pyairtable/api/base.py +++ b/pyairtable/api/base.py @@ -1,8 +1,7 @@ import warnings from functools import cached_property -from typing import Any, Dict, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union -import pyairtable.api.api import pyairtable.api.table from pyairtable.models.schema import BaseCollaborators, BaseSchema, BaseShares from pyairtable.models.webhook import ( @@ -13,6 +12,9 @@ ) from pyairtable.utils import Url, UrlBuilder, cache_unless_forced, enterprise_only +if TYPE_CHECKING: + from pyairtable.api.api import Api + class Base: """ @@ -25,7 +27,7 @@ class Base: """ #: The connection to the Airtable API. - api: "pyairtable.api.api.Api" + api: "Api" #: The base ID, in the format ``appXXXXXXXXXXXXXX`` id: str @@ -67,7 +69,7 @@ def interface(self, interface_id: str) -> Url: def __init__( self, - api: Union["pyairtable.api.api.Api", str], + api: Union["Api", str], base_id: str, *, name: Optional[str] = None, @@ -99,7 +101,10 @@ def __init__( category=DeprecationWarning, stacklevel=2, ) - api = pyairtable.api.api.Api(api) + + from pyairtable import Api + + api = Api(api) self.api = api self.id = base_id diff --git a/pyairtable/api/enterprise.py b/pyairtable/api/enterprise.py index 557097e0..8183a55f 100644 --- a/pyairtable/api/enterprise.py +++ b/pyairtable/api/enterprise.py @@ -1,6 +1,16 @@ from datetime import date, datetime from functools import cached_property, partialmethod -from typing import Any, Dict, Iterable, Iterator, List, Literal, Optional, Union +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterable, + Iterator, + List, + Literal, + Optional, + Union, +) import pydantic from typing_extensions import Self @@ -17,6 +27,9 @@ enterprise_only, ) +if TYPE_CHECKING: + from pyairtable.api.api import Api + @enterprise_only class Enterprise: @@ -85,7 +98,7 @@ def remove_user(self, user_id: str) -> Url: urls = cached_property(_urls) - def __init__(self, api: "pyairtable.api.api.Api", workspace_id: str): + def __init__(self, api: "Api", workspace_id: str): self.api = api self.id = workspace_id self._info: Optional[EnterpriseInfo] = None @@ -612,8 +625,3 @@ class MoveWorkspacesResponse(AirtableModel): rebuild_models(vars()) - - -# These are at the bottom of the module to avoid circular imports -import pyairtable.api.api # noqa -import pyairtable.api.base # noqa diff --git a/pyairtable/api/table.py b/pyairtable/api/table.py index 235b5989..96b2f0a4 100644 --- a/pyairtable/api/table.py +++ b/pyairtable/api/table.py @@ -5,10 +5,19 @@ import warnings from functools import cached_property from pathlib import Path -from typing import Any, Dict, Iterable, Iterator, List, Optional, Union, overload +from typing import ( + TYPE_CHECKING, + Any, + Dict, + Iterable, + Iterator, + List, + Optional, + Union, + overload, +) import pyairtable.models -from pyairtable.api.retrying import Retry from pyairtable.api.types import ( FieldName, RecordDeletedDict, @@ -25,6 +34,11 @@ from pyairtable.models.schema import FieldSchema, TableSchema, parse_field_schema from pyairtable.utils import Url, UrlBuilder, is_table_id +if TYPE_CHECKING: + from pyairtable.api.api import Api, TimeoutTuple + from pyairtable.api.base import Base + from pyairtable.api.retrying import Retry + class Table: """ @@ -37,7 +51,7 @@ class Table: """ #: The base that this table belongs to. - base: "pyairtable.api.base.Base" + base: "Base" #: Can be either the table name or the table ID (``tblXXXXXXXXXXXXXX``). name: str @@ -82,8 +96,8 @@ def __init__( base_id: str, table_name: str, *, - timeout: Optional["pyairtable.api.api.TimeoutTuple"] = None, - retry_strategy: Optional[Retry] = None, + timeout: Optional["TimeoutTuple"] = None, + retry_strategy: Optional["Retry"] = None, endpoint_url: str = "https://api.airtable.com", ): ... @@ -91,7 +105,7 @@ def __init__( def __init__( self, api_key: None, - base_id: "pyairtable.api.base.Base", + base_id: "Base", table_name: str, ): ... @@ -99,14 +113,14 @@ def __init__( def __init__( self, api_key: None, - base_id: "pyairtable.api.base.Base", + base_id: "Base", table_name: TableSchema, ): ... def __init__( self, api_key: Union[None, str], - base_id: Union["pyairtable.api.base.Base", str], + base_id: Union["Base", str], table_name: Union[str, TableSchema], **kwargs: Any, ): @@ -210,7 +224,7 @@ def id_or_name(self, quoted: bool = True) -> str: return value @property - def api(self) -> "pyairtable.api.api.Api": + def api(self) -> "Api": """ The API connection used by the table's :class:`~pyairtable.Base`. """ @@ -801,8 +815,3 @@ def upload_attachment( } response = self.api.post(url, json=payload) return assert_typed_dict(UploadAttachmentResultDict, response) - - -# These are at the bottom of the module to avoid circular imports -import pyairtable.api.api # noqa -import pyairtable.api.base # noqa diff --git a/pyairtable/api/workspace.py b/pyairtable/api/workspace.py index 5293d4eb..7a63763c 100644 --- a/pyairtable/api/workspace.py +++ b/pyairtable/api/workspace.py @@ -1,9 +1,13 @@ from functools import cached_property -from typing import Any, Dict, List, Optional, Sequence, Union +from typing import TYPE_CHECKING, Any, Dict, List, Optional, Sequence, Union from pyairtable.models.schema import WorkspaceCollaborators from pyairtable.utils import Url, UrlBuilder, cache_unless_forced, enterprise_only +if TYPE_CHECKING: + from pyairtable.api.api import Api + from pyairtable.api.base import Base + class Workspace: """ @@ -33,7 +37,7 @@ class _urls(UrlBuilder): urls = cached_property(_urls) - def __init__(self, api: "pyairtable.api.api.Api", workspace_id: str): + def __init__(self, api: "Api", workspace_id: str): self.api = api self.id = workspace_id @@ -41,7 +45,7 @@ def create_base( self, name: str, tables: Sequence[Dict[str, Any]], - ) -> "pyairtable.api.base.Base": + ) -> "Base": """ Create a base in the given workspace. @@ -73,7 +77,7 @@ def collaborators(self) -> WorkspaceCollaborators: return WorkspaceCollaborators.from_api(payload, self.api, context=self) @enterprise_only - def bases(self) -> List["pyairtable.api.base.Base"]: + def bases(self) -> List["Base"]: """ Retrieve all bases within the workspace. """ @@ -103,7 +107,7 @@ def delete(self) -> None: @enterprise_only def move_base( self, - base: Union[str, "pyairtable.api.base.Base"], + base: Union[str, "Base"], target: Union[str, "Workspace"], index: Optional[int] = None, ) -> None: @@ -123,8 +127,3 @@ def move_base( if index is not None: payload["targetIndex"] = index self.api.post(self.urls.move_base, json=payload) - - -# These are at the bottom of the module to avoid circular imports -import pyairtable.api.api # noqa -import pyairtable.api.base # noqa diff --git a/pyairtable/orm/fields.py b/pyairtable/orm/fields.py index 27e00d9e..f2ac64b9 100644 --- a/pyairtable/orm/fields.py +++ b/pyairtable/orm/fields.py @@ -50,7 +50,7 @@ from typing_extensions import Self as SelfType from typing_extensions import TypeAlias -from pyairtable import utils +from pyairtable import formulas, utils from pyairtable.api.types import ( AITextDict, AttachmentDict, @@ -69,7 +69,7 @@ from pyairtable.orm.lists import AttachmentsList, ChangeTrackingList if TYPE_CHECKING: - from pyairtable.orm import Model # noqa + from pyairtable.orm import Model _ClassInfo: TypeAlias = Union[type, Tuple["_ClassInfo", ...]] @@ -610,7 +610,7 @@ def __init__( lazy: If ``True``, this field will return empty objects with only IDs; call :meth:`~pyairtable.orm.Model.fetch` to retrieve values. """ - from pyairtable.orm import Model # noqa, avoid circular import + from pyairtable.orm import Model if not ( model is _LinkFieldOptions.LinkSelf @@ -1588,7 +1588,3 @@ class CreatedTimeField(RequiredDatetimeField): "UrlField", ] # [[[end]]] (checksum: 87b0a100c9e30523d9aab8cc935c7960) - - -# Delayed import to avoid circular dependency -from pyairtable import formulas # noqa diff --git a/tests/test_api_api.py b/tests/test_api_api.py index 6583f4b9..458497b2 100644 --- a/tests/test_api_api.py +++ b/tests/test_api_api.py @@ -2,7 +2,7 @@ import pytest -from pyairtable import Api, Base, Table # noqa +from pyairtable import Api, Base, Table @pytest.fixture