-
Notifications
You must be signed in to change notification settings - Fork 4
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
feat: add version check support #299
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
requests==2.32.2 | ||
packaging==24.1 |
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -73,6 +73,7 @@ def test(self): | |
|
||
# setup | ||
c = Client("https://connect.example", "12345") | ||
c.ctx.version = None | ||
integration = c.oauth.integrations.get(guid) | ||
|
||
# invoke | ||
|
@@ -93,6 +94,7 @@ def test(self): | |
) | ||
|
||
c = Client("https://connect.example", "12345") | ||
c.ctx.version = None | ||
integration = c.oauth.integrations.get(guid) | ||
assert integration.guid == guid | ||
|
||
|
@@ -137,6 +139,7 @@ def test(self): | |
|
||
# setup | ||
c = Client("https://connect.example", "12345") | ||
c.ctx.version = None | ||
|
||
# invoke | ||
integration = c.oauth.integrations.create( | ||
|
@@ -164,10 +167,11 @@ def test(self): | |
) | ||
|
||
# setup | ||
client = Client("https://connect.example", "12345") | ||
c = Client("https://connect.example", "12345") | ||
c.ctx.version = None | ||
Comment on lines
-167
to
+171
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any reason for the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just making the references the same across the file. |
||
|
||
# invoke | ||
integrations = client.oauth.integrations.find() | ||
integrations = c.oauth.integrations.find() | ||
|
||
# assert | ||
assert mock_get.call_count == 1 | ||
|
@@ -189,6 +193,7 @@ def test(self): | |
|
||
# setup | ||
c = Client("https://connect.example", "12345") | ||
c.ctx.version = None | ||
integration = c.oauth.integrations.get(guid) | ||
|
||
assert mock_get.call_count == 1 | ||
|
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() | ||
Comment on lines
+44
to
+57
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test is for behavior we use while testing, but not one we expect users to hit, yeah? If yeah, it would be good to comment about that just so it's clear this isn't a desired end-user behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. No, this is designed behavior. If the version is hidden in the server settings, then |
||
|
||
|
||
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" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are setting the version to
None
to avoid mocking the API request to /server_settings.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we put this in a helper function of some sort? Something like
test_client(...)
that does this internally. There are other ways too, but this is a lot of the same thing over and over (and a bit of specialized knowledge that you basically always have to do this in these tests