Skip to content

Commit

Permalink
merge: resolves conflicts (#40)
Browse files Browse the repository at this point in the history
* doc: put some words in README.md (#38)

* feat: adds passthrough methods for each HTTP request type (#25)

* feat: adds connect_version property to Client (#39)

* build: disables coverage threshold for new files (#41)

* ci: adds main.yaml (#42)

* ci: fixes main.yaml job name (#43)

---------

Co-authored-by: Neal Richardson <neal.p.richardson@gmail.com>
  • Loading branch information
tdstein and nealrichardson committed Feb 21, 2024
1 parent ecb0e8d commit 89b7afc
Show file tree
Hide file tree
Showing 14 changed files with 419 additions and 121 deletions.
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

0 comments on commit 89b7afc

Please sign in to comment.