Compare commits

...

15 Commits

Author SHA1 Message Date
52307f6028 fix: fixed deprecation warning 2025-01-21 22:17:08 +01:00
8019b566f2 fix: added string method for base model 2025-01-21 22:16:07 +01:00
83726f517c feat: added stripe service 2025-01-21 22:12:03 +01:00
abe84bcfcb Merge pull request #22 from creyD/dev
Major Version 3.0.0
2025-01-21 12:15:43 +01:00
vikynoah
2d6de99585 fix: post_file method change for testing (#29)
* fix: post_file method change for testing

* changes
2025-01-16 09:35:23 +01:00
vikynoah
573f59349f fix: changes to post method in testing_async (#28) 2025-01-08 19:37:10 +01:00
creyD
32bf089456 Adjusted files for isort & autopep 2025-01-02 22:20:49 +00:00
vikynoah
d75fede3d1 fix: Force postgresql SSL mode (#27)
* fix: Force postgresql SSL mode

* changes
2025-01-02 23:20:17 +01:00
creyD
f8b781b3e7 Adjusted files for isort & autopep 2024-12-11 16:15:33 +00:00
vikynoah
93c7f6f6cb fix: Async Testing (#26)
* fix: httpx fix as per latest version

* fix: Fix Async Testing client
2024-12-11 17:14:59 +01:00
creyD
2e44453915 Adjusted files for isort & autopep 2024-12-09 15:29:15 +00:00
vikynoah
2a22471de9 fix: httpx fix as per latest version (#25) 2024-12-09 16:28:44 +01:00
2176b1a37d fix: bumped security risks and enabled newer packages installed 2024-12-04 20:05:19 +01:00
5daddf260e fix: added timeouts to the requests to fix Bandit issue 2024-11-25 13:20:17 +01:00
364e07daa1 fix: fixed random issue (codacy) 2024-11-25 13:14:07 +01:00
17 changed files with 101 additions and 30 deletions

View File

@@ -5,7 +5,9 @@ from sqlalchemy.orm import sessionmaker
from .common import SQLALCHEMY_DATABASE_URL, name
async_engine = create_async_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True)
async_engine = create_async_engine(
SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, connect_args={"sslmode": "require"}
)
AsyncSessionLocal = sessionmaker(
bind=async_engine,

View File

@@ -6,7 +6,9 @@ from sqlalchemy.orm.session import Session
from .common import SQLALCHEMY_DATABASE_URL, name
engine = create_engine(SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True)
engine = create_engine(
SQLALCHEMY_DATABASE_URL + name, pool_pre_ping=True, connect_args={"sslmode": "require"}
)
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

@@ -1,5 +1,5 @@
import uuid
from datetime import datetime
from datetime import datetime, timezone
from sqlalchemy import Column, DateTime, String
from sqlalchemy.dialects.postgresql import UUID
@@ -14,15 +14,30 @@ class Base:
# Primary key as uuid
id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
created_at = Column(DateTime(timezone=True), server_default=func.now())
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
updated_at = Column(
DateTime(timezone=True),
default=lambda: datetime.now(timezone.utc),
onupdate=lambda: datetime.now(timezone.utc),
)
created_by_id = Column(String)
__name__: str
# TODO: Add default representation string
# TODO: Add automated foreign key resolution
# Generate __tablename__ automatically
@declared_attr
def __tablename__(cls) -> str:
return cls.__name__.lower()
def __str__(self) -> str:
# if the object has a name, title or similar attribute, return it
if hasattr(self, "name"):
return str(self.name) # type: ignore
# if the object has a title attribute, return it
if hasattr(self, "title"):
return str(self.title) # type: ignore
# otherwise return the object's id
return str(self.id)

View File

@@ -41,7 +41,7 @@ class GenericClient(TestClient):
re = self.c.post(
url,
files={"file": file},
headers=self.default_headers | {"Content-Type": "application/json"},
headers=self.default_headers,
*args,
**kwargs,
)

View File

@@ -1,11 +1,14 @@
import json
from httpx import AsyncClient
from httpx import ASGITransport, AsyncClient
class AsyncGenericClient:
def __init__(self, app):
self.c = AsyncClient(app=app, base_url="http://testserver", follow_redirects=True)
self.default_headers = {}
def __init__(self, app, headers={}):
self.c = AsyncClient(
transport=ASGITransport(app=app), base_url="http://testserver", follow_redirects=True
)
self.default_headers = headers
async def get(self, url: str, r_code: int = 200, parse_json=True):
re = await self.c.get(url, headers=self.default_headers)
@@ -33,7 +36,8 @@ class AsyncGenericClient:
)
if re.status_code != r_code:
print(re.content)
assert r_code == re.status_code
if not raw_response:
assert r_code == re.status_code
return re.json() if not raw_response else re
async def post_file(
@@ -42,7 +46,7 @@ class AsyncGenericClient:
re = await self.c.post(
url,
files={"file": file},
headers=self.default_headers | {"Content-Type": "application/json"},
headers=self.default_headers,
*args,
**kwargs,
)

View File

@@ -1,4 +1,4 @@
import random
import secrets
import string
@@ -6,11 +6,11 @@ def create_random_password(length: int = 12) -> str:
all_characters = string.ascii_letters + string.digits + string.punctuation
password = [
random.choice(string.ascii_lowercase),
random.choice(string.ascii_uppercase),
random.choice(string.digits),
random.choice(string.punctuation),
secrets.choice(string.ascii_lowercase),
secrets.choice(string.ascii_uppercase),
secrets.choice(string.digits),
secrets.choice(string.punctuation),
]
password += random.choices(all_characters, k=length - 4)
random.shuffle(password)
password += [secrets.choice(all_characters) for _ in range(length - 4)]
secrets.SystemRandom().shuffle(password)
return "".join(password)

View File

@@ -1 +1,2 @@
from .auth0 import * # noqa
from .stripe import * # noqa

View File

@@ -1,3 +1,4 @@
from .exceptions import * # noqa
from .manage import * # noqa
from .testing import * # noqa
from .utils import * # noqa

View File

@@ -8,7 +8,7 @@ cache = TTLCache(maxsize=100, ttl=600)
@cached(cache)
def get_management_token() -> str:
re = requests.post(
response = requests.post(
f"https://{AUTH0_DOMAIN}/oauth/token",
json={
"client_id": AUTH0_CLIENT_ID,
@@ -16,5 +16,6 @@ def get_management_token() -> str:
"audience": f"https://{AUTH0_DOMAIN}/api/v2/", # This should be the management audience
"grant_type": "client_credentials",
},
timeout=5, # Add a timeout parameter to avoid hanging requests
).json()
return re["access_token"]
return response["access_token"]

View File

@@ -54,6 +54,7 @@ def get_user(sub) -> dict:
re = requests.get(
f"https://{AUTH0_DOMAIN}/api/v2/users/{sub}",
headers={"Authorization": f"Bearer {get_management_token()}"},
timeout=5,
)
if re.status_code != 200:
raise HTTPException(re.status_code, re.json())
@@ -65,6 +66,7 @@ def patch_user(input_obj: dict, sub) -> dict:
f"https://{AUTH0_DOMAIN}/api/v2/users/{sub}",
headers={"Authorization": f"Bearer {get_management_token()}"},
json=input_obj,
timeout=5,
)
if re.status_code != 200:
raise HTTPException(re.status_code, re.json())
@@ -92,6 +94,7 @@ def request_verification_mail(sub: str) -> None:
f"https://{AUTH0_DOMAIN}/api/v2/jobs/verification-email",
headers={"Authorization": f"Bearer {get_management_token()}"},
json={"user_id": sub},
timeout=5,
)
if re.status_code != 201:
raise HTTPException(re.status_code, re.json())
@@ -109,6 +112,7 @@ def create_user_invite(email: str) -> dict:
"verify_email": False,
"app_metadata": {"invitedToMyApp": True},
},
timeout=5,
)
if re.status_code != 201:
raise HTTPException(re.status_code, re.json())
@@ -124,6 +128,7 @@ def password_change_mail(email: str) -> bool:
"email": email,
"connection": "Username-Password-Authentication",
},
timeout=5,
)
if re.status_code != 200:

