diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10bd741d..6ea99819 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -50,3 +50,17 @@ jobs: cache: 'pip' - run: make deps - run: make build + + docs: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: 3.x + check-latest: true + cache: 'pip' + - uses: quarto-dev/quarto-actions/setup@v2 + - run: make deps + - run: make dev + - run: make docs diff --git a/Makefile b/Makefile index aa398c4b..00b3b179 100644 --- a/Makefile +++ b/Makefile @@ -13,20 +13,7 @@ else PIP := pip3 endif -.PHONY: - build - clean - cov - default - deps - dev - fmt - fix - install - lint - test - uninstall - version +.PHONY: build clean cov default deps dev docs fmt fix install lint test uninstall version # Default target that runs the necessary steps to build the project all: deps dev test lint build @@ -64,6 +51,10 @@ deps: dev: $(PIP) install -e . +# Build documentation. +docs: + $(MAKE) -C ./docs + # Target for fixing linting issues. fix: $(PYTHON) -m ruff check --fix @@ -87,8 +78,8 @@ test: # Target for uninstalling the project uninstall: - $(PIP) uninstall -y $(NAME) + $(PIP) uninstall $(NAME) # Target for displaying the project version version: - $(PYTHON) -m setuptools_scm + @$(PYTHON) -m setuptools_scm diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..8c9421ed --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,8 @@ +_extensions/ +_inv/ +_site/ +.quarto/ +objects.json +reference/ + +/.quarto/ diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..05158648 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,33 @@ +.DEFAULT_GOAL := all + +# Command aliases +QUARTO=quarto +QUARTODOC=quartodoc + +# Environment variables +CURRENT_YEAR := $(shell date +%Y) +VERSION := $(shell make -C ../ version) +export CURRENT_YEAR VERSION + +.PHONY: clean build deps preview + +all: deps api build + +api: deps + $(QUARTODOC) build + $(QUARTODOC) interlinks + cp -r _extensions/ reference/_extensions # Required to render footer + +build: api + $(QUARTO) render + +clean: + rm -rf _extensions _inv _site .quarto reference objects.json + find . -type d -empty -delete + +deps: + $(QUARTO) add --no-prompt posit-dev/product-doc-theme@v4.0.2 + $(QUARTO) add --no-prompt machow/quartodoc + +preview: api + $(QUARTO) preview diff --git a/docs/_quarto.yml b/docs/_quarto.yml new file mode 100644 index 00000000..cb3cbaa6 --- /dev/null +++ b/docs/_quarto.yml @@ -0,0 +1,107 @@ +project: + type: website + +website: + title: "Posit SDK {{< env VERSION >}}" + bread-crumbs: true + favicon: "_extensions/posit-dev/posit-docs/assets/images/favicon.svg" + navbar: + pinned: true + logo: "_extensions/posit-dev/posit-docs/assets/images/posit-icon-fullcolor.svg" + logo-alt: "Posit Documentation" + left: + - text: Installation + file: installation.qmd + - text: Quick Start + file: quickstart.qmd + - text: API + file: reference/index.qmd + right: + - icon: "list" + aria-label: 'Drop-down menu for additional Posit resources' + menu: + - text: "docs.posit.co" + href: "https://docs.posit.co" + - text: "Posit Support" + href: "https://support.posit.co/hc/en-us/" + page-footer: + left: | + Copyright © 2000-{{< env CURRENT_YEAR >}} Posit Software, PBC. All Rights Reserved. + center: | + Posit PRODUCT {{< env VERSION >}} + right: + - icon: question-circle-fill + aria-label: 'Link to Posit Support' + href: "https://support.posit.co/hc/en-us" + - icon: lightbulb-fill + aria-label: 'Link to Posit Solutions' + href: "https://solutions.posit.co/" + - text: "" + href: "https://docs.posit.co/" + - text: "" + href: "https://posit.co/" + search: + copy-button: true + show-item-context: true + +filters: + - interlinks + +format: + html: + theme: + light: + - _extensions/posit-dev/posit-docs/theme.scss + dark: + - _extensions/posit-dev/posit-docs/theme-dark.scss + include-in-header: "_extensions/posit-dev/posit-docs/assets/_analytics.html" + link-external-icon: true + link-external-newwindow: true + toc: true + toc-expand: true + +interlinks: + sources: + python: + url: https://docs.python.org/3/ + requests: + url: https://requests.readthedocs.io/en/latest/ + +quartodoc: + title: API Reference + style: pkgdown + dir: reference + package: posit + render_interlinks: true + options: + include_classes: true + include_functions: true + include_empty: true + sections: + - title: Clients + package: posit + desc: > + The `Client` is the entrypoint for each Posit product. Initialize a `Client` to get started. + contents: + - name: connect.Client + members: + # methods + - request + - get + - post + - put + - patch + - delete + - title: Posit Connect Resources + package: posit + contents: + - connect.bundles + - connect.content + - connect.permissions + - connect.tasks + - connect.users + - title: Posit Connect Metrics + package: posit + contents: + - connect.metrics + - connect.metrics.usage diff --git a/docs/index.qmd b/docs/index.qmd new file mode 100644 index 00000000..9eb8d71a --- /dev/null +++ b/docs/index.qmd @@ -0,0 +1,7 @@ +--- +title: Posit SDK for Python +--- + +> A Pythonic interface for developers to work with Posit professional products. It's lightweight and expressive. + +Welcome to the Posit SDK for Python documentation! Get started with [Installation](./installation.qmd) and then check out [Quickstart](./quickstart.qmd). A full API reference is available in the [API](./reference/index.qmd) section. diff --git a/docs/installation.qmd b/docs/installation.qmd new file mode 100644 index 00000000..435f7ed4 --- /dev/null +++ b/docs/installation.qmd @@ -0,0 +1,27 @@ +--- +title: "Installation" +format: html +toc: true +toc-title: "Contents" +toc-depth: 2 +--- + +## Python version + +We recommend using the latest version of Python available to you. The Posit SDK supports Python 3.8 and newer. + +## Dependencies + +These dependencies are installed automatically during installation. + +- [Requests](https://requests.readthedocs.io/en/latest/) provides the HTTP layer. + +## Install the Posit SDK + +Use the following command to install the Posit SDK: + +```bash +$ pip install posit-sdk +``` + +The SDK is now installed. Check out the [Quickstart](./quickstart.qmd) section. diff --git a/docs/quickstart.qmd b/docs/quickstart.qmd new file mode 100644 index 00000000..da2f673d --- /dev/null +++ b/docs/quickstart.qmd @@ -0,0 +1,56 @@ +--- +title: "Quickstart" +format: html +toc: true +toc-title: "Contents" +toc-depth: 2 +--- + +## Setup + +After [installing](./installation.qmd) the SDK, you need to import it. Here's a simple example to get you started: + +```python +>>> import posit +``` + +## Basic usage + +### Initialize a client + +To get started, initialize a client to work with your Posit products. For this example, we will work with Posit Connect. + +::: {.callout-warning} +Keeping your API key secret is essential to protect your application's security, prevent unauthorized access and usage, and ensure data privacy and regulatory compliance. By default, the API key is read from the `CONNECT_API_KEY` environment variable when not provided by during client initialization. +::: + + +```python +>>> from posit import connect +>>> client = connect.Client(api_key="btOVKLXjt8CoGP2gXvSuTqu025MJV4da", url="https://connect.example.com") +``` + +### Who am I? + +Your API key is your secret identity. Now that we're connected, let's double check our username. + +```python +>>> client.me.username +'gandalf' +``` + +### How much content do I have access to? + +One of the best features of Connect is the ability to share content with your peers. Let's see just how much content we have access to! + +```python +>>> client.content.count() +5754 +``` + +VoilĂ ! I have a whopping 5,754 pieces of content to browse. + + +## Next steps + +Check out the [API Reference](./reference/index.qmd) for more information. diff --git a/requirements-dev.txt b/requirements-dev.txt index ff1a036f..76259bb1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -5,6 +5,7 @@ pandas pre-commit pyjson5 pytest +quartodoc responses ruff setuptools diff --git a/src/posit/__init__.py b/src/posit/__init__.py index 32a10a33..e02b4183 100644 --- a/src/posit/__init__.py +++ b/src/posit/__init__.py @@ -1 +1,5 @@ """The Posit SDK.""" + +from . import connect # noqa + +__all__ = "connect" diff --git a/src/posit/connect/bundles.py b/src/posit/connect/bundles.py index 405e758d..ef2b5a47 100644 --- a/src/posit/connect/bundles.py +++ b/src/posit/connect/bundles.py @@ -1,14 +1,32 @@ +"""Bundle resources.""" + from __future__ import annotations -import io +import io import requests - from typing import List - from . import config, resources, tasks, urls class BundleMetadata(resources.Resource): + """Bundle metadata resource. + + Attributes + ---------- + source : str | None + Source of the bundle. + source_repo : str | None + Source repository of the bundle. + source_branch : str | None + Source branch of the bundle. + source_commit : str | None + Source commit of the bundle. + archive_md5 : str | None + MD5 checksum of the bundle archive. + archive_sha1 : str | None + SHA-1 checksum of the bundle archive. + """ + @property def source(self) -> str | None: return self.get("source") @@ -35,6 +53,38 @@ def archive_sha1(self) -> str | None: class Bundle(resources.Resource): + """Bundle resource. + + Attributes + ---------- + id : str + Identifier of the bundle. + content_guid : str + Content GUID of the bundle. + created_time : str + Creation time of the bundle. + cluster_name : str | None + Cluster name associated with the bundle. + image_name : str | None + Image name of the bundle. + r_version : str | None + R version used in the bundle. + r_environment_management : bool | None + Indicates if R environment management is enabled. + py_version : str | None + Python version used in the bundle. + py_environment_management : bool | None + Indicates if Python environment management is enabled. + quarto_version : str | None + Quarto version used in the bundle. + active : bool | None + Indicates if the bundle is active. + size : int | None + Size of the bundle. + metadata : BundleMetadata + Metadata of the bundle. + """ + @property def id(self) -> str: return self["id"] @@ -89,9 +139,8 @@ def metadata(self) -> BundleMetadata: self.config, self.session, **self.get("metadata", {}) ) - # CRUD Methods - def delete(self) -> None: + """Delete the bundle.""" path = f"v1/content/{self.content_guid}/bundles/{self.id}" url = urls.append(self.config.url, path) self.session.delete(url) @@ -119,14 +168,14 @@ def deploy(self) -> tasks.Task: ts = tasks.Tasks(self.config, self.session) return ts.get(result["task_id"]) - def download(self, output: io.BufferedWriter | str): + def download(self, output: io.BufferedWriter | str) -> None: """Download a bundle. Download a bundle to a file or memory. Parameters ---------- - output: io.BufferedWriter | str + output : io.BufferedWriter or str An io.BufferedWriter instance or a str representing a relative or absolute path. Raises @@ -147,7 +196,7 @@ def download(self, output: io.BufferedWriter | str): """ if not isinstance(output, (io.BufferedWriter, str)): raise TypeError( - f"download() expected argument type 'io.BufferedWriter` or 'str', but got '{type(input).__name__}'" + f"download() expected argument type 'io.BufferedWriter` or 'str', but got '{type(output).__name__}'" ) path = f"v1/content/{self.content_guid}/bundles/{self.id}/download" @@ -156,16 +205,30 @@ def download(self, output: io.BufferedWriter | str): if isinstance(output, io.BufferedWriter): for chunk in response.iter_content(): output.write(chunk) - return - - if isinstance(output, str): + elif isinstance(output, str): with open(output, "wb") as file: for chunk in response.iter_content(): file.write(chunk) - return class Bundles(resources.Resources): + """Bundles resource. + + Parameters + ---------- + config : config.Config + Configuration object. + session : requests.Session + HTTP session object. + content_guid : str + Content GUID associated with the bundles. + + Attributes + ---------- + content_guid: str + Content GUID associated with the bundles. + """ + def __init__( self, config: config.Config, @@ -176,13 +239,14 @@ def __init__( self.content_guid = content_guid def create(self, input: io.BufferedReader | bytes | str) -> Bundle: - """Create a bundle. + """ + Create a bundle. Create a bundle from a file or memory. Parameters ---------- - input : io.BufferedReader | bytes | str + input : io.BufferedReader, bytes, or str Input archive for bundle creation. A 'str' type assumes a relative or absolute filepath. Returns @@ -229,24 +293,45 @@ def create(self, input: io.BufferedReader | bytes | str) -> Bundle: return Bundle(self.config, self.session, **result) def find(self) -> List[Bundle]: + """Find all bundles. + + Returns + ------- + list of Bundle + List of all found bundles. + """ path = f"v1/content/{self.content_guid}/bundles" url = urls.append(self.config.url, path) response = self.session.get(url) results = response.json() return [ - Bundle( - self.config, - self.session, - **result, - ) - for result in results + Bundle(self.config, self.session, **result) for result in results ] def find_one(self) -> Bundle | None: + """Find a bundle. + + Returns + ------- + Bundle | None + The first found bundle | None if no bundles are found. + """ bundles = self.find() return next(iter(bundles), None) def get(self, id: str) -> Bundle: + """Get a bundle. + + Parameters + ---------- + id : str + Identifier of the bundle to retrieve. + + Returns + ------- + Bundle + The bundle with the specified ID. + """ path = f"v1/content/{self.content_guid}/bundles/{id}" url = urls.append(self.config.url, path) response = self.session.get(url) diff --git a/src/posit/connect/client.py b/src/posit/connect/client.py index 160c6b9d..16fd4e88 100644 --- a/src/posit/connect/client.py +++ b/src/posit/connect/client.py @@ -1,115 +1,139 @@ -"""Contains the Client class.""" +"""Client connection for Posit Connect.""" from __future__ import annotations from requests import Response, Session from typing import Optional -from . import hooks, me, metrics, tasks, urls +from . import hooks, me, urls from .auth import Auth from .config import Config from .oauth import OAuthIntegration from .content import Content +from .metrics import Metrics +from .tasks import Tasks from .users import User, Users class Client: - """Main interface for Posit Connect.""" + """ + Client connection for Posit Connect. + + This class provides an interface to interact with the Posit Connect API, + allowing for authentication, resource management, and data retrieval. + + Parameters + ---------- + api_key : str, optional + API key for authentication + url : str, optional + Sever API URL + + Attributes + ---------- + content: Content + Content resource. + me: User + Connect user resource. + metrics: Metrics + Metrics resource. + tasks: Tasks + Tasks resource. + users: Users + Users resource. + version: str + Server version. + """ def __init__( self, api_key: Optional[str] = None, url: Optional[str] = None, ) -> None: - """ - Initialize the Client instance. - - Args: - api_key (str, optional): API key for authentication. Defaults to None. - url (str, optional): API url URL. Defaults to None. - """ - # Create a Config object. self.config = Config(api_key=api_key, url=url) - # Create a Session object for making HTTP requests. session = Session() - # Authenticate the session using the provided Config. session.auth = Auth(config=self.config) - # Add hook for checking for deprecation warnings. session.hooks["response"].append(hooks.check_for_deprecation_header) - # Add error handling hooks to the session. session.hooks["response"].append(hooks.handle_errors) - - # Store the Session object. self.session = session - # Internal attributes to hold settings we fetch lazily - self._server_settings = None - @property - def connect_version(self): - """The server version. + def version(self) -> str: + """ + The server version. - Return: - str + Returns + ------- + str + The version of the Posit Connect server. """ - if self._server_settings is None: - self._server_settings = self.get("server_settings").json() - return self._server_settings["version"] + return self.get("server_settings").json()["version"] @property def me(self) -> User: - """The connected user. + """ + The connected user. Returns ------- User + The currently authenticated user. """ return me.get(self.config, self.session) @property def oauth(self) -> OAuthIntegration: - """An OAuthIntegration. + """ + An OAuthIntegration. Returns ------- OAuthIntegration + The OAuth integration instance. """ return OAuthIntegration(config=self.config, session=self.session) @property - def tasks(self) -> tasks.Tasks: - """The tasks resource interface. + def tasks(self) -> Tasks: + """ + The tasks resource interface. Returns ------- tasks.Tasks + The tasks resource instance. """ - return tasks.Tasks(self.config, self.session) + return Tasks(self.config, self.session) @property def users(self) -> Users: - """The users resource interface. + """ + The users resource interface. Returns ------- Users + The users resource instance. """ return Users(config=self.config, session=self.session) @property def content(self) -> Content: - """The content resource interface. + """ + The content resource interface. Returns ------- Content + The content resource instance. """ return Content(config=self.config, session=self.session) @property - def metrics(self) -> metrics.Metrics: - """The Metrics API interface. + def metrics(self) -> Metrics: + """ + The Metrics API interface. The Metrics API is designed for capturing, retrieving, and managing quantitative measurements of Connect interactions. It is commonly used @@ -120,7 +144,8 @@ def metrics(self) -> metrics.Metrics: Returns ------- - metrics.Metrics + Metrics + The metrics API instance. Examples -------- @@ -131,7 +156,7 @@ def metrics(self) -> metrics.Metrics: >>> len(events) 24 """ - return metrics.Metrics(self.config, self.session) + return Metrics(self.config, self.session) def __del__(self): """Close the session when the Client instance is deleted.""" @@ -146,10 +171,14 @@ def __exit__(self, exc_type, exc_value, exc_tb): """ Close the session if it exists. - Args: - exc_type: The type of the exception raised (if any). - exc_value: The exception instance raised (if any). - exc_tb: The traceback for the exception raised (if any). + Parameters + ---------- + exc_type : type + The type of the exception raised (if any). + exc_value : Exception + The exception instance raised (if any). + exc_tb : traceback + The traceback for the exception raised (if any). """ if hasattr(self, "session") and self.session is not None: self.session.close() @@ -158,16 +187,21 @@ def request(self, method: str, path: str, **kwargs) -> Response: """ Send an HTTP request. - A facade for requests.Session.request. + A facade for [](`requests.request`) configured for the target server. - Args: - method (str): The HTTP method to use for the request. - path (str): Appended to the url object attribute. - **kwargs: Additional keyword arguments passed to requests.Session.post. + Parameters + ---------- + method : str + The HTTP method to use for the request. + path : str + Appended to the url object attribute. + **kwargs + Additional keyword arguments passed to [](`requests.request`). Returns ------- - Response: A requests.Response object. + Response + A [](`requests.Response`) object. """ url = urls.append(self.config.url, path) return self.session.request(method, url, **kwargs) @@ -176,14 +210,19 @@ def get(self, path: str, **kwargs) -> Response: """ Send a GET request. - Args: - path (str): Appended to the configured base url. - **kwargs: Additional keyword arguments passed to requests.Session.get. + A facade for [](`requests.get`) configured for the target server. + + Parameters + ---------- + path : str + Appended to the configured base url. + **kwargs + Additional keyword arguments passed to [](`requests.get`). Returns ------- - Response: A requests.Response object. - + Response + A [](`requests.Response`) object. """ url = urls.append(self.config.url, path) return self.session.get(url, **kwargs) @@ -192,14 +231,19 @@ def post(self, path: str, **kwargs) -> Response: """ Send a POST request. - Args: - path (str): Appended to the configured base url. - **kwargs: Additional keyword arguments passed to requests.Session.post. + A facade for [](`requests.post`) configured for the target server. + + Parameters + ---------- + path : str + Appended to the configured base url. + **kwargs + Additional keyword arguments passed to [](`requests.post`). Returns ------- - Response: A requests.Response object. - + Response + A [](`requests.Response`) object. """ url = urls.append(self.config.url, path) return self.session.post(url, **kwargs) @@ -208,14 +252,19 @@ def put(self, path: str, **kwargs) -> Response: """ Send a PUT request. - Args: - path (str): Appended to the configured base url. - **kwargs: Additional keyword arguments passed to requests.Session.put. + A facade for [](`requests.put`) configured for the target server. + + Parameters + ---------- + path : str + Appended to the configured base url. + **kwargs + Additional keyword arguments passed to [](`requests.put`). Returns ------- - Response: A requests.Response object. - + Response + A [](`requests.Response`) object. """ url = urls.append(self.config.url, path) return self.session.put(url, **kwargs) @@ -224,14 +273,19 @@ def patch(self, path: str, **kwargs) -> Response: """ Send a PATCH request. - Args: - path (str): Appended to the configured base url. - **kwargs: Additional keyword arguments passed to requests.Session.patch. + A facade for [](`requests.patch`) configured for the target server. + + Parameters + ---------- + path : str + Appended to the configured base url. + **kwargs + Additional keyword arguments passed to [](`requests.patch`). Returns ------- - Response: A requests.Response object. - + Response + A [](`requests.Response`) object. """ url = urls.append(self.config.url, path) return self.session.patch(url, **kwargs) @@ -240,14 +294,19 @@ def delete(self, path: str, **kwargs) -> Response: """ Send a DELETE request. - Args: - path (str): Appended to the configured base url. - **kwargs: Additional keyword arguments passed to requests.Session.delete. + A facade for [](`requests.delete`) configured for the target server. + + Parameters + ---------- + path : str + Appended to the configured base url. + **kwargs + Additional keyword arguments passed to [](`requests.delete`). Returns ------- - Response: A requests.Response object. - + Response + A [](`requests.Response`) object. """ url = urls.append(self.config.url, path) return self.session.delete(url, **kwargs) diff --git a/src/posit/connect/content.py b/src/posit/connect/content.py index 9512c8da..188c6a4c 100644 --- a/src/posit/connect/content.py +++ b/src/posit/connect/content.py @@ -1,4 +1,4 @@ -"""Provides the Content resource interface.""" +"""Content resources.""" from __future__ import annotations @@ -16,7 +16,7 @@ class ContentItemOwner(Resource): - """The owner of a piece of content.""" + """Owner information.""" @property def guid(self) -> str: @@ -36,7 +36,105 @@ def last_name(self) -> Optional[str]: class ContentItem(Resource): - """A piece of content.""" + """Content item resource. + + Attributes + ---------- + bundles : Bundles + Bundles resource for the content item. + permissions : Permissions + Permissions resource for the content item. + id : str + Unique identifier of the content item. + guid : str + Globally unique identifier of the content item. + name : str + Name of the content item. + title : Optional[str] + Title of the content item. + description : str + Description of the content item. + access_type : str + Access type of the content item. + connection_timeout : Optional[int] + Connection timeout setting for the content item. + read_timeout : Optional[int] + Read timeout setting for the content item. + init_timeout : Optional[int] + Initialization timeout setting for the content item. + idle_timeout : Optional[int] + Idle timeout setting for the content item. + max_processes : Optional[int] + Maximum number of processes allowed for the content item. + min_processes : Optional[int] + Minimum number of processes required for the content item. + max_conns_per_process : Optional[int] + Maximum number of connections per process for the content item. + load_factor : Optional[float] + Load factor for the content item. + cpu_request : Optional[float] + CPU request for the content item. + cpu_limit : Optional[float] + CPU limit for the content item. + memory_request : Optional[int] + Memory request for the content item. + memory_limit : Optional[int] + Memory limit for the content item. + amd_gpu_limit : Optional[int] + AMD GPU limit for the content item. + nvidia_gpu_limit : Optional[int] + NVIDIA GPU limit for the content item. + created_time : str + Creation time of the content item. + last_deployed_time : str + Last deployment time of the content item. + bundle_id : Optional[str] + Bundle ID associated with the content item. + app_mode : str + Application mode of the content item. + content_category : Optional[str] + Content category of the content item. + parameterized : bool + Indicates if the content item is parameterized. + cluster_name : Optional[str] + Name of the cluster associated with the content item. + image_name : Optional[str] + Name of the image associated with the content item. + default_image_name : Optional[str] + Default image name for the content item. + default_r_environment_management : Optional[bool] + Indicates if R environment management is enabled by default. + default_py_environment_management : Optional[bool] + Indicates if Python environment management is enabled by default. + service_account_name : Optional[str] + Name of the service account associated with the content item. + r_version : Optional[str] + R version used by the content item. + r_environment_management : Optional[bool] + Indicates if R environment management is enabled. + py_version : Optional[str] + Python version used by the content item. + py_environment_management : Optional[bool] + Indicates if Python environment management is enabled. + quarto_version : Optional[str] + Quarto version used by the content item. + run_as : Optional[str] + User to run the content item as. + run_as_current_user : bool + Indicates if the content item runs as the current user. + owner_guid : str + GUID of the owner of the content item. + owner : ContentItemOwner + Owner information of the content item. + content_url : str + URL of the content item. + dashboard_url : str + Dashboard URL of the content item. + app_role : str + Application role of the content item. + tags : List[dict] + Tags associated with the content item. + """ # Relationships @@ -336,6 +434,8 @@ def update(self, *args, **kwargs) -> None: class Content(Resources): + """Content resource.""" + def __init__(self, config: Config, session: Session) -> None: self.url = urls.append(config.url, "v1/content") self.config = config diff --git a/src/posit/connect/metrics/__init__.py b/src/posit/connect/metrics/__init__.py index fd82f39d..0d0dafb5 100644 --- a/src/posit/connect/metrics/__init__.py +++ b/src/posit/connect/metrics/__init__.py @@ -1,9 +1,20 @@ +"""Metric resources.""" + from .. import resources from . import usage +from .usage import Usage class Metrics(resources.Resources): + """Metrics resource. + + Attributes + ---------- + usage: Usage + Usage resource. + """ + @property - def usage(self) -> usage.Usage: - return usage.Usage(self.config, self.session) + def usage(self) -> Usage: + return Usage(self.config, self.session) diff --git a/src/posit/connect/metrics/usage.py b/src/posit/connect/metrics/usage.py index e9deda27..fb838233 100644 --- a/src/posit/connect/metrics/usage.py +++ b/src/posit/connect/metrics/usage.py @@ -1,6 +1,6 @@ -from __future__ import annotations +"""Usage resources.""" -import itertools +from __future__ import annotations from typing import List, overload @@ -155,6 +155,8 @@ def path(self) -> str | None: class Usage(resources.Resources): + """Usage resource.""" + @overload def find( self, diff --git a/src/posit/connect/permissions.py b/src/posit/connect/permissions.py index f8a788db..703dfc52 100644 --- a/src/posit/connect/permissions.py +++ b/src/posit/connect/permissions.py @@ -1,3 +1,5 @@ +"""Permission resources.""" + from __future__ import annotations from typing import List, overload diff --git a/src/posit/connect/tasks.py b/src/posit/connect/tasks.py index 5c3f8e7a..4bcc0e30 100644 --- a/src/posit/connect/tasks.py +++ b/src/posit/connect/tasks.py @@ -1,3 +1,5 @@ +"""Task resources.""" + from __future__ import annotations from typing import List, overload diff --git a/src/posit/connect/users.py b/src/posit/connect/users.py index 58b2254d..f6c0ea24 100644 --- a/src/posit/connect/users.py +++ b/src/posit/connect/users.py @@ -1,10 +1,10 @@ +"""User resources.""" + from __future__ import annotations from typing import List, overload - import requests - from . import me, urls from .config import Config @@ -13,6 +13,25 @@ class User(Resource): + """User resource. + + Attributes + ---------- + guid : str + email : str + username : str + first_name : str + last_name : str + user_role : str + created_time : str + updated_time : str + active_time : str + confirmed : bool + Whether the user has confirmed their email address. + locked : bool + Whether the user is locked. + """ + @property def guid(self) -> str: return self.get("guid") # type: ignore @@ -152,6 +171,8 @@ def update(self, *args, **kwargs) -> None: class Users(Resources): + """Users resource.""" + def __init__(self, config: Config, session: requests.Session) -> None: self.url = urls.append(config.url, "v1/users") self.config = config diff --git a/tests/posit/connect/test_client.py b/tests/posit/connect/test_client.py index d61f2c69..51a5e85a 100644 --- a/tests/posit/connect/test_client.py +++ b/tests/posit/connect/test_client.py @@ -74,7 +74,7 @@ def test_connect_version(self): "http://foo.bar/__api__/server_settings", json={"version": "2024.01.0"}, ) - assert client.connect_version == "2024.01.0" + assert client.version == "2024.01.0" @responses.activate def test_me_request(self):