-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add version check support (#299)
Adds a new decorator, `@context.requires`, which asserts version compatibility when the server version is known. The check is skipped if the server version is unknown (e.g., the Connect configuration disables version information). Also marks the OAuth API with a '2024.08.0' requirement. Closes #272
- Loading branch information
Showing
12 changed files
with
185 additions
and
15 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
requests==2.32.2 | ||
packaging==24.1 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
import functools | ||
from typing import Optional, Protocol | ||
|
||
from packaging.version import Version | ||
|
||
|
||
def requires(version: str): | ||
def decorator(func): | ||
@functools.wraps(func) | ||
def wrapper(instance: ContextManager, *args, **kwargs): | ||
ctx = instance.ctx | ||
if ctx.version and Version(ctx.version) < Version(version): | ||
raise RuntimeError( | ||
f"This API is not available in Connect version {ctx.version}. Please upgrade to version {version} or later.", | ||
) | ||
return func(instance, *args, **kwargs) | ||
|
||
return wrapper | ||
|
||
return decorator | ||
|
||
|
||
class Context(dict): | ||
def __init__(self, session, url): | ||
self.session = session | ||
self.url = url | ||
|
||
@property | ||
def version(self) -> Optional[str]: | ||
try: | ||
value = self["version"] | ||
except KeyError: | ||
endpoint = self.url + "server_settings" | ||
response = self.session.get(endpoint) | ||
result = response.json() | ||
value = self["version"] = result.get("version") | ||
return value | ||
|
||
@version.setter | ||
def version(self, value: str): | ||
self["version"] = value | ||
|
||
|
||
class ContextManager(Protocol): | ||
ctx: Context |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
from email.contentmanager import ContentManager | ||
from unittest.mock import MagicMock, Mock | ||
|
||
import pytest | ||
import requests | ||
import responses | ||
|
||
from posit.connect.context import Context, requires | ||
from posit.connect.urls import Url | ||
|
||
|
||
class TestRequires: | ||
def test_version_unsupported(self): | ||
class Stub(ContentManager): | ||
def __init__(self, ctx): | ||
self.ctx = ctx | ||
|
||
@requires("1.0.0") | ||
def fail(self): | ||
pass | ||
|
||
ctx = MagicMock() | ||
ctx.version = "0.0.0" | ||
instance = Stub(ctx) | ||
|
||
with pytest.raises(RuntimeError): | ||
instance.fail() | ||
|
||
def test_version_supported(self): | ||
class Stub(ContentManager): | ||
def __init__(self, ctx): | ||
self.ctx = ctx | ||
|
||
@requires("1.0.0") | ||
def success(self): | ||
pass | ||
|
||
ctx = MagicMock() | ||
ctx.version = "1.0.0" | ||
instance = Stub(ctx) | ||
|
||
instance.success() | ||
|
||
def test_version_missing(self): | ||
class Stub(ContentManager): | ||
def __init__(self, ctx): | ||
self.ctx = ctx | ||
|
||
@requires("1.0.0") | ||
def success(self): | ||
pass | ||
|
||
ctx = MagicMock() | ||
ctx.version = None | ||
instance = Stub(ctx) | ||
|
||
instance.success() | ||
|
||
|
||
class TestContextVersion: | ||
@responses.activate | ||
def test_unknown(self): | ||
responses.get( | ||
f"http://connect.example/__api__/server_settings", | ||
json={}, | ||
) | ||
|
||
session = requests.Session() | ||
url = Url("http://connect.example") | ||
ctx = Context(session, url) | ||
|
||
assert ctx.version is None | ||
|
||
@responses.activate | ||
def test_known(self): | ||
responses.get( | ||
f"http://connect.example/__api__/server_settings", | ||
json={"version": "2024.09.24"}, | ||
) | ||
|
||
session = requests.Session() | ||
url = Url("http://connect.example") | ||
ctx = Context(session, url) | ||
|
||
assert ctx.version == "2024.09.24" | ||
|
||
def test_setter(self): | ||
ctx = Context(Mock(), Mock()) | ||
ctx.version = "2024.09.24" | ||
assert ctx.version == "2024.09.24" |