Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

195 migrate to pydantic v2 #199

Merged
merged 50 commits into from
Nov 29, 2023
Merged
Show file tree
Hide file tree
Changes from 41 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
b01fe7c
chore: change AnyMqttUrl
djs0109 Jul 13, 2023
7a4a3fc
chore: pydantic migration based on scripts
djs0109 Jul 13, 2023
720888c
chore: migration of QueryString
djs0109 Jul 13, 2023
330c816
chore: added some migration to tests
tstorek Jul 14, 2023
ff9b17a
chore: fixed some more issues
tstorek Jul 14, 2023
2d87ca0
chore: fixed open TODO[pydantic]
tstorek Jul 17, 2023
6e127d7
chore: fixed some deprecations
tstorek Jul 17, 2023
c1a3235
fix: replace json() with model_dump_json()
djs0109 Jul 17, 2023
4b43c09
chore: migrate function calls in test_units
djs0109 Jul 18, 2023
cd9693d
fix: pydantic-V2 regex validation
djs0109 Jul 21, 2023
ec15855
fix: log level in env template must be uppercase
djs0109 Jul 24, 2023
878e4b6
fix: IOT_JSON should not exist in .env
djs0109 Jul 24, 2023
bc6edc2
chore: further migration
djs0109 Jul 24, 2023
54361fd
chore: rename filip.types
djs0109 Jul 24, 2023
78eec86
chore: update signature of post validator
djs0109 Jul 28, 2023
b43d3fb
chore: migration of TypeAdapter, Url, and some small things
djs0109 Jul 28, 2023
112d66e
chore: add test for model_dump_json
djs0109 Jul 28, 2023
a477820
chore: make validation check optional
djs0109 Jul 31, 2023
3c2c302
chore: rework mqtt client test
djs0109 Jul 31, 2023
3b77341
chore: migration changes for Url
djs0109 Jul 31, 2023
7236a42
chore: migration changes for Url
djs0109 Jul 31, 2023
9d98684
chore: small fix
djs0109 Jul 31, 2023
e00076d
fix: model serializer should only be set once in the lowest level
djs0109 Aug 28, 2023
d9b7544
chore: add test for QueryString/Statement serialization
djs0109 Aug 28, 2023
b29b549
chore: tests rework
djs0109 Aug 28, 2023
53d154e
fix: update gitlab workflow of pages
djs0109 Sep 1, 2023
c7e4c23
fix: settings model
djs0109 Sep 1, 2023
fc74a24
fix: test environments
djs0109 Sep 1, 2023
27e143c
chore: ignore extra env variables in test_config
djs0109 Sep 4, 2023
756a370
chore: replace gh pages template with gl
djs0109 Sep 4, 2023
5e5aca8
chore: overwrite PYTHON_PACKAGE_NAME in gitlab workflow
djs0109 Sep 4, 2023
fa0cae6
test CI
djs0109 Sep 5, 2023
181cee3
test CI_PAGES_URL
djs0109 Sep 5, 2023
85d8e98
test CI_PAGES_URL
djs0109 Sep 5, 2023
167c894
test CI_PAGES_URL
djs0109 Sep 5, 2023
59697ab
test CI
djs0109 Sep 28, 2023
9769e94
chore: update deprecated functions
djs0109 Sep 29, 2023
165dac1
Merge remote-tracking branch 'origin/195-Migrate-to-pydantic-v2' into…
djs0109 Sep 29, 2023
96a9796
chore: update change log
djs0109 Sep 29, 2023
1e8a1c9
chore: revision after review
djs0109 Oct 30, 2023
9f34c9b
chore: revision after review
djs0109 Oct 30, 2023
0b271de
chore: revision after review
djs0109 Oct 31, 2023
af583fb
chore: use new syntax for HTTP model
djs0109 Nov 16, 2023
70ab616
chore: move model_config to the top of each class
djs0109 Nov 16, 2023
6525921
fix: github pages
djs0109 Nov 24, 2023
13537d0
chore: add hints for pydantic v1 users
djs0109 Nov 27, 2023
da5bbd1
chore: change back to gitlab pages
djs0109 Nov 27, 2023
b881bf1
chore: use AnyMqttUrl in TestMQTTClient
djs0109 Nov 28, 2023
01d1848
chore: update the usage of network types
djs0109 Nov 29, 2023
b340730
chore: exclude python 3.12 in CI
djs0109 Nov 29, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,15 @@ variables:
EXCLUDE_PYTHON: 311
PYTHON_VERSION: "registry.git.rwth-aachen.de/ebc/ebc_all/gitlab_ci/templates:python_3.9"
PAGES_BRANCH: master
PYTHON_PACKAGE_NAME: "filip"

