Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0533f19
feat: add jobs
tdstein Oct 18, 2024
6b79912
--wip-- [skip ci]
tdstein Oct 22, 2024
279fcd6
refactor: introduce the active pattern
tdstein Oct 23, 2024
e349870
add link to parent
tdstein Oct 23, 2024
533839b
skip when Quarto unavailable
tdstein Oct 23, 2024
1066ca3
adds unit tests
tdstein Oct 23, 2024
437c515
adds docstrings
tdstein Oct 23, 2024
a1ca377
Update src/posit/connect/resources.py
tdstein Oct 24, 2024
82b9b7e
applies feedback discussed in pull requests
tdstein Oct 24, 2024
6b8126d
refactor: inject url path parts instead of endpoints
tdstein Oct 25, 2024
b64f3e7
update docstrings
tdstein Oct 28, 2024
f57340d
renames init arguments to path and pathinfo
tdstein Oct 28, 2024
72b62ac
minor cleanup
tdstein Oct 28, 2024
f1d6f42
refactors _data property into _get_or_fetch method
tdstein Oct 29, 2024
dd74d60
fix method signature
tdstein Oct 29, 2024
fb52c83
fix cache check
tdstein Oct 29, 2024
bbbd6b4
Update src/posit/connect/resources.py
tdstein Oct 29, 2024
bc2cfcb
feat: add jobs
tdstein Oct 18, 2024
9c3d6dd
--wip-- [skip ci]
tdstein Oct 22, 2024
9019386
refactor: introduce the active pattern
tdstein Oct 23, 2024
4bfe3f8
add link to parent
tdstein Oct 23, 2024
a721b61
skip when Quarto unavailable
tdstein Oct 23, 2024
107ee85
adds unit tests
tdstein Oct 23, 2024
a070f0a
adds docstrings
tdstein Oct 23, 2024
d196271
Update src/posit/connect/resources.py
tdstein Oct 24, 2024
d87cfe7
applies feedback discussed in pull requests
tdstein Oct 24, 2024
2add280
Merge remote-tracking branch 'origin/tdstein/jobs' into tdstein/jobs-…
tdstein Oct 29, 2024
63e38d9
--wip-- [skip ci]
tdstein Oct 29, 2024
db14d38
--wip--
tdstein Oct 29, 2024
e81068f
--wip-- [skip ci]
tdstein Oct 29, 2024
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
36 changes: 36 additions & 0 deletions integration/tests/posit/connect/test_environments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import pytest
from packaging import version

from posit import connect

from . import CONNECT_VERSION


@pytest.mark.skipif(
CONNECT_VERSION <= version.parse("2023.01.1"),
reason="Environments API not available",
)
class TestContent:
@classmethod
def setup_class(cls):
cls.client = connect.Client()
cls.environment = cls.client.environments.create(
title="Title", cluster_name="Kubernetes", name="Name"
)

@classmethod
def teardown_class(cls):
cls.environment.destroy()

def test_find(self):
uid = self.environment["guid"]
environment = self.client.environments.find(uid)
assert environment == self.environment

def test_find_by(self):
environment = self.client.environments.find_by(name="Name")
assert environment == self.environment

def test_update(self):
self.environment.update(title="New Title")
assert self.environment["title"] == "New Title"
38 changes: 38 additions & 0 deletions integration/tests/posit/connect/test_jobs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pathlib import Path

import pytest
from packaging import version

from posit import connect

from . import CONNECT_VERSION


class TestJobs:
@classmethod
def setup_class(cls):
cls.client = connect.Client()
cls.content = cls.client.content.create(name="example-quarto-minimal")

@classmethod
def teardown_class(cls):
cls.content.delete()
assert cls.client.content.count() == 0

@pytest.mark.skipif(
CONNECT_VERSION <= version.parse("2023.01.1"),
reason="Quarto not available",
)
def test(self):
content = self.content

path = Path("../../../resources/connect/bundles/example-quarto-minimal/bundle.tar.gz")
path = Path(__file__).parent / path
path = path.resolve()
path = str(path)

bundle = content.bundles.create(path)
bundle.deploy()

jobs = content.jobs
assert len(jobs) == 1
7 changes: 7 additions & 0 deletions src/posit/connect/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

from requests import Response, Session

from posit.connect.environments import Environments

from . import hooks, me
from .auth import Auth
from .config import Config
Expand Down Expand Up @@ -184,6 +186,11 @@ def me(self) -> User:
"""
return me.get(self.resource_params)

@property
@requires(version="2023.05.0")
def environments(self) -> Environments:
return Environments(self.ctx, "v1")

@property
def groups(self) -> Groups:
"""The groups resource interface.
Expand Down
10 changes: 9 additions & 1 deletion src/posit/connect/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@

