-
Notifications
You must be signed in to change notification settings - Fork 108
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #37 from privacera/paig_common
paig-common added #34
- Loading branch information
Showing
31 changed files
with
2,193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
name: paig-common-ci | ||
|
||
on: | ||
push: | ||
branches: [ "main" ] | ||
paths: | ||
- 'paig-common/**' | ||
pull_request: | ||
branches: [ "main" ] | ||
paths: | ||
- 'paig-common/**' | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
build_and_test: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 # Ensure the repo is checked out | ||
|
||
- name: Set up Python 3.10 | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: "3.10" | ||
|
||
- name: Install dependencies | ||
run: | | ||
python3 -m pip install virtualenv | ||
virtualenv -p python3 venv && . venv/bin/activate | ||
pip install twine build pytest pytest-cov | ||
pip install -r paig-common/requirements.txt | ||
- name: Test with pytest | ||
run: | | ||
filepath=$PWD | ||
. venv/bin/activate && cd paig-common/src | ||
python3 -m pytest --cov="." --cov-report term --cov-report xml:$filepath/coverage.xml --junitxml=$filepath/junit.xml tests | ||
- name: Upload coverage report | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: coverage-report | ||
path: coverage.xml | ||
|
||
- name: Upload test results | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: junit-results | ||
path: junit.xml | ||
|
||
- name: Build the wheel | ||
run: | | ||
. venv/bin/activate | ||
cd paig-common | ||
python3 -m build -w | ||
cd .. | ||
- name: Install created package | ||
run: | | ||
rm -rf venv | ||
virtualenv -p python3 venv && . venv/bin/activate | ||
cd paig-common | ||
pip install dist/*.whl |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
name: paig-common upload to PyPI | ||
|
||
on: | ||
workflow_dispatch: # Allows manual trigger | ||
|
||
permissions: | ||
contents: read | ||
|
||
jobs: | ||
release: | ||
runs-on: ubuntu-latest | ||
steps: | ||
- uses: actions/checkout@v4 # Ensure the repo is checked out | ||
|
||
- name: Set up Python 3.10 | ||
uses: actions/setup-python@v5 | ||
with: | ||
python-version: "3.10" | ||
|
||
- name: Install dependencies | ||
run: | | ||
python3 -m pip install virtualenv | ||
virtualenv -p python3 venv && . venv/bin/activate | ||
pip install twine build pytest pytest-cov | ||
pip install -r paig-common/requirements.txt | ||
- name: Test with pytest | ||
run: | | ||
filepath=$PWD | ||
. venv/bin/activate && cd paig-common/src | ||
python3 -m pytest --cov="." --cov-report term --cov-report xml:$filepath/coverage.xml --junitxml=$filepath/junit.xml tests | ||
- name: Upload coverage report | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: coverage-report | ||
path: coverage.xml | ||
|
||
- name: Upload test results | ||
uses: actions/upload-artifact@v4 | ||
with: | ||
name: junit-results | ||
path: junit.xml | ||
|
||
- name: Build the wheel | ||
run: | | ||
. venv/bin/activate | ||
cd paig-common | ||
python3 -m build -w | ||
- name: Install created package | ||
run: | | ||
rm -rf venv | ||
virtualenv -p python3 venv && . venv/bin/activate | ||
cd paig-common | ||
pip install dist/*.whl | ||
- name: Release to PyPI | ||
env: | ||
TWINE_USERNAME: ${{ secrets.TWINE_USERNAME }} | ||
TWINE_PASSWORD: ${{ secrets.TWINE_PYPI_API_TOKEN }} | ||
run: | | ||
. venv/bin/activate | ||
pip install twine | ||
twine upload paig-common/dist/* |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
## Common library for Privacera AI Governance Applications | ||
The paig_common provides the common classes/functions used by the privacera AI governance applications. | ||
|
||
|
||
## Development | ||
``` | ||
# go inside the project repository | ||
cd paig-common | ||
# setup environment | ||
python3 -m venv venv | ||
# activate environment | ||
. venv/bin/activate | ||
# install dependencies | ||
pip install -r requirements.txt | ||
``` | ||
|
||
## Tests | ||
``` | ||
# go to shield directory inside the project repository | ||
cd paig-common | ||
# activate environment | ||
. venv/bin/activate | ||
# run tests with coverage | ||
cd src | ||
sh run_unit_test.sh | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
[build-system] | ||
requires = ["hatchling"] | ||
build-backend = "hatchling.build" | ||
|
||
[project] | ||
name = "paig-common" | ||
dynamic = ["version"] | ||
authors = [ | ||
{ name = "Privacera ", email = "[email protected]" }, | ||
] | ||
description = "PAIG Shield Common Library" | ||
readme = "README.md" | ||
license = {file = "../LICENSE"} | ||
requires-python = ">=3.8.1" | ||
classifiers = [ | ||
"Programming Language :: Python :: 3", | ||
"License :: Other/Proprietary License", | ||
"Operating System :: OS Independent", | ||
] | ||
dependencies = [ | ||
"urllib3>=2.0.6", | ||
"cryptography>=41.0.4", | ||
"jproperties>=2.1.1" | ||
] | ||
|
||
[project.urls] | ||
"Homepage" = "https://github.com/pypa/paig_common" | ||
"Bug Tracker" = "https://github.com/pypa/paig_common/issues" | ||
|
||
[tool.hatch.version] | ||
path = "src/paig_common/VERSION" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
urllib3==2.2.1 | ||
cryptography==42.0.5 | ||
build==1.2.1 | ||
pytest==8.0.0 | ||
pytest-cov==4.1.0 | ||
pytest-html==4.1.1 | ||
pytest-mock==3.12.0 | ||
twine==5.0.0 | ||
boto3==1.34.82 | ||
python-gitlab==4.4.0 | ||
jproperties==2.1.1 | ||
Flask==3.0.3 | ||
fastapi==0.111.0 | ||
httpx==0.27.0 | ||
pytest-asyncio==0.23.7 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
__version__ = '0.0.1' |
Empty file.
141 changes: 141 additions & 0 deletions
141
paig-common/src/paig_common/async_base_rest_http_client.py
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
import asyncio | ||
import json | ||
from typing import Collection | ||
import httpx | ||
|
||
from . import config_utils | ||
from .async_http_transport import AsyncHttpTransport | ||
import logging | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class AsyncBaseRESTHttpClient: | ||
""" | ||
An asynchronous base class for making HTTP requests to a RESTful API using httpx. | ||
""" | ||
_setup_done = False # Flag to track if setup has been performed | ||
_setup_lock = asyncio.Lock() | ||
_max_retries = config_utils.get_property_value_int("http.rest.client.max_retries", 4) | ||
_backoff_factor = config_utils.get_property_value_int("http.rest.client.backoff_factor", 1) | ||
_allowed_methods_str = config_utils.get_property_value("http.rest.client.allowed_methods", | ||
'["GET", "POST", "PUT", "DELETE"]') | ||
_allowed_methods: Collection[str] = eval(_allowed_methods_str) | ||
_status_forcelist_str = config_utils.get_property_value("http.rest.client.status_forcelist", | ||
'[500, 502, 503, 504]') | ||
_status_forcelist: Collection[int] = eval(_status_forcelist_str) | ||
|
||
def __init__(self, base_url): | ||
""" | ||
Initialize the AsyncBaseRESTHttpClient instance. | ||
Args: | ||
base_url (str): The base URL of the REST API. | ||
""" | ||
self.baseUrl = base_url | ||
|
||
@staticmethod | ||
async def setup(): | ||
""" | ||
Set up the Async HTTP transport configuration (called only once). | ||
Returns: | ||
None | ||
""" | ||
async with AsyncBaseRESTHttpClient._setup_lock: | ||
if not AsyncBaseRESTHttpClient._setup_done: | ||
connect_timeout_sec = config_utils.get_property_value_float("http.rest.client.connect_timeout_sec", 2.0) | ||
read_timeout_sec = config_utils.get_property_value_float("http.rest.client.read_timeout_sec", 7.0) | ||
await AsyncHttpTransport.setup( | ||
connect_timeout_sec=connect_timeout_sec, | ||
read_timeout_sec=read_timeout_sec | ||
) | ||
AsyncBaseRESTHttpClient._setup_done = True | ||
|
||
def get_auth(self): | ||
""" | ||
Get the authentication configuration for the HTTP requests. | ||
Returns: | ||
None or httpx.Auth: None if no authentication is needed, an httpx.Auth object otherwise. | ||
""" | ||
return None | ||
|
||
def get_default_headers(self): | ||
""" | ||
Get the default HTTP headers to include in each request. | ||
Returns: | ||
dict: A dictionary containing default headers. | ||
""" | ||
return {"Content-Type": "application/json", "Accept": "application/json"} | ||
|
||
async def request(self, *args, **kwargs): | ||
""" | ||
Make an asynchronous HTTP request to the API. | ||
Args: | ||
*args: Positional arguments for the request method. | ||
**kwargs: Keyword arguments for the request. | ||
Returns: | ||
AsyncReturnValue: The HTTP response object. | ||
""" | ||
headers = kwargs.get("headers", {}) | ||
|
||
if "url" in kwargs: | ||
kwargs["url"] = self.baseUrl + kwargs["url"] | ||
|
||
updated_headers = self.get_default_headers() | ||
updated_headers.update(headers) | ||
kwargs["headers"] = updated_headers | ||
|
||
auth = self.get_auth() | ||
if auth is not None: | ||
kwargs["auth"] = auth | ||
|
||
await AsyncBaseRESTHttpClient.setup() | ||
client = await AsyncHttpTransport.get_client() | ||
retries = AsyncBaseRESTHttpClient._max_retries | ||
backoff_factor = AsyncBaseRESTHttpClient._backoff_factor | ||
|
||
for attempt in range(retries + 1): | ||
try: | ||
response = await client.request(*args, auth=auth, **kwargs) | ||
if response.status_code not in AsyncBaseRESTHttpClient._status_forcelist: | ||
return AsyncReturnValue(response) | ||
except httpx.RequestError as exc: | ||
if attempt == retries: | ||
# Log error and raise exception after max retries | ||
logger.error(f"Max retries reached without a successful response getting this exception: {exc}") | ||
raise httpx.RequestError( | ||
f"Max retries reached without a successful response getting this exception: {exc}") | ||
# Wait before retrying | ||
await asyncio.sleep(backoff_factor * (2 ** attempt)) | ||
|
||
async def get(self, *args, **kwargs): | ||
return await self.request(method='GET', *args, **kwargs) | ||
|
||
async def post(self, *args, **kwargs): | ||
return await self.request(method='POST', *args, **kwargs) | ||
|
||
async def put(self, *args, **kwargs): | ||
return await self.request(method='PUT', *args, **kwargs) | ||
|
||
async def delete(self, *args, **kwargs): | ||
return await self.request(method='DELETE', *args, **kwargs) | ||
|
||
async def patch(self, *args, **kwargs): | ||
return await self.request(method='PATCH', *args, **kwargs) | ||
|
||
|
||
class AsyncReturnValue: | ||
def __init__(self, response: httpx.Response): | ||
self.status_code = response.status_code | ||
self.text = response.text | ||
|
||
def json(self): | ||
return json.loads(self.text) | ||
|
||
def __str__(self): | ||
return f"Response return value: (status_code={self.status_code}, text={self.text})" |
Oops, something went wrong.