Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
3 changes: 2 additions & 1 deletion .coveragerc
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
# This file contains the configuration settings for the coverage report generated.

[report]
# exclude '...' (ellipsis literal). This option uses regex, so an escaped literal is required.
exclude_also =
\.\.\.
exclude_lines =
if TYPE_CHECKING:

fail_under = 80
40 changes: 40 additions & 0 deletions integration/tests/posit/connect/test_environments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import pytest
from packaging import version

from posit import connect

from . import CONNECT_VERSION


@pytest.mark.skipif(
CONNECT_VERSION < version.parse("2023.05.0"),
reason="Environments API unavailable",
)
class TestEnvironments:
@classmethod
def setup_class(cls):
cls.client = connect.Client()
cls.environment = cls.client.environments.create(
title="title",
name="name",
cluster_name="Kubernetes",
)

@classmethod
def teardown_class(cls):
cls.environment.destroy()
assert len(cls.client.environments) == 0

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):
assert self.environment["title"] == "title"
self.environment.update(title="new-title")
assert self.environment["title"] == "new-title"
4 changes: 4 additions & 0 deletions integration/tests/posit/connect/test_jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,3 +51,7 @@ def test_find_by(self):

jobs = content.jobs
assert len(jobs) != 0

job = jobs[0]
key = job["key"]
assert content.jobs.find_by(key=key) == job
15 changes: 11 additions & 4 deletions src/posit/connect/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

from __future__ import annotations

from typing import overload
from typing import TYPE_CHECKING, overload

from requests import Response, Session

from posit.connect.tags import Tags

from . import hooks, me
from .auth import Auth
from .config import Config
Expand All @@ -17,11 +15,15 @@
from .metrics import Metrics
from .oauth import OAuth
from .packages import Packages
from .resources import ResourceParameters
from .resources import ResourceParameters, _ResourceSequence
from .tags import Tags
from .tasks import Tasks
from .users import User, Users
from .vanities import Vanities

if TYPE_CHECKING:
from .environments import Environments


class Client(ContextManager):
"""
Expand Down Expand Up @@ -303,6 +305,11 @@ def packages(self) -> Packages:
def vanities(self) -> Vanities:
return Vanities(self.resource_params)

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

def __del__(self):
"""Close the session when the Client instance is deleted."""
if hasattr(self, "session") and self.session is not None:
Expand Down
11 changes: 8 additions & 3 deletions src/posit/connect/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,17 @@
from .bundles import Bundles
from .env import EnvVars
from .errors import ClientError
from .jobs import JobsMixin
from .oauth.associations import ContentItemAssociations
from .packages import ContentPackagesMixin as PackagesMixin
from .permissions import Permissions
from .resources import Resource, ResourceParameters, Resources
from .resources import Resource, ResourceParameters, Resources, _ResourceSequence
from .tags import ContentItemTags
from .vanities import VanityMixin
from .variants import Variants

if TYPE_CHECKING:
from .context import Context
from .jobs import Jobs
from .tasks import Task


Expand Down Expand Up @@ -174,7 +174,7 @@ class ContentItemOwner(Resource):
pass


class ContentItem(JobsMixin, PackagesMixin, VanityMixin, Resource):
class ContentItem(PackagesMixin, VanityMixin, Resource):
class _AttrsBase(TypedDict, total=False):
# # `name` will be set by other _Attrs classes
# name: str
Expand Down Expand Up @@ -511,6 +511,11 @@ def tags(self) -> ContentItemTags:
content_guid=self["guid"],
)

@property
def jobs(self) -> Jobs:
path = posixpath.join(self._path, "jobs")
return _ResourceSequence(self._ctx, path, uid="key")


class Content(Resources):
"""Content resource.
Expand Down
219 changes: 219 additions & 0 deletions src/posit/connect/environments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
from __future__ import annotations

from abc import abstractmethod
from collections.abc import Mapping, Sized
from typing import (
Any,
List,
Literal,
Protocol,
SupportsIndex,
TypedDict,
overload,
runtime_checkable,
)

MatchingType = Literal["any", "exact", "none"]
"""Directions for how environments are considered for selection.

- any: The image may be selected by Connect if not defined in the bundle manifest.
- exact: The image must be defined in the bundle manifest
- none: Never use this environment
"""


class Installation(TypedDict):
"""Interpreter installation in an execution environment."""

path: str
"""The absolute path to the interpreter's executable."""