View File

@@ -0,0 +1 @@
from .testing import * # noqa

View File

@@ -0,0 +1,23 @@
class ItemReturn:
quantity = 1
class SubscriptionItem:
def retrieve(self, id: str = ""):
return ItemReturn
def modify(self, id: str, quantity: int):
return ItemReturn
class StripeAPI:
def __init__(self, key: str):
pass
@property
def SubscriptionItem(self):
return SubscriptionItem
def get_stripe_api():
return StripeAPI("test")

View File

@@ -0,0 +1,11 @@
import os
import stripe
from dotenv import load_dotenv
load_dotenv()
def get_stripe_api():
stripe.api_key = os.getenv("STRIPE_API_KEY", "")
return stripe

View File

@@ -1,7 +1,7 @@
cachetools==5.5.0 # for caching
charset-normalizer==3.4.0 # Auth0 API interactions
requests==2.32.3 # Auth0 API interactions
pyjwt==2.10.0 # Auth0 API interactions
cffi==1.17.1 # Auth0 API interactions
cryptography==43.0.3 # Auth0 API interactions
pycparser==2.22 # Auth0 API interactions
cachetools>=5.5.0 # for caching
charset-normalizer>=3.4.0 # Auth0 API interactions
requests>=2.32.3 # Auth0 API interactions
pyjwt>=2.10.1 # Auth0 API interactions
cffi>=1.17.1 # Auth0 API interactions
cryptography>=43.0.3 # Auth0 API interactions
pycparser>=2.22 # Auth0 API interactions

1
requirements.stripe.txt Normal file
View File

@@ -0,0 +1 @@
stripe==10.12.0 # Stripe

View File

@@ -11,7 +11,7 @@ starlette>=0.37.2 # FastAPI
fastapi-pagination>=0.12.26 # Pagination
sqlalchemy>=2.0.31 # SQLAlchemy
sqlalchemy-utils==0.41.2 # For managing databases
sqlalchemy-utils>=0.41.2 # For managing databases
python-dotenv>=1.0.1 # Environment variables

View File

@@ -14,6 +14,9 @@ with open("requirements.pg.txt") as f:
with open("requirements.auth0.txt") as f:
auth0_requirements = f.read().splitlines()
with open("requirements.stripe.txt") as f:
stripe_requirements = f.read().splitlines()
def get_latest_git_tag() -> str:
try:
@@ -46,7 +49,8 @@ setup(
"build": build_requirements,
"postgres": pg_requirements,
"auth0": auth0_requirements,
"all": build_requirements + pg_requirements + auth0_requirements,
"stripe": stripe_requirements,
"all": build_requirements + pg_requirements + auth0_requirements + stripe_requirements,
},
keywords=[
"creyPY",