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

Update pydantic to version 2.4 #46

Merged
merged 12 commits into from
Jan 5, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
10 changes: 8 additions & 2 deletions .github/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
## Release 0.4.0 (development release)
## Release 0.3.2 (current release)

### Bug fixes

* Sets lower bound on compatible `pydantic` versions to v2.0.0. [(#46)](https://github.com/XanaduAI/xanadu-cloud-client/pull/46)

### Contributors

This release contains contributions from (in alphabetical order):

## Release 0.3.1 (current release)
[Mikhail Andrenkov](https://github.com/Mandrenkov), [Luke Helt](https://github.com/heltluke).

## Release 0.3.1

### Improvements

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/format.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: "3.8"

- uses: actions/checkout@v2

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
strategy:
matrix:
os: [macos-latest, ubuntu-latest]
python-version: [3.7, 3.8, 3.9]
python-version: ["3.8", "3.9", "3.10"]

steps:
- name: Cancel Previous Runs
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/upload.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v2
with:
python-version: 3.7
python-version: "3.8"

- name: Install dependencies
run: |
Expand Down
4 changes: 3 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
appdirs==1.4.4
fire==0.4.0
numpy==1.21.3
pydantic[dotenv]==1.8.2
pydantic==2.4
pydantic-settings==2.1.0
python-dateutil==2.8.2
python-dotenv==0.21.1
requests==2.31.0
5 changes: 3 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
"appdirs",
"fire",
"numpy",
"pydantic[dotenv]<2",
"pydantic>=2",
"pydantic-settings",
"python-dateutil",
"python-dotenv",
"requests",
]

Expand Down Expand Up @@ -41,7 +43,6 @@
"Operating System :: Microsoft :: Windows",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
Expand Down
4 changes: 2 additions & 2 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ def connection() -> xcc.Connection:
def settings(monkeypatch) -> Iterator[xcc.Settings]:
"""Returns a :class:`xcc.Settings` instance configured to use a mock .env file."""
with NamedTemporaryFile("w") as env_file:
monkeypatch.setattr("xcc.Settings.Config.env_file", env_file.name)
monkeypatch.setitem(xcc.Settings.model_config, "env_file", env_file.name)

settings_ = xcc.Settings(REFRESH_TOKEN="j.w.t", HOST="example.com", PORT=80, TLS=False)
# Saving ensures that new Settings instances are loaded with the same values.
settings_.save()

# Environment variables take precedence over fields in the .env file.
for env_var in map(xcc.settings.get_name_of_env_var, settings_.dict()):
for env_var in map(xcc.settings.get_name_of_env_var, settings_.model_dump()):
monkeypatch.delenv(env_var, raising=False)

yield settings_
5 changes: 4 additions & 1 deletion tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,10 @@ def test_invalid_name(self):

def test_invalid_value(self):
"""Tests that a ValueError is raised when the value of a setting is invalid."""
match = r"Failed to update PORT setting: value is not a valid integer"
match = (
r"Failed to update PORT setting: "
r"Input should be a valid integer, unable to parse string as an integer"
)
with pytest.raises(ValueError, match=match):
xcc.commands.set_setting(name="PORT", value="string")

Expand Down
8 changes: 4 additions & 4 deletions tests/test_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
def env_file(monkeypatch):
"""Returns a mock .env file which :class:`xcc.Settings` is configured to use."""
with NamedTemporaryFile("w") as env_file:
monkeypatch.setattr("xcc.settings.Settings.Config.env_file", env_file.name)
monkeypatch.setitem(xcc.Settings.model_config, "env_file", env_file.name)
yield env_file


Expand Down Expand Up @@ -77,7 +77,7 @@ def test_save_bad_base64_url(self, settings):
settings.save()

# Check that the .env file was not modified since there was a "\n" in the refresh token.
assert dotenv_values(xcc.Settings.Config.env_file) == {
assert dotenv_values(settings.model_config["env_file"]) == {
"XANADU_CLOUD_REFRESH_TOKEN": "j.w.t",
"XANADU_CLOUD_HOST": "example.com",
"XANADU_CLOUD_PORT": "80",
Expand All @@ -86,7 +86,7 @@ def test_save_bad_base64_url(self, settings):

def test_save_multiple_times(self, settings):
"""Tests that settings can be saved to a .env file multiple times."""
path_to_env_file = xcc.Settings.Config.env_file
path_to_env_file = settings.model_config["env_file"]

settings.REFRESH_TOKEN = None
settings.save()
Expand All @@ -108,7 +108,7 @@ def test_save_to_nonexistent_directory(self, monkeypatch):
"""Tests that settings can be saved to a .env file in a nonexistent directory."""
with TemporaryDirectory() as env_dir:
env_file = os.path.join(env_dir, "foo", "bar", ".env")
monkeypatch.setattr("xcc.settings.Settings.Config.env_file", env_file)
monkeypatch.setitem(xcc.Settings.model_config, "env_file", env_file)

xcc.Settings().save()
assert os.path.exists(env_file) is True
2 changes: 1 addition & 1 deletion xcc/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,4 @@
See https://semver.org/.
"""

__version__ = "0.4.0-dev"
__version__ = "0.3.2"
14 changes: 7 additions & 7 deletions xcc/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ def list_settings() -> Mapping[str, Any]:
Returns:
Mapping[str, Any]: Mapping from setting names to values.
"""
return Settings().dict()
return Settings().model_dump()


@beautify
Expand All @@ -108,14 +108,14 @@ def set_setting(name: str, value: Union[str, int, bool]) -> str:
key, _ = _resolve_setting(name)

try:
settings = Settings(**{key: value})
settings = Settings.model_validate({key: value})
settings.save()
except ValidationError as exc:
err = exc.errors()[0].get("msg", "invalid value")
raise ValueError(f"Failed to update {key} setting: {err}") from exc

# Using repr() ensures that strings are quoted.
val = repr(settings.dict()[key])
val = repr(settings.model_dump()[key])
return f"Successfully updated {key} setting to {val}."


Expand All @@ -133,11 +133,11 @@ def _resolve_setting(name: str) -> Tuple[str, Any]:
"""
key = name.upper()

settings = Settings()
if key not in settings.dict():
raise ValueError(f"The setting name '{name}' must be one of {list(settings.dict())}.")
settings_dict = Settings().model_dump()
if key not in settings_dict:
raise ValueError(f"The setting name '{name}' must be one of {list(settings_dict)}.")

return key, settings.dict()[key]
return key, settings_dict[key]


# Device CLI
Expand Down
25 changes: 13 additions & 12 deletions xcc/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@

from appdirs import user_config_dir
from dotenv import dotenv_values, set_key, unset_key
from pydantic import BaseSettings
from pydantic_settings import BaseSettings, SettingsConfigDict

# Matches when string contains chars outside Base64URL set
# https://base64.guru/standards/base64url
Expand Down Expand Up @@ -69,7 +69,7 @@ class Settings(BaseSettings):
>>> import xcc
>>> settings = xcc.Settings()
>>> settings
REFRESH_TOKEN=None ACCESS_TOKEN=None HOST='platform.xanadu.ai' PORT=443 TLS=True
Settings(REFRESH_TOKEN=None, ACCESS_TOKEN=None, HOST'platform.xanadu.ai', PORT=443, TLS=True)

Now, individual options can be accessed or assigned through their
corresponding attribute:
Expand All @@ -84,9 +84,9 @@ class Settings(BaseSettings):

Several aggregate representations of options are also available, such as

>>> settings.dict()
>>> settings.model_dump()
{'REFRESH_TOKEN': None, 'ACCESS_TOKEN': None, ..., 'TLS': True}
>>> settings.json()
>>> settings.model_dump_json()
'{"REFRESH_TOKEN": null, "ACCESS_TOKEN": null, ..., "TLS": true}'

Finally, saving a configuration can be done by invoking :meth:`Settings.save`:
Expand All @@ -109,25 +109,26 @@ class Settings(BaseSettings):
TLS: bool = True
"""Whether to use HTTPS for requests to the Xanadu Cloud."""

class Config: # pylint: disable=missing-class-docstring
case_sensitive = True
env_file = get_path_to_env_file()
env_prefix = get_name_of_env_var()
model_config = SettingsConfigDict(
case_sensitive=True,
env_file=get_path_to_env_file(),
env_prefix=get_name_of_env_var(),
)

def save(self) -> None:
"""Saves the current settings to the .env file."""

env_file = Settings.Config.env_file
env_file = self.model_config["env_file"]
env_dir = os.path.dirname(env_file)
os.makedirs(env_dir, exist_ok=True)

saved = dotenv_values(dotenv_path=env_file)

# must be done first as dict is not ordered
for key, val in self.dict().items():
# Must be done first as the dictionary is not ordered.
for key, val in self.model_dump().items():
_check_for_invalid_values(key, val)

for key, val in self.dict().items():
for key, val in self.model_dump().items():
field = get_name_of_env_var(key)

# Remove keys that are assigned to None.
Expand Down
Loading