Skip to content

Commit

Permalink
[DOP-13337] Add Pydantic v2 support
Browse files Browse the repository at this point in the history
  • Loading branch information
dolfinus committed Mar 5, 2024
1 parent e29a0c1 commit 5d04b0f
Show file tree
Hide file tree
Showing 7 changed files with 50 additions and 33 deletions.
23 changes: 15 additions & 8 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -47,18 +53,18 @@ 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 }}-
- name: Upgrade pip
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: |
Expand All @@ -68,14 +74,15 @@ 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
- 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
Expand Down
2 changes: 1 addition & 1 deletion conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
1 change: 1 addition & 0 deletions docs/changelog/next_release/14.feature.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add Pydantic v2 support.
51 changes: 30 additions & 21 deletions horizon_hwm_store/horizon_hwm_store.py
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -136,31 +144,32 @@ 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()
hwm_id = self._get_hwm_id(namespace_id, name)
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)
Expand All @@ -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:
"""
Expand All @@ -199,7 +208,7 @@ def check(self) -> HorizonHWMStore:
Self
"""
self._client.whoami()
self.client.whoami()
self._get_namespace_id()
return self

Expand All @@ -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.")

Expand All @@ -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
2 changes: 1 addition & 1 deletion requirements-docs.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
autodoc_pydantic<2
autodoc_pydantic
furo
numpydoc
sphinx
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -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
2 changes: 1 addition & 1 deletion tests/tests_unit/test_horizon_hwm_store_unit.py
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit 5d04b0f

Please sign in to comment.