diff --git a/.github/workflows/release_stage.yml b/.github/workflows/release_stage.yml new file mode 100644 index 0000000..8408f3e --- /dev/null +++ b/.github/workflows/release_stage.yml @@ -0,0 +1,64 @@ +name: Release Stage Build + +on: + push: + branches: + - development + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: 3.11 + + - name: Install dependencies + run: sudo apt-get install make + + - name: Create virtual environment + run: make venv + + - name: Build package + run: | + set -x + source venv/bin/activate + rm -rf build dist *.egg-info + make build ENV=stage + + - name: Extract Version from pyproject.toml + id: get_version + run: | + # Extract the version assuming a line like: version = "0.1.0" + VERSION=$(grep -Po '^version\s*=\s*"\K[^"]+' pyproject.toml) + echo "Version extracted: $VERSION" + echo "version=$VERSION" >> $GITHUB_OUTPUT + + - name: Create GitHub Release + id: create_release + uses: actions/create-release@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + tag_name: v${{ steps.get_version.outputs.version }} + release_name: Release ${{ steps.get_version.outputs.version }} + draft: false + prerelease: false + + - name: Upload Wheel Asset + uses: actions/upload-release-asset@v1 + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + upload_url: ${{ steps.create_release.outputs.upload_url }} + asset_path: ./dist/extend-${{ steps.get_version.outputs.version }}-py3-none-any.whl + asset_name: extend-${{ steps.get_version.outputs.version }}-py3-none-any.whl + asset_content_type: application/octet-stream diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..34f60df --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +export ENV ?= stage +VENV_NAME ?= venv +PIP ?= pip +PYTHON ?= python3.11 + +venv: $(VENV_NAME)/bin/activate + +$(VENV_NAME)/bin/activate: pyproject.toml + @test -d $(VENV_NAME) || $(PYTHON) -m venv $(VENV_NAME) + $(VENV_NAME)/bin/python -m pip install -e . + @touch $(VENV_NAME)/bin/activate + +test: venv + $(VENV_NAME)/bin/python -m unittest discover tests + +build: venv + cp LICENSE LICENSE.bak + $(VENV_NAME)/bin/python -m build + rm LICENSE.bak diff --git a/README.md b/README.md index d6e791c..f8ee7d5 100644 --- a/README.md +++ b/README.md @@ -38,23 +38,23 @@ pip install -e . ```python import asyncio -from extend import ExtendAPI +from extend import ExtendClient async def main(): # Initialize the client - client = ExtendAPI( + client = ExtendClient( api_key="your-api-key", api_secret="your-api-secret" ) # Get all virtual cards - cards = await client.get_virtual_cards() - print("Virtual Cards:", cards) + response = await client.virtual_cards.get_virtual_cards() + print("Virtual Cards:", response["virtualCards"]) # Get all transactions - transactions = await client.get_transactions() - print("Transactions:", transactions) + response = await client.transactions.get_transactions() + print("Transactions:", response["transactions"]) # Run the async function diff --git a/extend/client.py b/extend/client.py index 02beb53..42dcf33 100644 --- a/extend/client.py +++ b/extend/client.py @@ -3,6 +3,8 @@ import httpx +from .config import API_HOST, API_VERSION + class APIClient: """Client for interacting with the Extend API. @@ -17,8 +19,6 @@ class APIClient: cards = await client.get_virtual_cards() ``` """ - BASE_URL = "https://apiv2.paywithextend.com" - API_VERSION = "application/vnd.paywithextend.v2021-03-12+json" _shared_instance: Optional["APIClient"] = None @@ -33,7 +33,7 @@ def __init__(self, api_key: str, api_secret: str): self.headers = { "x-extend-api-key": api_key, "Authorization": f"Basic {auth_value}", - "Accept": self.API_VERSION + "Accept": API_VERSION } @classmethod @@ -70,7 +70,8 @@ async def get(self, url: str, params: Optional[Dict] = None) -> Any: response = await client.get( self.build_full_url(url), headers=self.headers, - params=params + params=params, + timeout=httpx.Timeout(30) ) response.raise_for_status() return response.json() @@ -79,7 +80,7 @@ async def post(self, url: str, data: Dict) -> Any: """Make a POST request to the Extend API. Args: - url (str): The API endpoint path (e.g., "virtualcards") + url (str): The API endpoint path (e.g., "/virtualcards") data (Dict): The JSON payload to send in the request body Returns: @@ -93,7 +94,8 @@ async def post(self, url: str, data: Dict) -> Any: response = await client.post( self.build_full_url(url), headers=self.headers, - json=data + json=data, + timeout=httpx.Timeout(30) ) response.raise_for_status() return response.json() @@ -102,7 +104,7 @@ async def put(self, url: str, data: Dict) -> Any: """Make a PUT request to the Extend API. Args: - url (str): The API endpoint path (e.g., "virtualcards/{card_id}") + url (str): The API endpoint path (e.g., "/virtualcards/{card_id}") data (Dict): The JSON payload to send in the request body Returns: @@ -116,10 +118,11 @@ async def put(self, url: str, data: Dict) -> Any: response = await client.put( self.build_full_url(url), headers=self.headers, - json=data + json=data, + timeout=httpx.Timeout(30) ) response.raise_for_status() return response.json() def build_full_url(self, url: Optional[str]): - return f"{self.BASE_URL}{url or ''}" + return f"https://{API_HOST}{url or ''}" diff --git a/extend/config/__init__.py b/extend/config/__init__.py new file mode 100644 index 0000000..96a52f0 --- /dev/null +++ b/extend/config/__init__.py @@ -0,0 +1,19 @@ +import os + +from dotenv import load_dotenv + +load_dotenv() + +env = os.getenv("ENV", "dev") + +if env == "stage": + from .config_stage import API_HOST, API_VERSION +elif env == "prod": + from .config_prod import API_HOST, API_VERSION +else: + from .config_stage import API_HOST, API_VERSION + +__all__ = [ + "API_HOST", + "API_VERSION" +] diff --git a/extend/config/config_prod.py b/extend/config/config_prod.py new file mode 100644 index 0000000..ab351fe --- /dev/null +++ b/extend/config/config_prod.py @@ -0,0 +1,2 @@ +API_HOST = "apiv2.paywithextend.com" +API_VERSION = "application/vnd.paywithextend.v2021-03-12+json" diff --git a/extend/config/config_stage.py b/extend/config/config_stage.py new file mode 100644 index 0000000..292d408 --- /dev/null +++ b/extend/config/config_stage.py @@ -0,0 +1,2 @@ +API_HOST = "apiv2-stage.paywithextend.com" +API_VERSION = "application/vnd.paywithextend.v2021-03-12+json" diff --git a/pyproject.toml b/pyproject.toml index 6b0c738..e21c972 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -16,6 +16,7 @@ classifiers = [ ] requires-python = ">=3.8" dependencies = [ + "build", "httpx>=0.24.0", "typing-extensions>=4.0.0", "python-dotenv==1.0.1", @@ -41,7 +42,7 @@ dev = [ "jupyter>=1.0.0", "ipykernel>=6.0.0", "notebook>=7.0.0", - "pytest-mock==3.14.0" + "pytest-mock==3.14.0", ] [tool.pytest.ini_options] diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index d172405..0000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,9 +0,0 @@ -pytest>=7.0 -pytest-asyncio>=0.21.0 -pytest-cov>=4.0 -black>=23.0 -isort>=5.0 -jupyter>=1.0.0 -ipykernel>=6.0.0 -notebook>=7.0.0 -pytest-mock==3.14.0 diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 188e220..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,0 @@ -httpx>=0.24.0 -typing-extensions>=4.0.0 -python-dotenv==1.0.1 \ No newline at end of file diff --git a/tests/test_integration.py b/tests/test_integration.py index 11f7d60..fadcbbe 100644 --- a/tests/test_integration.py +++ b/tests/test_integration.py @@ -1,7 +1,6 @@ import os from datetime import datetime, timedelta -import httpx import pytest from dotenv import load_dotenv @@ -26,7 +25,6 @@ def extend(): """Create a real API client for integration testing""" api_key = os.getenv("EXTEND_API_KEY") api_secret = os.getenv("EXTEND_API_SECRET") - timeout = httpx.Timeout(30.0) return ExtendClient(api_key, api_secret)