from . import tasks
from .bundles import Bundles
from .context import Context
from .env import EnvVars
from .jobs import JobsMixin
from .oauth.associations import ContentItemAssociations
from .permissions import Permissions
from .resources import Resource, ResourceParameters, Resources
Expand All @@ -32,7 +34,13 @@ class ContentItemOwner(Resource):
pass


class ContentItem(VanityMixin, Resource):
class ContentItem(JobsMixin, VanityMixin, Resource):
def __init__(self, /, params: ResourceParameters, **kwargs):
ctx = Context(params.session, params.url)
path = f"v1/content"
pathinfo = kwargs["guid"]
super().__init__(ctx, path, pathinfo, **kwargs)

def __getitem__(self, key: Any) -> Any:
v = super().__getitem__(key)
if key == "owner" and isinstance(v, dict):
Expand Down
6 changes: 4 additions & 2 deletions src/posit/connect/context.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import requests
from packaging.version import Version

from .urls import Url


def requires(version: str):
def decorator(func):
Expand All @@ -22,7 +24,7 @@ def wrapper(instance: ContextManager, *args, **kwargs):


class Context(dict):
def __init__(self, session: requests.Session, url: str):
def __init__(self, session: requests.Session, url: Url):
self.session = session
self.url = url

Expand All @@ -38,7 +40,7 @@ def version(self) -> Optional[str]:
return value

@version.setter
def version(self, value: str):
def version(self, value):
self["version"] = value


Expand Down
86 changes: 86 additions & 0 deletions src/posit/connect/environments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
from typing import Dict, Literal, Optional, TypedDict, overload

from typing_extensions import NotRequired, Required, Unpack

from .resources import (
ActiveCreatorMethods,
ActiveDestroyerMethods,
ActiveFinderMethods,
ActiveUpdaterMethods,
)

_Kubernetes = Literal["Kubernetes"]
_Matching = Literal["any", "exact", "none"]
_Installation = TypedDict("_Installation", { 'path': Required[str], 'version': Required[str] })
_Installations = Dict[Literal["installations"], _Installation]


class Environment(ActiveUpdaterMethods, ActiveDestroyerMethods):
class _UpdateEnvironment(TypedDict, total=False):
title: Required[str]
description: NotRequired[Optional[str]]
matching: NotRequired[Optional[_Matching]]
supervisor: NotRequired[Optional[str]]
python: NotRequired[_Installations]
quarto: NotRequired[_Installations]
r: NotRequired[_Installations]
tensorflow: NotRequired[_Installations]

@overload
def update(self, /, **attributes: Unpack[_UpdateEnvironment]): ...

@overload
def update(self, /, **attributes): ...

def update(self, /, **attributes):
return super().update(**attributes)


class Environments(ActiveFinderMethods[Environment], ActiveCreatorMethods[Environment]):
def __init__(self, ctx, path, pathinfo="environments", uid="guid"):
super().__init__(ctx, path, pathinfo, uid)

class _CreateEnvironment(TypedDict):
title: Required[str]
description: NotRequired[Optional[str]]
cluster_name: Required[_Kubernetes]
name: Required[str]
matching: NotRequired[Optional[_Matching]]
supervisor: NotRequired[Optional[str]]
python: NotRequired[_Installations]
quarto: NotRequired[_Installations]
r: NotRequired[_Installations]
tensorflow: NotRequired[_Installations]

@overload
def create(self, **attributes: Unpack[_CreateEnvironment]) -> Environment: ...

@overload
def create(self, **attributes) -> Environment: ...

def create(self, **attributes) -> Environment:
return super().create(**attributes)

class _FindByEnvironment(TypedDict):
title: NotRequired[str]
description: NotRequired[Optional[str]]
cluster_name: NotRequired[_Kubernetes]
name: NotRequired[str]
matching: NotRequired[Optional[_Matching]]
supervisor: NotRequired[Optional[str]]
python: NotRequired[_Installations]
quarto: NotRequired[_Installations]
r: NotRequired[_Installations]
tensorflow: NotRequired[_Installations]

@overload
def find_by(self, **conditions: Unpack[_FindByEnvironment]): ...

@overload
def find_by(self, **conditions): ...

def find_by(self, **conditions):
return super().find_by(**conditions)

def _create_instance(self, path, pathinfo, /, **attributes):
return Environment(self._ctx, path, pathinfo, **attributes)
Loading