Skip to content
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

merge: resolves conflicts #40

Merged
merged 8 commits into from
Feb 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
24 changes: 24 additions & 0 deletions .github/workflows/main.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Main
on:
push:
branches:
- main
jobs:
default:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version:
- '3.8'
- '3.9'
- '3.10'
- '3.11'
- '3.12'
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: make
- run: make install
2 changes: 0 additions & 2 deletions .github/workflows/pull-request.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,4 @@ jobs:
with:
coverageFile: coverage.xml
thresholdAll: 0.8
thresholdNew: 0.9
thresholdModified: 0.9
token: ${{ secrets.GITHUB_TOKEN }}
28 changes: 26 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,26 @@
# posit-sdk-py
Posit SDK for Python
# Posit SDK for Python

This package provides a Pythonic interface for developers to work against the public APIs of Posit's professional products. It is intended to be lightweight yet expressive.

> The Posit SDK is in the very early stages of development, and currently only Posit Connect has any support.

## Installation

```shell
pip install posit-sdk
```

## Usage

```python
from posit.connect import Client

# If CONNECT_API_KEY and CONNECT_SERVER are set in your environment,
# they will be picked up, or you can pass them as arguments
con = Client()
con.users.find()
```

<!-- Add notes about local development -->

<!-- Add code of conduct -->
1 change: 1 addition & 0 deletions requirements-dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ build==1.0.3
coverage==7.4.0
mypy==1.8.0
pytest==7.4.4
responses>=0.25
ruff==0.1.14
setuptools==69.0.3
setuptools-scm==8.0.4
142 changes: 124 additions & 18 deletions src/posit/connect/client.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,174 @@
from __future__ import annotations

import os
from requests import Session
from requests import Response, Session
from typing import Optional

from . import hooks
from . import hooks, urls

from .auth import Auth
from .config import Config
from .users import User, Users, CachedUsers
from .users import CachedUsers, Users, User


class Client:
def __init__(
self,
api_key: Optional[str] = None,
endpoint: Optional[str] = None,
url: Optional[str] = None,
) -> None:
"""
Initialize the Client instance.

Args:
api_key (str, optional): API key for authentication. Defaults to None.
endpoint (str, optional): API endpoint URL. Defaults to None.
url (str, optional): API url URL. Defaults to None.
"""
# Create a Config object.
config = Config(api_key=api_key, endpoint=endpoint)
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=config)
session.auth = Auth(config=self.config)
# Add error handling hooks to the session.
session.hooks["response"].append(hooks.handle_errors)

# Store the Config and Session objects.
self._config = config
self._session = session
# Store the Session object.
self.session = session

# Internal properties for storing public resources
self.server_settings = None
self._current_user: Optional[User] = None


@property
def me(self) -> User:
if self._current_user is None:
endpoint = os.path.join(self._config.endpoint, "v1/user")
response = self._session.get(endpoint)
url = urls.append_path(self.config.url, "v1/user")
response = self.session.get(url)
self._current_user = User(**response.json())
return self._current_user


@property
def users(self) -> CachedUsers:
return Users(client=self)
# Place to cache the server settings
self.server_settings = None

@property
def connect_version(self):
if self.server_settings is None:
self.server_settings = self.get("server_settings").json()
return self.server_settings["version"]

def __del__(self):
"""
Close the session when the Client instance is deleted.
"""
self._session.close()
if hasattr(self, "session") and self.session is not None:
self.session.close()

def __enter__(self):
"""
Enter method for using the client as a context manager.
"""
return self

def __exit__(self, exc_type, exc_value, exc_tb):
self._session.close()
"""
Closes 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).
"""
if hasattr(self, "session") and self.session is not None:
self.session.close()

def request(self, method: str, path: str, **kwargs) -> Response:
"""
Sends an HTTP request to the specified path using the given method.

Args:
method (str): The HTTP method to use for the request.
path (str): The path to send the request to.
**kwargs: Additional keyword arguments to pass to the underlying session's request method.

Returns:
Response: The response object containing the server's response to the request.
"""
url = urls.append_path(self.config.url, path)
return self.session.request(method, url, **kwargs)

def get(self, path: str, **kwargs) -> Response:
"""
Send a GET request to the specified path.

Args:
path (str): The path to send the request to.
**kwargs: Additional keyword arguments to be passed to the underlying session's `get` method.

Returns:
Response: The response object.

"""
url = urls.append_path(self.config.url, path)
return self.session.get(url, **kwargs)

def post(self, path: str, **kwargs) -> Response:
"""
Send a POST request to the specified path.

Args:
path (str): The path to send the request to.
**kwargs: Additional keyword arguments to be passed to the underlying session's `post` method.

Returns:
Response: The response object.

"""
url = urls.append_path(self.config.url, path)
return self.session.post(url, **kwargs)

def put(self, path: str, **kwargs) -> Response:
"""
Send a PUT request to the specified path.

Args:
path (str): The path to send the request to.
**kwargs: Additional keyword arguments to be passed to the underlying session's `put` method.

Returns:
Response: The response object.

"""
url = urls.append_path(self.config.url, path)
return self.session.put(url, **kwargs)

def patch(self, path: str, **kwargs) -> Response:
"""
Send a PATCH request to the specified path.

Args:
path (str): The path to send the request to.
**kwargs: Additional keyword arguments to be passed to the underlying session's `patch` method.

Returns:
Response: The response object.

"""
url = urls.append_path(self.config.url, path)
return self.session.patch(url, **kwargs)

def delete(self, path: str, **kwargs) -> Response:
"""
Send a DELETE request to the specified path.

Args:
path (str): The path to send the request to.
**kwargs: Additional keyword arguments to be passed to the underlying session's `delete` method.

Returns:
Response: The response object.

"""
url = urls.append_path(self.config.url, path)
return self.session.delete(url, **kwargs)
Loading
Loading