include:
- project: 'EBC/EBC_all/gitlab_ci/templates'
file: 'python/code-quality/pylint.gitlab-ci.yml'
- project: 'EBC/EBC_all/gitlab_ci/templates'
file: 'python/doc/sphinxdoc.gitlab-ci.yml'
- project: 'EBC/EBC_all/gitlab_ci/templates'
file: 'pages/pages.gitlab-ci.yml'
file: 'pages/gl-pages.gitlab-ci.yml'
tstorek marked this conversation as resolved.
Show resolved Hide resolved
- project: 'EBC/EBC_all/gitlab_ci/templates'
file: 'python/tests/tests.gitlab-ci.yml'
- project: 'EBC/EBC_all/gitlab_ci/templates'
Expand Down
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#### v0.2.5
#### v0.3.0
- fixed inconsistency of `entity_type` as required argument ([#188](https://github.com/RWTH-EBC/FiLiP/issues/188))
- BREAKING CHANGE: Migration of pydantic v1 to v2 ([#199](https://github.com/RWTH-EBC/FiLiP/issues/199))

#### v0.2.5
- fixed service group edition not working ([#170](https://github.com/RWTH-EBC/FiLiP/issues/170))
Expand Down
12 changes: 6 additions & 6 deletions filip/clients/base_http_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ def __init__(self,
def __enter__(self):
if not self.session:
self.session = requests.Session()
self.headers.update(self.fiware_headers.dict(by_alias=True))
self.headers.update(self.fiware_headers.model_dump(by_alias=True))
self._external_session = False
return self

Expand Down Expand Up @@ -94,12 +94,12 @@ def fiware_headers(self, headers: Union[Dict, FiwareHeader]) -> None:
if isinstance(headers, FiwareHeader):
self._fiware_headers = headers
elif isinstance(headers, dict):
self._fiware_headers = FiwareHeader.parse_obj(headers)
self._fiware_headers = FiwareHeader.model_validate(headers)
elif isinstance(headers, str):
self._fiware_headers = FiwareHeader.parse_raw(headers)
self._fiware_headers = FiwareHeader.model_validate_json(headers)
else:
raise TypeError(f'Invalid headers! {type(headers)}')
self.headers.update(self.fiware_headers.dict(by_alias=True))
self.headers.update(self.fiware_headers.model_dump(by_alias=True))

@property
def fiware_service(self) -> str:
Expand All @@ -121,7 +121,7 @@ def fiware_service(self, service: str) -> None:
None
"""
self._fiware_headers.service = service
self.headers.update(self.fiware_headers.dict(by_alias=True))
self.headers.update(self.fiware_headers.model_dump(by_alias=True))

@property
def fiware_service_path(self) -> str:
Expand All @@ -143,7 +143,7 @@ def fiware_service_path(self, service_path: str) -> None:
None
"""
self._fiware_headers.service_path = service_path
self.headers.update(self.fiware_headers.dict(by_alias=True))
self.headers.update(self.fiware_headers.model_dump(by_alias=True))

@property
def headers(self):
Expand Down
6 changes: 3 additions & 3 deletions filip/clients/mqtt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ def __validate_device(self, device: Union[Device, Dict]) -> Device:
AssertionError: for faulty configurations
"""
if isinstance(device, dict):
device = Device.parse_obj(device)
device = Device.model_validate(device)

assert isinstance(device, Device), "Invalid device configuration!"

Expand Down Expand Up @@ -444,7 +444,7 @@ def add_service_group(self, service_group: Union[ServiceGroup, Dict]):
ValueError: if service group already exists
"""
if isinstance(service_group, dict):
service_group = ServiceGroup.parse_obj(service_group)
service_group = ServiceGroup.model_validate(service_group)
assert isinstance(service_group, ServiceGroup), \
"Invalid content for service group!"

Expand Down Expand Up @@ -491,7 +491,7 @@ def update_service_group(self, service_group: Union[ServiceGroup, Dict]):
KeyError: if service group not yet registered
"""
if isinstance(service_group, dict):
service_group = ServiceGroup.parse_obj(service_group)
service_group = ServiceGroup.model_validate(service_group)
assert isinstance(service_group, ServiceGroup), \
"Invalid content for service group"

Expand Down
110 changes: 57 additions & 53 deletions filip/clients/ngsi_v2/cb.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
from math import inf
from pkg_resources import parse_version
from pydantic import \
parse_obj_as, \
PositiveInt, \
PositiveFloat, \
AnyHttpUrl
from typing import Any, Dict, List , Optional, TYPE_CHECKING, Union
from pydantic.type_adapter import TypeAdapter
from typing import Any, Dict, List, Optional, TYPE_CHECKING, Union
import re
import requests
from urllib.parse import urljoin
Expand Down Expand Up @@ -53,6 +53,7 @@ class ContextBrokerClient(BaseHttpClient):
We use the reference implementation for development. Therefore, some
other brokers may show slightly different behavior!
"""

def __init__(self,
url: str = None,
*,
Expand Down Expand Up @@ -232,9 +233,9 @@ def post_entity(self,
res = self.post(
url=url,
headers=headers,
json=entity.dict(exclude_unset=True,
exclude_defaults=True,
exclude_none=True))
json=entity.model_dump(exclude_unset=True,
exclude_defaults=True,
exclude_none=True))
if res.ok:
self.logger.info("Entity successfully posted!")
return res.headers.get('Location')
Expand Down Expand Up @@ -385,9 +386,11 @@ def get_entity_list(self,
params=params,
headers=headers)
if AttrsFormat.NORMALIZED in response_format:
return parse_obj_as(List[ContextEntity], items)
adapter = TypeAdapter(List[ContextEntity])
return adapter.validate_python(items)
if AttrsFormat.KEY_VALUES in response_format:
return parse_obj_as(List[ContextEntityKeyValues], items)
adapter = TypeAdapter(List[ContextEntityKeyValues])
return adapter.validate_python(items)
return items

except requests.RequestException as err:
Expand Down Expand Up @@ -615,7 +618,6 @@ def delete_entity(self,

iota_client_local.close()


def delete_entities(self, entities: List[ContextEntity]) -> None:
"""
Remove a list of entities from the context broker. This methode is
Expand All @@ -637,8 +639,8 @@ def delete_entities(self, entities: List[ContextEntity]) -> None:
# attributes.
entities_with_attributes: List[ContextEntity] = []
for entity in entities:
attribute_names = [key for key in entity.dict() if key not in
ContextEntity.__fields__]
attribute_names = [key for key in entity.model_dump() if key not in
ContextEntity.model_fields]
if len(attribute_names) > 0:
entities_with_attributes.append(
ContextEntity(id=entity.id, type=entity.type))
Expand Down Expand Up @@ -705,9 +707,9 @@ def update_or_append_entity_attributes(
try:
res = self.post(url=url,
headers=headers,
json=entity.dict(exclude=excluded_keys,
exclude_unset=True,
exclude_none=True),
json=entity.model_dump(exclude=excluded_keys,
exclude_unset=True,
exclude_none=True),
params=params)
if res.ok:
self.logger.info("Entity '%s' successfully "
Expand Down Expand Up @@ -753,9 +755,9 @@ def update_existing_entity_attributes(
try:
res = self.patch(url=url,
headers=headers,
json=entity.dict(exclude={'id', 'type'},
exclude_unset=True,
exclude_none=True),
json=entity.model_dump(exclude={'id', 'type'},
exclude_unset=True,
exclude_none=True),
params=params)
if res.ok:
self.logger.info("Entity '%s' successfully "
Expand Down Expand Up @@ -800,9 +802,9 @@ def replace_entity_attributes(
try:
res = self.put(url=url,
headers=headers,
json=entity.dict(exclude={'id', 'type'},
exclude_unset=True,
exclude_none=True),
json=entity.model_dump(exclude={'id', 'type'},
exclude_unset=True,
exclude_none=True),
params=params)
if res.ok:
self.logger.info("Entity '%s' successfully "
Expand All @@ -820,7 +822,7 @@ def get_attribute(self,
attr_name: str,
entity_type: str = None,
metadata: str = None,
response_format = '') -> ContextAttribute:
response_format='') -> ContextAttribute:
"""
Retrieves a specified attribute from an entity.

Expand Down Expand Up @@ -910,9 +912,9 @@ def update_entity_attribute(self,
res = self.put(url=url,
headers=headers,
params=params,
json=attr.dict(exclude={'name'},
exclude_unset=True,
exclude_none=True))
json=attr.model_dump(exclude={'name'},
exclude_unset=True,
exclude_none=True))
if res.ok:
self.logger.info("Attribute '%s' of '%s' "
"successfully updated!", attr_name, entity_id)
Expand Down Expand Up @@ -1127,7 +1129,8 @@ def get_subscription_list(self,
url=url,
params=params,
headers=headers)
return parse_obj_as(List[Subscription], items)
adapter = TypeAdapter(List[Subscription])
return adapter.validate_python(items)
except requests.RequestException as err:
msg = "Could not load subscriptions!"
self.log_error(err=err, msg=msg)
Expand Down Expand Up @@ -1162,9 +1165,9 @@ def post_subscription(self,
"""
existing_subscriptions = self.get_subscription_list()

sub_hash = subscription.json(include={'subject', 'notification'})
sub_hash = subscription.model_dump_json(include={'subject', 'notification'})
for ex_sub in existing_subscriptions:
if sub_hash == ex_sub.json(include={'subject', 'notification'}):
if sub_hash == ex_sub.model_dump_json(include={'subject', 'notification'}):
self.logger.info("Subscription already exists")
if update:
self.logger.info("Updated subscription")
Expand Down Expand Up @@ -1197,10 +1200,10 @@ def post_subscription(self,
res = self.post(
url=url,
headers=headers,
data=subscription.json(exclude={'id'},
exclude_unset=True,
exclude_defaults=True,
exclude_none=True),
data=subscription.model_dump_json(exclude={'id'},
exclude_unset=True,
exclude_defaults=True,
exclude_none=True),
params=params)
if res.ok:
self.logger.info("Subscription successfully created!")
Expand Down Expand Up @@ -1271,10 +1274,10 @@ def update_subscription(self,
res = self.patch(
url=url,
headers=headers,
data=subscription.json(exclude={'id'},
exclude_unset=True,
exclude_defaults=False,
exclude_none=True))
data=subscription.model_dump_json(exclude={'id'},
exclude_unset=True,
exclude_defaults=False,
exclude_none=True))
if res.ok:
self.logger.info("Subscription successfully updated!")
else:
Expand Down Expand Up @@ -1329,8 +1332,8 @@ def get_registration_list(self,
url=url,
params=params,
headers=headers)

return parse_obj_as(List[Registration], items)
adapter = TypeAdapter(List[Registration])
return adapter.validate_python(items)
except requests.RequestException as err:
msg = "Could not load registrations!"
self.log_error(err=err, msg=msg)
Expand All @@ -1355,10 +1358,10 @@ def post_registration(self, registration: Registration):
res = self.post(
url=url,
headers=headers,
data=registration.json(exclude={'id'},
exclude_unset=True,
exclude_defaults=True,
exclude_none=True))
data=registration.model_dump_json(exclude={'id'},
exclude_unset=True,
exclude_defaults=True,
exclude_none=True))
if res.ok:
self.logger.info("Registration successfully created!")
return res.headers['Location'].split('/')[-1]
Expand Down Expand Up @@ -1407,10 +1410,10 @@ def update_registration(self, registration: Registration):
res = self.patch(
url=url,
headers=headers,
data=registration.json(exclude={'id'},
exclude_unset=True,
exclude_defaults=True,
exclude_none=True))
data=registration.model_dump_json(exclude={'id'},
exclude_unset=True,
exclude_defaults=True,
exclude_none=True))
if res.ok:
self.logger.info("Registration successfully updated!")
else:
Expand Down Expand Up @@ -1496,7 +1499,7 @@ def update(self,
url=url,
headers=headers,
params=params,
data=update.json(by_alias=True))
json=update.model_dump(by_alias=True))
if res.ok:
self.logger.info("Update operation '%s' succeeded!",
action_type)
Expand Down Expand Up @@ -1541,13 +1544,15 @@ def query(self,
url=url,
headers=headers,
params=params,
data=query.json(exclude_unset=True,
exclude_none=True),
data=query.model_dump_json(exclude_unset=True,
exclude_none=True),
limit=limit)
if response_format == AttrsFormat.NORMALIZED:
return parse_obj_as(List[ContextEntity], items)
adapter = TypeAdapter(List[ContextEntity])
return adapter.validate_python(items)
if response_format == AttrsFormat.KEY_VALUES:
return parse_obj_as(List[ContextEntityKeyValues], items)
adapter = TypeAdapter(List[ContextEntityKeyValues])
return adapter.validate_python(items)
return items
except requests.RequestException as err:
msg = "Query operation failed!"
Expand Down Expand Up @@ -1579,14 +1584,14 @@ def notify(self, message: Message) -> None:
url=url,
headers=headers,
params=params,
data=message.json(by_alias=True))
data=message.model_dump_json(by_alias=True))
if res.ok:
self.logger.info("Notification message sent!")
else:
res.raise_for_status()
except requests.RequestException as err:
msg = f"Sending notifcation message failed! \n " \
f"{message.json(inent=2)}"
f"{message.model_dump_json(inent=2)}"
self.log_error(err=err, msg=msg)
raise

Expand All @@ -1613,7 +1618,7 @@ def post_command(self,
assert isinstance(command, (Command, dict))
if isinstance(command, dict):
command = Command(**command)
command = {command_name: command.dict()}
command = {command_name: command.model_dump()}
else:
assert isinstance(command, (NamedCommand, dict))
if isinstance(command, dict):
Expand Down Expand Up @@ -1654,7 +1659,6 @@ def does_entity_exist(self,
raise
return False


def patch_entity(self,
entity: ContextEntity,
old_entity: Optional[ContextEntity] = None,
Expand Down
6 changes: 4 additions & 2 deletions filip/clients/ngsi_v2/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,9 +101,11 @@ def config(self, config: HttpClientConfig):
if isinstance(config, HttpClientConfig):
self._config = config
elif isinstance(config, (str, Path)):
self._config = HttpClientConfig.parse_file(config)
with open(config) as f:
config_json = f.read()
self._config = HttpClientConfig.model_validate_json(config_json)
else:
self._config = HttpClientConfig.parse_obj(config)
self._config = HttpClientConfig.model_validate(config)

@property
def cert(self):
Expand Down
Loading