version: str
"""The semantic version of the interpreter."""


class Installations(TypedDict):
"""Interpreter installations in an execution environment."""

installations: List[Installation]
"""Interpreter installations in an execution environment."""


class Environment(Mapping[str, Any]):
@abstractmethod
def destroy(self) -> None:
"""Destroy the environment.

Warnings
--------
This operation is irreversible.

Note
----
This action requires administrator privileges.
"""

@abstractmethod
def update(
self,
*,
title: str,
description: str | None = ...,
matching: MatchingType | None = ...,
supervisor: str | None = ...,
python: Installations | None = ...,
quarto: Installations | None = ...,
r: Installations | None = ...,
tensorflow: Installations | None = ...,
) -> None:
"""Update the environment.

Parameters
----------
title : str
A human-readable title.
description : str | None, optional, not required
A human-readable description.
matching : MatchingType, optional, not required
Directions for how the environment is considered for selection
supervisor : str | None, optional, not required
Path to the supervisor script.
python : Installations, optional, not required
The Python installations available in this environment
quarto : Installations, optional, not required
The Quarto installations available in this environment
r : Installations, optional, not required
The R installations available in this environment
tensorflow : Installations, optional, not required
The Tensorflow installations available in this environment

Note
----
This action requires administrator privileges.
"""


@runtime_checkable
class Environments(Sized, Protocol):
@overload
def __getitem__(self, index: SupportsIndex) -> Environment: ...

@overload
def __getitem__(self, index: slice) -> List[Environment]: ...

def create(
self,
*,
title: str,
name: str,
cluster_name: str | Literal["Kubernetes"],
matching: MatchingType = "any",
description: str | None = ...,
supervisor: str | None = ...,
python: Installations | None = ...,
quarto: Installations | None = ...,
r: Installations | None = ...,
tensorflow: Installations | None = ...,
) -> Environment:
"""Create an environment.

Parameters
----------
title : str
A human-readable title.
name : str
The container image name used for execution in this environment.
cluster_name : str | Literal["Kubernetes"]
The cluster identifier for this environment. Defaults to "Kubernetes" when Off-Host-Execution is enabled.
description : str, optional
A human-readable description.
matching : MatchingType
Directions for how the environment is considered for selection, by default is "any".
supervisor : str, optional
Path to the supervisor script
python : Installations, optional
The Python installations available in this environment
quarto : Installations, optional
The Quarto installations available in this environment
r : Installations, optional
The R installations available in this environment
tensorflow : Installations, optional
The Tensorflow installations available in this environment

Returns
-------
Environment

Note
----
This action requires administrator privileges.
"""
...

def find(self, guid: str, /) -> Environment: ...

def find_by(
self,
*,
id: str = ..., # noqa: A002
guid: str = ...,
created_time: str = ...,
updated_time: str = ...,
title: str = ...,
name: str = ...,
description: str | None = ...,
cluster_name: str | Literal["Kubernetes"] = ...,
environment_type: str | Literal["Kubernetes"] = ...,
matching: MatchingType = ...,
supervisor: str | None = ...,
python: Installations | None = ...,
quarto: Installations | None = ...,
r: Installations | None = ...,
tensorflow: Installations | None = ...,
) -> Environment | None:
"""Find the first record matching the specified conditions.

There is no implied ordering, so if order matters, you should specify it yourself.

Parameters
----------
id : str
The numerical identifier.
guid : str
The unique identifier.
created_time : str
The timestamp (RFC3339) when the environment was created.
updated_time : str
The timestamp (RFC3339) when the environment was updated.
title : str
A human-readable title.
name : str
The container image name used for execution in this environment.
description : str, optional
A human-readable description.
cluster_name : str | Literal["Kubernetes"]
The cluster identifier for this environment. Defaults to "Kubernetes" when Off-Host-Execution is enabled.
environment_type : str | Literal["Kubernetes"]
The cluster environment type. Defaults to "Kubernetes" when Off-Host-Execution is enabled.
matching : MatchingType
Directions for how the environment is considered for selection.
supervisor : str, optional
Path to the supervisor script
python : Installations, optional
The Python installations available in this environment
quarto : Installations, optional
The Quarto installations available in this environment
r : Installations, optional
The R installations available in this environment
tensorflow : Installations, optional
The Tensorflow installations available in this environment

Returns
-------
Environment | None

Note
----
This action requires administrator or publisher privileges.
"""
...
Loading
Loading