Skip to content

Commit

Permalink
Improve docs, make "org" param optional (#63)
Browse files Browse the repository at this point in the history
* Improve docs, make "org" param optional

* fix

* fix

* add more doctests

* add more links

* fix

* fix again
  • Loading branch information
epwalsh authored Apr 12, 2022
1 parent 5cb08b2 commit 375f3e8
Show file tree
Hide file tree
Showing 18 changed files with 250 additions and 103 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,15 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Changed

- Made `org` parameter optional, defaulting to `Config.default_org`.

### Fixed

- Fixed the behavior of some methods that take a `workspace` parameter. Previously, if the workspace
didn't exist, it would be silently created. Now a `WorkspaceNotFound` error is raised.

## [v0.6.1](https://github.com/allenai/beaker-py/releases/tag/v0.6.1) - 2022-04-11

### Added
Expand Down
10 changes: 8 additions & 2 deletions beaker/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,25 @@
Manage Beaker organizations with :data:`Beaker.organization`.
For example, you can get information about an organization with
:meth:`Beaker.organization.get() <services.OrganizationClient>`:
:meth:`Beaker.organization.get() <services.OrganizationClient.get>`:
>>> beaker.organization.get(beaker_org_name).display_name
'AI2'
You can also add, get, list, or remove members with
:meth:`Beaker.organization.add_member() <services.OrganizationClient.add_member>`,
:meth:`.get_member() <services.OrganizationClient.get_member>`,
:meth:`.list_members() <services.OrganizationClient.list_members>`, or
:meth:`.remove_member() <services.OrganizationClient.remove_member>`, respectively.
Workspaces
----------
Manage Beaker workspaces with :data:`Beaker.workspace`.
For example, you can create a workspace with :meth:`Beaker.workspace.ensure() <services.WorkspaceClient.ensure>`:
>>> beaker.workspace.ensure(workspace_name)
>>> workspace = beaker.workspace.ensure(workspace_name)
You can retreive metadata about a workspace with :meth:`Beaker.workspace.get() <services.WorkspaceClient.get>`:
Expand Down
72 changes: 71 additions & 1 deletion beaker/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,19 @@ class Beaker:
:param config: The Beaker :class:`Config`.
The easiest way to initialize a Beaker client is with :meth:`.from_env()`.
The easiest way to initialize a Beaker client is with :meth:`.from_env()`:
>>> beaker = Beaker.from_env()
You can then interact with the various Beaker services through the corresponding
property. For example, to manage workspaces, use :data:`Beaker.workspace`:
>>> beaker.workspace.get(workspace_name).full_name
'ai2/petew-testing'
.. tip::
Use the right side nav to browse through the API docs for all of the different services.
"""

def __init__(self, config: Config, check_for_upgrades: bool = True):
Expand Down Expand Up @@ -96,6 +108,11 @@ def account(self) -> AccountClient:
"""
Manage accounts.
:examples:
>>> beaker.account.name
'petew'
.. tip::
See the `Accounts Overview <overview.html#accounts>`_ for a walk-through of the
main methods, or check out the `Account API Docs <#account>`_
Expand All @@ -108,6 +125,11 @@ def organization(self) -> OrganizationClient:
"""
Manage organizations.
:examples:
>>> beaker.organization.get("ai2").display_name
'AI2'
.. tip::
See the `Organizations Overview <overview.html#organizations>`_ for a walk-through of the
main methods, or check out the `Organization API Docs <#organization>`_
Expand All @@ -120,6 +142,15 @@ def workspace(self) -> WorkspaceClient:
"""
Manage workspaces.
:examples:
>>> beaker.workspace.datasets(
... match="squad",
... uncommitted=False,
... results=False,
... )[0].full_name
'petew/squad-train'
.. tip::
See the `Workspaces Overview <overview.html#workspaces>`_ for a walk-through of the
main methods, or check out the `Workspace API Docs <#workspace>`_
Expand All @@ -132,6 +163,11 @@ def cluster(self) -> ClusterClient:
"""
Manage clusters.
:examples:
>>> beaker.cluster.get(beaker_cluster_name).autoscale
True
.. tip::
See the `Clusters Overview <overview.html#clusters>`_ for a walk-through of the
main methods, or check out the `Cluster API Docs <#cluster>`_
Expand All @@ -144,6 +180,11 @@ def node(self) -> NodeClient:
"""
Manage nodes.
:examples:
>>> beaker.node.get(beaker_node_id).limits.gpu_count
8
.. tip::
See the `Nodes Overview <overview.html#nodes>`_ for a walk-through of the
main methods, or check out the `Node API Docs <#node>`_
Expand All @@ -156,6 +197,11 @@ def dataset(self) -> DatasetClient:
"""
Manage datasets.
:examples:
>>> [file_info.path for file_info in beaker.dataset.ls("petew/squad-train")]
['squad-train.arrow']
.. tip::
See the `Datasets Overview <overview.html#datasets>`_ for a walk-through of the
main methods, or check out the `Dataset API Docs <#dataset>`_
Expand All @@ -168,6 +214,11 @@ def image(self) -> ImageClient:
"""
Manage images.
:examples:
>>> beaker.image.get("petew/hello-world").original_tag
'hello-world'
.. tip::
See the `Images Overview <overview.html#images>`_ for a walk-through of the
main methods, or check out the `Image API Docs <#image>`_
Expand All @@ -180,6 +231,13 @@ def job(self) -> JobClient:
"""
Manage jobs.
:examples:
>>> running_jobs = beaker.job.list(
... beaker_on_prem_cluster_name,
... finalized=False,
... )
.. tip::
See the `Jobs Overview <overview.html#jobs>`_ for a walk-through of the
main methods, or check out the `Job API Docs <#job>`_
Expand All @@ -192,6 +250,14 @@ def experiment(self) -> ExperimentClient:
"""
Manage experiments.
:examples:
>>> logs = "".join([
... line.decode() for line in
... beaker.experiment.logs("petew/hello-world", quiet=True)
... ])
<BLANKLINE>
.. tip::
See the `Experiments Overview <overview.html#experiments>`_ for a walk-through of the
main methods, or check out the `Experiment API Docs <#experiment>`_
Expand All @@ -204,6 +270,10 @@ def secret(self) -> SecretClient:
"""
Manage secrets.
:examples:
>>> secret = beaker.secret.write(secret_name, "foo")
.. tip::
See the `Secrets Overview <overview.html#secrets>`_ for a walk-through of the
main methods, or check out the `Secret API Docs <#secret>`_
Expand Down
2 changes: 2 additions & 0 deletions beaker/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ def doctest_fixtures(
download_path,
beaker_org_name,
beaker_node_id,
secret_name,
):
doctest_namespace["beaker"] = client
doctest_namespace["workspace_name"] = workspace_name
Expand All @@ -27,3 +28,4 @@ def doctest_fixtures(
doctest_namespace["download_path"] = download_path
doctest_namespace["beaker_org_name"] = beaker_org_name
doctest_namespace["beaker_node_id"] = beaker_node_id
doctest_namespace["secret_name"] = secret_name
4 changes: 4 additions & 0 deletions beaker/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ class OrganizationNotFound(BeakerError):
pass


class OrganizationNotSet(BeakerError):
pass


class ConfigurationError(BeakerError):
pass

Expand Down
4 changes: 4 additions & 0 deletions beaker/services/account.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@


class AccountClient(ServiceClient):
"""
Accessed via :data:`Beaker.account <beaker.Beaker.account>`.
"""

@property # type: ignore[misc]
@cached(cache=TTLCache(maxsize=10, ttl=5 * 60))
def name(self):
Expand Down
17 changes: 12 additions & 5 deletions beaker/services/cluster.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@


class ClusterClient(ServiceClient):
"""
Accessed via :data:`Beaker.cluster <beaker.Beaker.cluster>`.
"""

def create(
self,
name: str,
Expand Down Expand Up @@ -115,22 +119,25 @@ def delete(self, cluster: Union[str, Cluster]):
exceptions_for_status={404: ClusterNotFound(self._not_found_err_msg(cluster_id))},
)

def list(self, org: Union[str, Organization]) -> List[Cluster]:
def list(self, org: Optional[Union[str, Organization]] = None) -> List[Cluster]:
"""
List clusters under an organization.
:param org: The organization name or object.
:param org: The organization name or object. If not specified,
:data:`Beaker.config.default_org <beaker.Config.default_org>` is used.
:raises OrganizationNotFound: If the organization doesn't exist.
:raises OrganizationNotSet: If neither ``org`` nor
:data:`Beaker.config.default_org <beaker.Config.default_org>` are set.
:raises HTTPError: Any other HTTP exception that can occur.
"""
org_name = org if isinstance(org, str) else org.name
org: Organization = self._resolve_org(org)
return [
Cluster.from_json(d)
for d in self.request(
f"clusters/{self._url_quote(org_name)}",
f"clusters/{org.id}",
method="GET",
exceptions_for_status={404: OrganizationNotFound(org_name)},
exceptions_for_status={404: OrganizationNotFound(org.id)},
).json()["data"]
]

Expand Down
11 changes: 9 additions & 2 deletions beaker/services/dataset.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@


class DatasetClient(ServiceClient):
"""
Accessed via :data:`Beaker.dataset <beaker.Beaker.dataset>`.
"""

HEADER_UPLOAD_ID = "Upload-ID"
HEADER_UPLOAD_LENGTH = "Upload-Length"
HEADER_UPLOAD_OFFSET = "Upload-Offset"
Expand Down Expand Up @@ -65,7 +69,7 @@ def create(
the contents of one of the directory's files changes while creating the dataset.
:raises FileNotFoundError: If a source doesn't exist.
"""
workspace_name = self._resolve_workspace(workspace)
workspace: Workspace = self._resolve_workspace(workspace)

# Create the dataset.
def make_dataset() -> Dataset:
Expand All @@ -74,7 +78,10 @@ def make_dataset() -> Dataset:
"datasets",
method="POST",
query={"name": name},
data={"workspace": workspace_name, "fileheap": True},
data={
"workspace": workspace.id, # type: ignore
"fileheap": True,
},
exceptions_for_status={409: DatasetConflict(name)},
).json()
)
Expand Down
8 changes: 6 additions & 2 deletions beaker/services/experiment.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@


