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

chore: created http client, serialiser #1089

Merged
merged 12 commits into from
Oct 3, 2024
7 changes: 6 additions & 1 deletion .github/workflows/test-and-deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ jobs:
timeout-minutes: 20
strategy:
matrix:
python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9', '3.10', '3.11' ]
python-version: ['3.8', '3.9', '3.10', '3.11' ]
env:
DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }}
steps:
Expand All @@ -31,6 +31,11 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_AUTH_TOKEN }}

- name: Install Docker Compose
run: |
sudo apt-get update
sudo apt-get install -y docker-compose

- name: Build & Test
run: make test-docker version=${{ matrix.python-version }}

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ example.pdf
TODO.txt
twilio.env
prism*
**/.openapi-generator*
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
MIT License

Copyright (C) 2023, Twilio SendGrid, Inc. <[email protected]>
Copyright (C) 2024, Twilio SendGrid, Inc. <[email protected]>

Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
Expand Down
6 changes: 5 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
Flask==1.1.2
#Flask==1.1.2
requests>=2.31.0
#aiohttp>=3.9.4
#aiohttp-retry>=2.8.3

PyYAML>=4.2b1
python-http-client>=3.2.1
six==1.11.0
Expand Down
1 change: 1 addition & 0 deletions sendgrid/base/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# __init__.py
12 changes: 12 additions & 0 deletions sendgrid/base/auth_strategy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Handle different of authentications, Currently sendgrid authenticate using apikey.
# class AuthStrategy:
# def authenticate(self):
# print('Not yet implemented')
#
#
# class ApiKeyAuthStrategy(AuthStrategy):
# def __init__(self, api_key):
# self.api_key = api_key
# print('init ApiKeyAuthStrategy')
# def authenticate(self, api_key):
# print(f"Authenticating {api_key} using Token Authentication.")
7 changes: 7 additions & 0 deletions sendgrid/base/client_base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class ClientBase:

def __init__(self):
print("Creating ClientBase class")

def request(self):
print("Making request")
16 changes: 16 additions & 0 deletions sendgrid/base/url_builder.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
def build_url(url: str, region: str) -> str:
base_url = "https://api.sendgrid.com"

if region and isinstance(region, str):
new_url = f"https://api.{region}.sendgrid.com"
else:
new_url = base_url

# Ensure that there's a '/' before appending the url
if not new_url.endswith('/'):
new_url += '/'

new_url += url.lstrip('/')

return new_url

13 changes: 13 additions & 0 deletions sendgrid/base/values.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from typing import Dict

unset = object()


def of(d: Dict[str, object]) -> Dict[str, object]:
"""
Remove unset values from a dict.

:param d: A dict to strip.
:return A dict with unset values removed.
"""
return {k: v for k, v in d.items() if v != unset}
47 changes: 47 additions & 0 deletions sendgrid/client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from typing import List, Optional
from sendgrid.http.http_client import SendgridHttpClient, HttpClient
from sendgrid.http.request import Request
from sendgrid.base.url_builder import build_url

# class AuthStrategy:
# def authenticate(self):
# pass
#
#
# class ApiKeyAuthStrategy(AuthStrategy):
# def __init__(self, api_key):
# self.api_key = api_key
#
# def authenticate(
# self,
# headers: Optional[Dict[str, str]] = None
# ):
# headers["Authorization"] = f"Bearer {self.api_key}"
#


class Client:
def __init__(
self,
api_key: str,
region: Optional[str] = None,
edge: Optional[str] = None,
http_client: Optional[HttpClient] = None,
user_agent_extensions: Optional[List[str]] = None,
):
self.api_key = api_key
self.region = region
self.edge = edge
self.user_agent_extensions = user_agent_extensions or []
self.http_client: SendgridHttpClient = SendgridHttpClient()

def send(self, request: Request):
url = build_url(request.url, self.region)
response = self.http_client.request(
method=request.method,
url=url,
data=request.data,
headers=request.headers,
api_key=self.api_key,
)
return response
Empty file added sendgrid/converters/__init__.py
Empty file.
43 changes: 43 additions & 0 deletions sendgrid/converters/serialize.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
from enum import Enum

from enum import Enum


def to_serializable(obj):
if isinstance(obj, list):
return [
to_serializable(item) for item in obj if item is not None
] # Remove None from lists
elif isinstance(obj, dict):
return {
key: to_serializable(value)
for key, value in obj.items()
if value is not None
} # Remove None from dicts
elif hasattr(obj, "to_dict"):
return obj.to_dict()
elif isinstance(obj, Enum):
return obj.value
else:
return obj


def from_serializable(data, cls=None):
"""
Converts a dictionary or list into a class instance or a list of instances.
If `cls` is provided, it will instantiate the class using the dictionary values.
"""
if isinstance(data, list):
return [
from_serializable(item, cls) for item in data
] # Recursively handle lists
elif isinstance(data, dict):
if cls:
# If a class is provided, instantiate it using the dictionary
return cls(**{key: from_serializable(value) for key, value in data.items()})
else:
return {
key: from_serializable(value) for key, value in data.items()
} # Recursively handle dicts
else:
return data # Return primitive types as is
15 changes: 15 additions & 0 deletions sendgrid/exceptions/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from typing import Any, Dict


class SendgridException(Exception):
pass


class ApiException(SendgridException):
def __init__(self, status_code: int, error: Any, headers: Dict[str, Any] = None):
self.status_code = status_code
self.error = error
self.headers = headers or {}

def __str__(self):
return f"ApiException(status_code={self.status_code}, error={self.error}, headers={self.headers})"
1 change: 1 addition & 0 deletions sendgrid/http/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@

Loading
Loading