From 5d04b0f9b2dea516b97880bb5f2d94935800a122 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9C=D0=B0=D1=80=D1=82=D1=8B=D0=BD=D0=BE=D0=B2=20=D0=9C?= =?UTF-8?q?=D0=B0=D0=BA=D1=81=D0=B8=D0=BC=20=D0=A1=D0=B5=D1=80=D0=B3=D0=B5?= =?UTF-8?q?=D0=B5=D0=B2=D0=B8=D1=87?= Date: Tue, 5 Mar 2024 18:14:42 +0300 Subject: [PATCH] [DOP-13337] Add Pydantic v2 support --- .github/workflows/test.yml | 23 ++++++--- conftest.py | 2 +- docs/changelog/next_release/14.feature.rst | 1 + horizon_hwm_store/horizon_hwm_store.py | 51 +++++++++++-------- requirements-docs.txt | 2 +- requirements.txt | 2 +- .../tests_unit/test_horizon_hwm_store_unit.py | 2 +- 7 files changed, 50 insertions(+), 33 deletions(-) create mode 100644 docs/changelog/next_release/14.feature.rst diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 518247a..d5dc593 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,13 +17,19 @@ env: jobs: tests: - name: Run ${{ matrix.mark }} tests (Python ${{ matrix.python-version }} on ${{ matrix.os }}) + name: Run tests (Python ${{ matrix.python-version }}, Pydantic ${{ matrix.pydantic-version }}, ${{ matrix.os }}) runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: - python-version: ['3.7', '3.12'] - os: [ubuntu-latest] + include: + - python-version: '3.7' + pydantic-version: '1' + os: ubuntu-latest + + - python-version: '3.12' + pydantic-version: '2' + os: ubuntu-latest steps: - name: Checkout code @@ -47,10 +53,10 @@ jobs: uses: actions/cache@v4 with: path: ~/.cache/pip - key: ${{ runner.os }}-python-${{ matrix.python-version }}-tests-${{ hashFiles('requirements*.txt') }} + key: ${{ runner.os }}-python-${{ matrix.python-version }}-pydantic-${{ matrix.pydantic-version}}-tests-${{ hashFiles('requirements*.txt') }} restore-keys: | - ${{ runner.os }}-python-${{ matrix.python-version }}-tests-${{ hashFiles('requirements*.txt') }} - ${{ runner.os }}-python-${{ matrix.python-version }}-tests- + ${{ runner.os }}-python-${{ matrix.python-version }}-pydantic-${{ matrix.pydantic-version}}-tests-${{ hashFiles('requirements*.txt') }} + ${{ runner.os }}-python-${{ matrix.python-version }}-pydantic-${{ matrix.pydantic-version}}-tests- ${{ runner.os }}-python ${{ runner.os }}- @@ -58,7 +64,7 @@ jobs: run: python -m pip install --upgrade pip setuptools wheel - name: Install dependencies - run: pip install -I -r requirements.txt -r requirements-test.txt + run: pip install -I -r requirements.txt -r requirements-test.txt "pydantic==${{ matrix.pydantic-version }}.*" - name: Build package run: | @@ -68,6 +74,7 @@ jobs: - name: Run tests run: | mkdir reports/ || echo "Directory exists" + # required to register package in etl-entities pip install -e . --no-build-isolation source .env.local ./run_tests.sh @@ -75,7 +82,7 @@ jobs: - name: Upload coverage results uses: actions/upload-artifact@v4 with: - name: coverage-${{ matrix.python-version }}-os-${{ matrix.os }} + name: coverage-python-${{ matrix.python-version }}-pydantic-${{ matrix.pydantic-version }}-os-${{ matrix.os }} path: reports/* - name: Shutdown Backend Container diff --git a/conftest.py b/conftest.py index db060f6..c555429 100644 --- a/conftest.py +++ b/conftest.py @@ -138,7 +138,7 @@ def ensure_namespace(): ) try: - store._client.create_namespace(NamespaceCreateRequestV1(name=HORIZON_NAMESPACE)) # noqa: WPS437 + store.client.create_namespace(NamespaceCreateRequestV1(name=HORIZON_NAMESPACE)) # noqa: WPS437 except HTTPError: # exception: 409 Client Error: Conflict for url: http://horizon/v1/namespaces/ - namespace already exists pass diff --git a/docs/changelog/next_release/14.feature.rst b/docs/changelog/next_release/14.feature.rst new file mode 100644 index 0000000..bda8f10 --- /dev/null +++ b/docs/changelog/next_release/14.feature.rst @@ -0,0 +1 @@ +Add Pydantic v2 support. diff --git a/horizon_hwm_store/horizon_hwm_store.py b/horizon_hwm_store/horizon_hwm_store.py index 4198d3c..b7f994b 100644 --- a/horizon_hwm_store/horizon_hwm_store.py +++ b/horizon_hwm_store/horizon_hwm_store.py @@ -14,7 +14,15 @@ HWMUpdateRequestV1, NamespacePaginateQueryV1, ) -from pydantic import Field, PrivateAttr + +try: + from pydantic.v1 import AnyHttpUrl, Field, PrivateAttr +except ImportError: + from pydantic import ( # type: ignore[no-redef, assignment] + AnyHttpUrl, + Field, + PrivateAttr, + ) @register_hwm_store_class("horizon") @@ -136,23 +144,24 @@ def main(config): """ - api_url: str + api_url: AnyHttpUrl auth: LoginPassword namespace: str retry: RetryConfig = Field(default_factory=RetryConfig) timeout: TimeoutConfig = Field(default_factory=TimeoutConfig) - _client: HorizonClientSync = PrivateAttr() - _namespace_id: Optional[int] = PrivateAttr() - - def __init__(self, **kwargs): - super().__init__(**kwargs) - self._client = HorizonClientSync( # noqa: WPS601 - base_url=self.api_url, - auth=self.auth, - retry=self.retry, - timeout=self.timeout, - ) - self._namespace_id = None # noqa: WPS601 + _client: Optional[HorizonClientSync] = PrivateAttr(default=None) + _namespace_id: Optional[int] = PrivateAttr(default=None) + + @property + def client(self) -> HorizonClientSync: + if not self._client: + self._client = HorizonClientSync( # noqa: WPS601 + base_url=self.api_url, + auth=self.auth, + retry=self.retry, + timeout=self.timeout, + ) + return self._client def get_hwm(self, name: str) -> Optional[HWM]: namespace_id = self._get_namespace_id() @@ -160,7 +169,7 @@ def get_hwm(self, name: str) -> Optional[HWM]: if hwm_id is None: return None - hwm = self._client.get_hwm(hwm_id) + hwm = self.client.get_hwm(hwm_id) hwm_data = hwm.dict(exclude={"id", "namespace_id", "changed_by", "changed_at"}) hwm_data["modified_time"] = hwm.changed_at return HWMTypeRegistry.parse(hwm_data) @@ -174,13 +183,13 @@ def set_hwm(self, hwm: HWM) -> str: hwm_id = self._get_hwm_id(namespace_id, hwm.name) # type: ignore if hwm_id is None: create_request = HWMCreateRequestV1.parse_obj(hwm_dict) - response = self._client.create_hwm(create_request) + response = self.client.create_hwm(create_request) else: update_request = HWMUpdateRequestV1.parse_obj(hwm_dict) - response = self._client.update_hwm(hwm_id, update_request) + response = self.client.update_hwm(hwm_id, update_request) # TODO: update response string after implementing UI - return f"{self._client.base_url}/v1/hwm/{response.id}" + return f"{self.client.base_url}/v1/hwm/{response.id}" def check(self) -> HorizonHWMStore: """ @@ -199,7 +208,7 @@ def check(self) -> HorizonHWMStore: Self """ - self._client.whoami() + self.client.whoami() self._get_namespace_id() return self @@ -220,7 +229,7 @@ def _get_namespace_id(self) -> int: if self._namespace_id is not None: return self._namespace_id - namespaces = self._client.paginate_namespaces(NamespacePaginateQueryV1(name=self.namespace)).items + namespaces = self.client.paginate_namespaces(NamespacePaginateQueryV1(name=self.namespace)).items if not namespaces: raise RuntimeError(f"Namespace {self.namespace!r} not found. Please create it before using.") @@ -244,5 +253,5 @@ def _get_hwm_id(self, namespace_id: int, hwm_name: str) -> Optional[int]: The ID of the HWM, or None if it does not exist. """ hwm_query = HWMPaginateQueryV1(namespace_id=namespace_id, name=hwm_name) - hwms = self._client.paginate_hwm(hwm_query).items + hwms = self.client.paginate_hwm(hwm_query).items return hwms[-1].id if hwms else None diff --git a/requirements-docs.txt b/requirements-docs.txt index 0ef5b3a..4883d56 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,4 +1,4 @@ -autodoc_pydantic<2 +autodoc_pydantic furo numpydoc sphinx diff --git a/requirements.txt b/requirements.txt index 3fba505..362339f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ data-horizon[client-sync]>=0.0.8,<0.1 -etl-entities>=2.1.0,<2.3.0 +etl-entities>=2.1.0,<2.4.0 diff --git a/tests/tests_unit/test_horizon_hwm_store_unit.py b/tests/tests_unit/test_horizon_hwm_store_unit.py index ac6a36f..c46981c 100644 --- a/tests/tests_unit/test_horizon_hwm_store_unit.py +++ b/tests/tests_unit/test_horizon_hwm_store_unit.py @@ -64,7 +64,7 @@ def test_horizon_hwm_store_init(caplog): assert password not in caplog.text -def test_horizon_hwm_no_schema(caplog): +def test_horizon_hwm_no_schema(): with pytest.raises(ValueError, match="invalid or missing URL scheme"): HorizonHWMStore( api_url="some.domain.com",