class ExperimentClient(ServiceClient):
"""
Accessed via :data:`Beaker.experiment <beaker.Beaker.experiment>`.
"""

def create(
self, name: str, spec: Union[ExperimentSpec, PathOrStr], workspace: Optional[str] = None
) -> Experiment:
Expand Down Expand Up @@ -38,9 +42,9 @@ def create(
spec = ExperimentSpec.from_file(spec)
json_spec = spec.to_json()

workspace_name = self._resolve_workspace(workspace)
workspace: Workspace = self._resolve_workspace(workspace)
experiment_data = self.request(
f"workspaces/{self._url_quote(workspace_name)}/experiments",
f"workspaces/{workspace.id}/experiments",
method="POST",
query={"name": name},
data=json_spec,
Expand Down
8 changes: 6 additions & 2 deletions beaker/services/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@


class ImageClient(ServiceClient):
"""
Accessed via :data:`Beaker.image <beaker.Beaker.image>`.
"""

def create(
self,
name: str,
Expand All @@ -32,7 +36,7 @@ def create(
:raises HTTPError: Any other HTTP exception that can occur.
"""
workspace_name = self._resolve_workspace(workspace)
workspace: Workspace = self._resolve_workspace(workspace)

# Get local Docker image object.
image = self.docker.images.get(image_tag)
Expand All @@ -41,7 +45,7 @@ def create(
image_data = self.request(
"images",
method="POST",
data={"Workspace": workspace_name, "ImageID": image.id, "ImageTag": image_tag},
data={"Workspace": workspace.id, "ImageID": image.id, "ImageTag": image_tag},
query={"name": name},
exceptions_for_status={409: ImageConflict(name)},
).json()
Expand Down
4 changes: 4 additions & 0 deletions beaker/services/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@


class JobClient(ServiceClient):
"""
Accessed via :data:`Beaker.job <beaker.Beaker.job>`.
"""

def get(self, job_id: str) -> Job:
"""
Get information about a job.
Expand Down
4 changes: 4 additions & 0 deletions beaker/services/node.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@


class NodeClient(ServiceClient):
"""
Accessed via :data:`Beaker.node <beaker.Beaker.node>`.
"""

def get(self, node_id: str) -> Node:
"""
Get information about a node.
Expand Down
Loading

0 comments on commit 375f3e8

Please sign in to comment.