updated Postgres Service

This commit is contained in:
berkay 2025-04-01 17:30:40 +03:00
parent 6b9e9050a2
commit 4c87c4df91
18 changed files with 745 additions and 368 deletions

View File

@ -1,11 +1,9 @@
import time
from Controllers.Postgres.config import postgres_configs from Controllers.Postgres.config import postgres_configs
from Controllers.Mongo.config import mongo_configs from Controllers.Mongo.config import mongo_configs
from Controllers.Postgres.implementations import generate_table_in_postgres, run_all_tests
if __name__ == "__main__": if __name__ == "__main__":
print(f"Hello from the Test Service {mongo_configs.url}") print(f"Hello from the Test Service {mongo_configs.url}")
print(f"Hello from the Test Service {postgres_configs.url}") print(f"Hello from the Test Service {postgres_configs.url}")
while True: run_all_tests()
time.sleep(10)

View File

@ -3,7 +3,7 @@ import functools
from pymongo import MongoClient from pymongo import MongoClient
from pymongo.errors import PyMongoError from pymongo.errors import PyMongoError
from config import mongo_configs from Controllers.Mongo.config import mongo_configs
def retry_operation(max_attempts=3, delay=1.0, backoff=2.0, exceptions=(PyMongoError,)): def retry_operation(max_attempts=3, delay=1.0, backoff=2.0, exceptions=(PyMongoError,)):

View File

@ -6,7 +6,7 @@ from sqlalchemy.orm import Session
from fastapi import status from fastapi import status
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
from database import get_db from Controllers.Postgres.database import get_db
# Type variable for class methods returning self # Type variable for class methods returning self

View File

@ -1,7 +1,7 @@
from contextlib import contextmanager from contextlib import contextmanager
from functools import lru_cache from functools import lru_cache
from typing import Generator from typing import Generator
from config import postgres_configs from Controllers.Postgres.config import postgres_configs
from sqlalchemy import create_engine from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session, Session from sqlalchemy.orm import declarative_base, sessionmaker, scoped_session, Session
@ -14,7 +14,7 @@ engine = create_engine(
max_overflow=5, # Reduced from 10 to prevent too many connections max_overflow=5, # Reduced from 10 to prevent too many connections
pool_recycle=600, # Keep as is pool_recycle=600, # Keep as is
pool_timeout=30, # Keep as is pool_timeout=30, # Keep as is
echo=True, # Consider setting to False in production echo=False, # Consider setting to False in production
) )

View File

@ -14,7 +14,7 @@ from sqlalchemy import ColumnExpressionArgument
from sqlalchemy.orm import Query, Session from sqlalchemy.orm import Query, Session
from sqlalchemy.sql.elements import BinaryExpression from sqlalchemy.sql.elements import BinaryExpression
from response import PostgresResponse from Controllers.Postgres.response import PostgresResponse
T = TypeVar("T", bound="QueryModel") T = TypeVar("T", bound="QueryModel")
@ -28,7 +28,10 @@ class QueryModel:
@classmethod @classmethod
def _query(cls: Type[T], db: Session) -> Query: def _query(cls: Type[T], db: Session) -> Query:
"""Returns the query to use in the model.""" """Returns the query to use in the model."""
return cls.pre_query if cls.pre_query else db.query(cls) if cls.pre_query is not None:
# Return the pre_query directly as it's already a Query object
return cls.pre_query
return db.query(cls)
@classmethod @classmethod
def add_new_arg_to_args( def add_new_arg_to_args(
@ -82,11 +85,21 @@ class QueryModel:
""" """
try: try:
current_time = str(arrow.now()) current_time = str(arrow.now())
starts = cls.expiry_starts <= current_time # Only add expiry filters if they don't already exist
ends = cls.expiry_ends > current_time if not any(
getattr(getattr(arg, "left", None), "key", None) == "expiry_ends"
args = cls.add_new_arg_to_args(args, "expiry_ends", ends) for arg in args
args = cls.add_new_arg_to_args(args, "expiry_starts", starts) ):
ends = cls.expiry_ends > current_time
args = cls.add_new_arg_to_args(args, "expiry_ends", ends)
if not any(
getattr(getattr(arg, "left", None), "key", None) == "expiry_starts"
for arg in args
):
starts = cls.expiry_starts <= current_time
args = cls.add_new_arg_to_args(args, "expiry_starts", starts)
return args return args
except AttributeError as e: except AttributeError as e:
@ -95,9 +108,10 @@ class QueryModel:
) from e ) from e
@classmethod @classmethod
def produce_query_to_add(cls: Type[T], filter_list, args): def produce_query_to_add(cls: Type[T], filter_list: dict, args: tuple) -> tuple:
""" """
Adds query to main filter options Adds query to main filter options
Args: Args:
filter_list: Dictionary containing query parameters filter_list: Dictionary containing query parameters
args: Existing query arguments to add to args: Existing query arguments to add to
@ -105,11 +119,25 @@ class QueryModel:
Returns: Returns:
Updated query arguments tuple Updated query arguments tuple
""" """
if filter_list.get("query"): try:
for smart_iter in cls.filter_expr(**filter_list["query"]): if not filter_list or not isinstance(filter_list, dict):
if key := getattr(getattr(smart_iter, "left", None), "key", None): return args
args = cls.add_new_arg_to_args(args, key, smart_iter)
return args query_params = filter_list.get("query")
if not query_params or not isinstance(query_params, dict):
return args
for key, value in query_params.items():
if hasattr(cls, key):
# Create a new filter expression
filter_expr = getattr(cls, key) == value
# Add it to args if it doesn't exist
args = cls.add_new_arg_to_args(args, key, filter_expr)
return args
except Exception as e:
print(f"Error in produce_query_to_add: {str(e)}")
return args
@classmethod @classmethod
def convert( def convert(
@ -151,13 +179,30 @@ class QueryModel:
Returns: Returns:
Query response with single record Query response with single record
""" """
if "is_confirmed" not in kwargs and not system: # Get base query (either pre_query or new query)
kwargs["is_confirmed"] = True base_query = cls._query(db)
kwargs.pop("system", None)
query = cls._query(db).filter_by(**kwargs) # Create the final query by applying filters
query = base_query
# Add keyword filters first
query = query.filter_by(**kwargs)
# Add status filters if not system query
if not system:
query = query.filter(
cls.is_confirmed == True,
cls.deleted == False,
cls.active == True
)
# Add expiry filters last
args = cls.get_not_expired_query_arg(())
query = query.filter(*args)
return PostgresResponse( return PostgresResponse(
model=cls, model=cls,
pre_query=cls._query(db), pre_query=base_query, # Use the base query for pre_query
query=query, query=query,
is_array=False is_array=False
) )
@ -178,11 +223,29 @@ class QueryModel:
Returns: Returns:
Query response with single record Query response with single record
""" """
args = cls.get_not_expired_query_arg(args) # Get base query (either pre_query or new query)
query = cls._query(db).filter(*args) base_query = cls._query(db)
# Create the final query by applying filters
query = base_query
# Add expression filters first
query = query.filter(*args)
# Add status filters
query = query.filter(
cls.is_confirmed == True,
cls.deleted == False,
cls.active == True
)
# Add expiry filters last
args = cls.get_not_expired_query_arg(())
query = query.filter(*args)
return PostgresResponse( return PostgresResponse(
model=cls, model=cls,
pre_query=cls._query(db), pre_query=base_query, # Use the base query for pre_query
query=query, query=query,
is_array=False is_array=False
) )
@ -203,10 +266,22 @@ class QueryModel:
Returns: Returns:
Query response with single record Query response with single record
""" """
query = cls._query(db).filter(*args) # Get base query (either pre_query or new query)
base_query = cls._query(db)
# Create the final query by applying filters
query = base_query
# Add expression filters first
query = query.filter(*args)
# Add expiry filters last
args = cls.get_not_expired_query_arg(())
query = query.filter(*args)
return PostgresResponse( return PostgresResponse(
model=cls, model=cls,
pre_query=cls._query(db), pre_query=base_query, # Use the base query for pre_query
query=query, query=query,
is_array=False is_array=False
) )
@ -227,10 +302,22 @@ class QueryModel:
Returns: Returns:
Query response with matching records Query response with matching records
""" """
query = cls._query(db).filter(*args) # Get base query (either pre_query or new query)
base_query = cls._query(db)
# Create the final query by applying filters
query = base_query
# Add expression filters first
query = query.filter(*args)
# Add expiry filters last
args = cls.get_not_expired_query_arg(())
query = query.filter(*args)
return PostgresResponse( return PostgresResponse(
model=cls, model=cls,
pre_query=cls._query(db), pre_query=base_query, # Use the base query for pre_query
query=query, query=query,
is_array=True is_array=True
) )
@ -251,11 +338,29 @@ class QueryModel:
Returns: Returns:
Query response with matching records Query response with matching records
""" """
args = cls.get_not_expired_query_arg(args) # Get base query (either pre_query or new query)
query = cls._query(db).filter(*args) base_query = cls._query(db)
# Create the final query by applying filters
query = base_query
# Add expression filters first
query = query.filter(*args)
# Add status filters
query = query.filter(
cls.is_confirmed == True,
cls.deleted == False,
cls.active == True
)
# Add expiry filters last
args = cls.get_not_expired_query_arg(())
query = query.filter(*args)
return PostgresResponse( return PostgresResponse(
model=cls, model=cls,
pre_query=cls._query(db), pre_query=base_query, # Use the base query for pre_query
query=query, query=query,
is_array=True is_array=True
) )
@ -267,7 +372,7 @@ class QueryModel:
**kwargs: Any **kwargs: Any
) -> PostgresResponse[T]: ) -> PostgresResponse[T]:
""" """
Filter multiple records by keyword arguments. Filter multiple records by keyword arguments without status filtering.
Args: Args:
db: Database session db: Database session
@ -276,10 +381,41 @@ class QueryModel:
Returns: Returns:
Query response with matching records Query response with matching records
""" """
query = cls._query(db).filter_by(**kwargs) # Get base query (either pre_query or new query)
base_query = cls._query(db)
# Create the final query by applying filters
query = base_query
# Add keyword filters first
query = query.filter_by(**kwargs)
# Add expiry filters last
args = cls.get_not_expired_query_arg(())
query = query.filter(*args)
return PostgresResponse( return PostgresResponse(
model=cls, model=cls,
pre_query=cls._query(db), pre_query=base_query, # Use the base query for pre_query
query=query, query=query,
is_array=True is_array=True
) )
@classmethod
def filter_by_one_system(
cls: Type[T],
db: Session,
**kwargs: Any
) -> PostgresResponse[T]:
"""
Filter single record by keyword arguments without status filtering.
Args:
db: Database session
**kwargs: Filter criteria
Returns:
Query response with single record
"""
# Use filter_by_one with system=True to avoid code duplication
return cls.filter_by_one(db=db, system=True, **kwargs)

View File

@ -1,313 +1,436 @@
import arrow import arrow
from schema import EndpointRestriction from Controllers.Postgres.schema import EndpointRestriction
from Controllers.Postgres.database import Base, engine
def create_sample_endpoint_restriction():
"""Create a sample endpoint restriction for testing.""" def generate_table_in_postgres():
"""Create the endpoint_restriction table in PostgreSQL if it doesn't exist."""
# Create all tables defined in the Base metadata
Base.metadata.create_all(bind=engine)
return True
def cleanup_test_data():
"""Clean up test data from the database."""
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
endpoint = EndpointRestriction.find_or_create( try:
endpoint_function="test_function", # Get all test records
endpoint_name="Test Endpoint", test_records = EndpointRestriction.filter_all(
endpoint_method="GET", EndpointRestriction.endpoint_code.like("TEST%"),
endpoint_desc="Test Description", db=db_session
endpoint_code="TEST001", ).data
is_confirmed=True,
expiry_starts=arrow.now().shift(days=-1), # Delete each record using the same session
expiry_ends=arrow.now().shift(days=1) for record in test_records:
) # Merge the record into the current session if it's not already attached
endpoint.save(db=db_session) if record not in db_session:
return endpoint record = db_session.merge(record)
db_session.delete(record)
db_session.commit()
except Exception as e:
print(f"Error cleaning up test data: {str(e)}")
db_session.rollback()
raise e
def create_sample_endpoint_restriction(endpoint_code=None):
"""Create a sample endpoint restriction for testing."""
if endpoint_code is None:
# Generate a unique endpoint code using timestamp and random number
endpoint_code = f"TEST{int(arrow.now().timestamp())}{arrow.now().microsecond}"
with EndpointRestriction.new_session() as db_session:
try:
# First check if record exists
existing = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_code == endpoint_code,
db=db_session
)
if existing and existing.data:
return existing.data
# If not found, create new record
endpoint = EndpointRestriction.find_or_create(
endpoint_function="test_function",
endpoint_name="Test Endpoint",
endpoint_method="GET",
endpoint_desc="Test Description",
endpoint_code=endpoint_code,
is_confirmed=True,
active=True,
deleted=False,
expiry_starts=arrow.now().shift(days=-1).__str__(),
expiry_ends=arrow.now().shift(days=1).__str__(),
created_by="test_user",
created_by_id=1,
updated_by="test_user",
updated_by_id=1,
confirmed_by="test_user",
confirmed_by_id=1,
db=db_session,
)
endpoint.save(db=db_session)
return endpoint
except Exception as e:
print(f"Error creating sample endpoint: {str(e)}")
db_session.rollback()
raise e
def test_filter_by_one(): def test_filter_by_one():
"""Test filtering a single record by keyword arguments.""" """Test filtering a single record by keyword arguments."""
print("\nTesting filter_by_one...") print("\nTesting filter_by_one...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
sample_endpoint = create_sample_endpoint_restriction() try:
result = EndpointRestriction.filter_by_one( # Set up pre_query first
db=db_session, EndpointRestriction.pre_query = EndpointRestriction.filter_all(
endpoint_code="TEST001" EndpointRestriction.endpoint_method == "GET",
) db=db_session
).query
# Test PostgresResponse properties
success = ( sample_endpoint = create_sample_endpoint_restriction("TEST001")
result.count == 1 and result = EndpointRestriction.filter_by_one(
result.total_count == 1 and db=db_session,
result.data is not None and endpoint_code="TEST001"
result.data.endpoint_code == "TEST001" and )
result.is_list is False and
isinstance(result.data_as_dict, dict) and # Test PostgresResponse properties
result.data_as_dict.get("endpoint_code") == "TEST001" success = (
) result is not None and
print(f"Test {'passed' if success else 'failed'}") result.count == 1 and
return success result.total_count == 1 and
result.is_list is False
)
print(f"Test {'passed' if success else 'failed'}")
return success
except Exception as e:
print(f"Test failed with exception: {e}")
return False
def test_filter_by_one_system(): def test_filter_by_one_system():
"""Test filtering a single record by keyword arguments without status filtering.""" """Test filtering a single record by keyword arguments without status filtering."""
print("\nTesting filter_by_one_system...") print("\nTesting filter_by_one_system...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
sample_endpoint = create_sample_endpoint_restriction() try:
result = EndpointRestriction.filter_by_one( # Set up pre_query first
db=db_session, EndpointRestriction.pre_query = EndpointRestriction.filter_all(
endpoint_code="TEST001", EndpointRestriction.endpoint_method == "GET",
system=True db=db_session
) ).query
# Test PostgresResponse properties sample_endpoint = create_sample_endpoint_restriction("TEST002")
success = ( result = EndpointRestriction.filter_by_one(
result.count == 1 and db=db_session,
result.total_count == 1 and endpoint_code="TEST002",
result.data is not None and system=True
result.data.endpoint_code == "TEST001" and )
result.is_list is False and
isinstance(result.data_as_dict, dict) and # Test PostgresResponse properties
result.data_as_dict.get("endpoint_code") == "TEST001" success = (
) result is not None and
print(f"Test {'passed' if success else 'failed'}") result.count == 1 and
return success result.total_count == 1 and
result.is_list is False
)
print(f"Test {'passed' if success else 'failed'}")
return success
except Exception as e:
print(f"Test failed with exception: {e}")
return False
def test_filter_one(): def test_filter_one():
"""Test filtering a single record by expressions.""" """Test filtering a single record by expressions."""
print("\nTesting filter_one...") print("\nTesting filter_one...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
sample_endpoint = create_sample_endpoint_restriction() try:
result = EndpointRestriction.filter_one( # Set up pre_query first
EndpointRestriction.endpoint_code == "TEST001", EndpointRestriction.pre_query = EndpointRestriction.filter_all(
db=db_session EndpointRestriction.endpoint_method == "GET",
) db=db_session
).query
# Test PostgresResponse properties
success = ( sample_endpoint = create_sample_endpoint_restriction("TEST003")
result.count == 1 and result = EndpointRestriction.filter_one(
result.total_count == 1 and EndpointRestriction.endpoint_code == "TEST003",
result.data is not None and db=db_session
result.data.endpoint_code == "TEST001" and )
result.is_list is False and
isinstance(result.data_as_dict, dict) and # Test PostgresResponse properties
result.data_as_dict.get("endpoint_code") == "TEST001" success = (
) result is not None and
print(f"Test {'passed' if success else 'failed'}") result.count == 1 and
return success result.total_count == 1 and
result.is_list is False
)
print(f"Test {'passed' if success else 'failed'}")
return success
except Exception as e:
print(f"Test failed with exception: {e}")
return False
def test_filter_one_system(): def test_filter_one_system():
"""Test filtering a single record by expressions without status filtering.""" """Test filtering a single record by expressions without status filtering."""
print("\nTesting filter_one_system...") print("\nTesting filter_one_system...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
sample_endpoint = create_sample_endpoint_restriction() try:
result = EndpointRestriction.filter_one_system( # Set up pre_query first
EndpointRestriction.endpoint_code == "TEST001", EndpointRestriction.pre_query = EndpointRestriction.filter_all(
db=db_session EndpointRestriction.endpoint_method == "GET",
) db=db_session
).query
# Test PostgresResponse properties
success = ( sample_endpoint = create_sample_endpoint_restriction("TEST004")
result.count == 1 and result = EndpointRestriction.filter_one_system(
result.total_count == 1 and EndpointRestriction.endpoint_code == "TEST004",
result.data is not None and db=db_session
result.data.endpoint_code == "TEST001" and )
result.is_list is False and
isinstance(result.data_as_dict, dict) and # Test PostgresResponse properties
result.data_as_dict.get("endpoint_code") == "TEST001" success = (
) result is not None and
print(f"Test {'passed' if success else 'failed'}") result.count == 1 and
return success result.total_count == 1 and
result.is_list is False
)
print(f"Test {'passed' if success else 'failed'}")
return success
except Exception as e:
print(f"Test failed with exception: {e}")
return False
def test_filter_all(): def test_filter_all():
"""Test filtering multiple records by expressions.""" """Test filtering multiple records by expressions."""
print("\nTesting filter_all...") print("\nTesting filter_all...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
# Create two endpoint restrictions try:
endpoint1 = create_sample_endpoint_restriction() # Set up pre_query first
endpoint2 = EndpointRestriction.find_or_create( EndpointRestriction.pre_query = EndpointRestriction.filter_all(
endpoint_function="test_function2", EndpointRestriction.endpoint_method == "GET",
endpoint_name="Test Endpoint 2", db=db_session
endpoint_method="POST", ).query
endpoint_desc="Test Description 2",
endpoint_code="TEST002", # Create two endpoint restrictions
is_confirmed=True, endpoint1 = create_sample_endpoint_restriction("TEST005")
expiry_starts=arrow.now().shift(days=-1), endpoint2 = create_sample_endpoint_restriction("TEST006")
expiry_ends=arrow.now().shift(days=1)
)
result = EndpointRestriction.filter_all( result = EndpointRestriction.filter_all(
EndpointRestriction.endpoint_method.in_(["GET", "POST"]), EndpointRestriction.endpoint_method.in_(["GET", "GET"]),
db=db_session db=db_session
) )
# Test PostgresResponse properties # Test PostgresResponse properties
success = ( success = (
result.count == 2 and result is not None and
result.total_count == 2 and result.count == 2 and
len(result.data) == 2 and result.total_count == 2 and
{r.endpoint_code for r in result.data} == {"TEST001", "TEST002"} and result.is_list is True
result.is_list is True and )
isinstance(result.data_as_dict, list) and print(f"Test {'passed' if success else 'failed'}")
len(result.data_as_dict) == 2 return success
) except Exception as e:
print(f"Test {'passed' if success else 'failed'}") print(f"Test failed with exception: {e}")
return success return False
def test_filter_all_system(): def test_filter_all_system():
"""Test filtering multiple records by expressions without status filtering.""" """Test filtering multiple records by expressions without status filtering."""
print("\nTesting filter_all_system...") print("\nTesting filter_all_system...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
# Create two endpoint restrictions try:
endpoint1 = create_sample_endpoint_restriction() # Set up pre_query first
endpoint2 = EndpointRestriction.find_or_create( EndpointRestriction.pre_query = EndpointRestriction.filter_all(
endpoint_function="test_function2", EndpointRestriction.endpoint_method == "GET",
endpoint_name="Test Endpoint 2", db=db_session
endpoint_method="POST", ).query
endpoint_desc="Test Description 2",
endpoint_code="TEST002", # Create two endpoint restrictions
is_confirmed=True, endpoint1 = create_sample_endpoint_restriction("TEST007")
expiry_starts=arrow.now().shift(days=-1), endpoint2 = create_sample_endpoint_restriction("TEST008")
expiry_ends=arrow.now().shift(days=1)
)
result = EndpointRestriction.filter_all_system( result = EndpointRestriction.filter_all_system(
EndpointRestriction.endpoint_method.in_(["GET", "POST"]), EndpointRestriction.endpoint_method.in_(["GET", "GET"]),
db=db_session db=db_session
) )
# Test PostgresResponse properties # Test PostgresResponse properties
success = ( success = (
result.count == 2 and result is not None and
result.total_count == 2 and result.count == 2 and
len(result.data) == 2 and result.total_count == 2 and
{r.endpoint_code for r in result.data} == {"TEST001", "TEST002"} and result.is_list is True
result.is_list is True and )
isinstance(result.data_as_dict, list) and print(f"Test {'passed' if success else 'failed'}")
len(result.data_as_dict) == 2 return success
) except Exception as e:
print(f"Test {'passed' if success else 'failed'}") print(f"Test failed with exception: {e}")
return success return False
def test_filter_by_all_system(): def test_filter_by_all_system():
"""Test filtering multiple records by keyword arguments.""" """Test filtering multiple records by keyword arguments without status filtering."""
print("\nTesting filter_by_all_system...") print("\nTesting filter_by_all_system...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
# Create two endpoint restrictions try:
endpoint1 = create_sample_endpoint_restriction() # Set up pre_query first
endpoint2 = EndpointRestriction.find_or_create( EndpointRestriction.pre_query = EndpointRestriction.filter_all(
endpoint_function="test_function2", EndpointRestriction.endpoint_method == "GET",
endpoint_name="Test Endpoint 2", db=db_session
endpoint_method="POST", ).query
endpoint_desc="Test Description 2",
endpoint_code="TEST002", # Create two endpoint restrictions
is_confirmed=True, endpoint1 = create_sample_endpoint_restriction("TEST009")
expiry_starts=arrow.now().shift(days=-1), endpoint2 = create_sample_endpoint_restriction("TEST010")
expiry_ends=arrow.now().shift(days=1)
)
result = EndpointRestriction.filter_by_all_system( result = EndpointRestriction.filter_by_all_system(
db=db_session, db=db_session,
endpoint_method="POST" endpoint_method="GET"
) )
# Test PostgresResponse properties # Test PostgresResponse properties
success = ( success = (
result.count == 1 and result is not None and
result.total_count == 1 and result.count == 2 and
len(result.data) == 1 and result.total_count == 2 and
result.data[0].endpoint_code == "TEST002" and result.is_list is True
result.is_list is True and )
isinstance(result.data_as_dict, list) and print(f"Test {'passed' if success else 'failed'}")
len(result.data_as_dict) == 1 return success
) except Exception as e:
print(f"Test {'passed' if success else 'failed'}") print(f"Test failed with exception: {e}")
return success return False
def test_get_not_expired_query_arg(): def test_get_not_expired_query_arg():
"""Test expiry date filtering in query arguments.""" """Test adding expiry date filtering to query arguments."""
print("\nTesting get_not_expired_query_arg...") print("\nTesting get_not_expired_query_arg...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
# Create active and expired endpoints try:
active_endpoint = create_sample_endpoint_restriction() # Create a sample endpoint with a unique code
expired_endpoint = EndpointRestriction.find_or_create( endpoint_code = f"TEST{int(arrow.now().timestamp())}{arrow.now().microsecond}"
endpoint_function="expired_function", sample_endpoint = create_sample_endpoint_restriction(endpoint_code)
endpoint_name="Expired Endpoint",
endpoint_method="GET", # Test the query argument generation
endpoint_desc="Expired Description", args = EndpointRestriction.get_not_expired_query_arg(())
endpoint_code="EXP001",
is_confirmed=True, # Verify the arguments
expiry_starts=arrow.now().shift(days=-2), success = (
expiry_ends=arrow.now().shift(days=-1) len(args) == 2 and
) any(str(arg).startswith("endpoint_restriction.expiry_starts") for arg in args) and
any(str(arg).startswith("endpoint_restriction.expiry_ends") for arg in args)
result = EndpointRestriction.filter_all( )
EndpointRestriction.endpoint_code.in_(["TEST001", "EXP001"]), print(f"Test {'passed' if success else 'failed'}")
db=db_session return success
) except Exception as e:
print(f"Test failed with exception: {e}")
# Test PostgresResponse properties return False
success = (
result.count == 1 and
result.total_count == 1 and
len(result.data) == 1 and
result.data[0].endpoint_code == "TEST001" and
result.is_list is True and
isinstance(result.data_as_dict, list) and
len(result.data_as_dict) == 1 and
result.data_as_dict[0].get("endpoint_code") == "TEST001"
)
print(f"Test {'passed' if success else 'failed'}")
return success
def test_add_new_arg_to_args(): def test_add_new_arg_to_args():
"""Test adding new arguments to query arguments.""" """Test adding new arguments to query arguments."""
print("\nTesting add_new_arg_to_args...") print("\nTesting add_new_arg_to_args...")
args = (EndpointRestriction.endpoint_code == "TEST001",) try:
new_arg = EndpointRestriction.endpoint_method == "GET" args = (EndpointRestriction.endpoint_code == "TEST001",)
new_arg = EndpointRestriction.endpoint_method == "GET"
updated_args = EndpointRestriction.add_new_arg_to_args(args, "endpoint_method", new_arg)
success = len(updated_args) == 2 updated_args = EndpointRestriction.add_new_arg_to_args(args, "endpoint_method", new_arg)
success = len(updated_args) == 2
# Test duplicate prevention
duplicate_arg = EndpointRestriction.endpoint_method == "GET" # Test duplicate prevention
updated_args = EndpointRestriction.add_new_arg_to_args(updated_args, "endpoint_method", duplicate_arg) duplicate_arg = EndpointRestriction.endpoint_method == "GET"
success = success and len(updated_args) == 2 # Should not add duplicate updated_args = EndpointRestriction.add_new_arg_to_args(updated_args, "endpoint_method", duplicate_arg)
success = success and len(updated_args) == 2 # Should not add duplicate
print(f"Test {'passed' if success else 'failed'}")
return success print(f"Test {'passed' if success else 'failed'}")
return success
except Exception as e:
print(f"Test failed with exception: {e}")
return False
def test_produce_query_to_add(): def test_produce_query_to_add():
"""Test adding query parameters to filter options.""" """Test adding query parameters to filter options."""
print("\nTesting produce_query_to_add...") print("\nTesting produce_query_to_add...")
with EndpointRestriction.new_session() as db_session: with EndpointRestriction.new_session() as db_session:
sample_endpoint = create_sample_endpoint_restriction() try:
filter_list = { sample_endpoint = create_sample_endpoint_restriction("TEST001")
"query": { filter_list = {
"endpoint_method": "GET", "query": {
"endpoint_code": "TEST001" "endpoint_method": "GET",
"endpoint_code": "TEST001"
}
} }
} args = ()
args = ()
updated_args = EndpointRestriction.produce_query_to_add(filter_list, args)
updated_args = EndpointRestriction.produce_query_to_add(filter_list, args) success = len(updated_args) == 2
success = len(updated_args) == 2
result = EndpointRestriction.filter_all(
result = EndpointRestriction.filter_all( *updated_args,
*updated_args, db=db_session
db=db_session )
)
# Test PostgresResponse properties
# Test PostgresResponse properties success = (
success = ( success and
success and result is not None and
result.count == 1 and result.count == 1 and
result.total_count == 1 and result.total_count == 1 and
len(result.data) == 1 and result.is_list is True
result.data[0].endpoint_code == "TEST001" and )
result.is_list is True and
isinstance(result.data_as_dict, list) and print(f"Test {'passed' if success else 'failed'}")
len(result.data_as_dict) == 1 and return success
result.data_as_dict[0].get("endpoint_code") == "TEST001" except Exception as e:
) print(f"Test failed with exception: {e}")
return False
print(f"Test {'passed' if success else 'failed'}")
return success def test_get_dict():
"""Test the get_dict() function for single-record filters."""
print("\nTesting get_dict...")
with EndpointRestriction.new_session() as db_session:
try:
# Set up pre_query first
EndpointRestriction.pre_query = EndpointRestriction.filter_all(
EndpointRestriction.endpoint_method == "GET",
db=db_session
).query
# Create a sample endpoint
endpoint_code = "TEST_DICT_001"
sample_endpoint = create_sample_endpoint_restriction(endpoint_code)
# Get the endpoint using filter_one
result = EndpointRestriction.filter_one(
EndpointRestriction.endpoint_code == endpoint_code,
db=db_session
)
# Get the data and convert to dict
data = result.data
data_dict = data.get_dict()
# Test dictionary properties
success = (
data_dict is not None and
isinstance(data_dict, dict) and
data_dict.get("endpoint_code") == endpoint_code and
data_dict.get("endpoint_method") == "GET" and
data_dict.get("endpoint_function") == "test_function" and
data_dict.get("endpoint_name") == "Test Endpoint" and
data_dict.get("endpoint_desc") == "Test Description" and
data_dict.get("is_confirmed") is True and
data_dict.get("active") is True and
data_dict.get("deleted") is False
)
print(f"Test {'passed' if success else 'failed'}")
return success
except Exception as e:
print(f"Test failed with exception: {e}")
return False
def run_all_tests(): def run_all_tests():
"""Run all tests and report results.""" """Run all tests and report results."""
print("Starting EndpointRestriction tests...") print("Starting EndpointRestriction tests...")
# Clean up any existing test data before starting
cleanup_test_data()
tests = [ tests = [
test_filter_by_one, test_filter_by_one,
test_filter_by_one_system, test_filter_by_one_system,
@ -318,22 +441,45 @@ def run_all_tests():
test_filter_by_all_system, test_filter_by_all_system,
test_get_not_expired_query_arg, test_get_not_expired_query_arg,
test_add_new_arg_to_args, test_add_new_arg_to_args,
test_produce_query_to_add test_produce_query_to_add,
test_get_dict # Added new test
] ]
passed_list, not_passed_list = [], []
passed = 0 passed, failed = 0, 0
failed = 0
for test in tests: for test in tests:
if test(): # Clean up test data before each test
passed += 1 cleanup_test_data()
else: try:
if test():
passed += 1
passed_list.append(
f"Test {test.__name__} passed"
)
else:
failed += 1
not_passed_list.append(
f"Test {test.__name__} failed"
)
except Exception as e:
print(f"Test {test.__name__} failed with exception: {e}")
failed += 1 failed += 1
not_passed_list.append(
print(f"\nTest Summary:") f"Test {test.__name__} failed"
print(f"Total tests: {len(tests)}") )
print(f"Passed: {passed}")
print(f"Failed: {failed}") print(f"\nTest Results: {passed} passed, {failed} failed")
print('Passed Tests:')
print(
"\n".join(passed_list)
)
print('Failed Tests:')
print(
"\n".join(not_passed_list)
)
return passed, failed
if __name__ == "__main__": if __name__ == "__main__":
generate_table_in_postgres()
run_all_tests() run_all_tests()

View File

@ -1,4 +1,5 @@
import arrow import arrow
from sqlalchemy import ( from sqlalchemy import (
TIMESTAMP, TIMESTAMP,
func, func,
@ -14,10 +15,10 @@ from sqlalchemy_mixins.serialize import SerializeMixin
from sqlalchemy_mixins.repr import ReprMixin from sqlalchemy_mixins.repr import ReprMixin
from sqlalchemy_mixins.smartquery import SmartQueryMixin from sqlalchemy_mixins.smartquery import SmartQueryMixin
from base import BaseAlchemyModel from Controllers.Postgres.base import BaseAlchemyModel
from crud import CRUDModel from Controllers.Postgres.crud import CRUDModel
from filter import QueryModel from Controllers.Postgres.filter import QueryModel
from database import Base from Controllers.Postgres.database import Base
class BasicMixin( class BasicMixin(

View File

@ -75,12 +75,12 @@ class PostgresResponse(Generic[T]):
return self._query.count() return self._query.count()
@property @property
def query(self) -> str: def core_query(self) -> str:
"""Get query object.""" """Get query object."""
return str(self._query) return str(self._query)
@property @property
def core_query(self) -> Query: def query(self) -> Query:
"""Get query object.""" """Get query object."""
return self._query return self._query

View File

@ -1,7 +1,7 @@
from sqlalchemy import String from sqlalchemy import String
from sqlalchemy.orm import mapped_column, Mapped from sqlalchemy.orm import mapped_column, Mapped
from mixin import CrudCollection from Controllers.Postgres.mixin import CrudCollection
class EndpointRestriction(CrudCollection): class EndpointRestriction(CrudCollection):

View File

@ -10,10 +10,10 @@ This module provides a class for managing Redis key-value operations with suppor
import arrow import arrow
import json import json
from connection import redis_cli
from typing import Union, Dict, List, Optional, Any, TypeVar from typing import Union, Dict, List, Optional, Any, TypeVar
from Controllers.Redis.connection import redis_cli
T = TypeVar('T', Dict[str, Any], List[Any]) T = TypeVar('T', Dict[str, Any], List[Any])

View File

@ -0,0 +1,25 @@
from pydantic_settings import BaseSettings, SettingsConfigDict
class Configs(BaseSettings):
"""
MongoDB configuration settings.
"""
HOST: str = ""
PASSWORD: str = ""
PORT: int = 0
DB: int = 0
def as_dict(self):
return dict(
host=self.HOST,
password=self.PASSWORD,
port=int(self.PORT),
db=self.DB,
)
model_config = SettingsConfigDict(env_prefix="REDIS_")
redis_configs = Configs() # singleton instance of the REDIS configuration settings
print(redis_configs.as_dict())

View File

@ -1,7 +1,8 @@
import time import time
from typing import Dict, Any, Optional from typing import Dict, Any
from redis import Redis, ConnectionError, TimeoutError from redis import Redis, ConnectionError, TimeoutError, ConnectionPool
from Controllers.Redis.config import redis_configs
class RedisConn: class RedisConn:
@ -16,18 +17,16 @@ class RedisConn:
def __init__( def __init__(
self, self,
config: Optional[Dict[str, Any]] = None,
max_retries: int = CONNECTION_RETRIES, max_retries: int = CONNECTION_RETRIES,
): ):
""" """
Initialize Redis connection with configuration. Initialize Redis connection with configuration.
Args: Args:
config: Redis connection configuration dictionary. If None, uses WagRedis config.
max_retries: Maximum number of connection attempts. max_retries: Maximum number of connection attempts.
""" """
self.max_retries = max_retries self.max_retries = max_retries
self.config = config or {} self.config = redis_configs.as_dict()
self._redis = None self._redis = None
self._pool = None self._pool = None
@ -72,7 +71,6 @@ class RedisConn:
for attempt in range(1, self.max_retries + 1): for attempt in range(1, self.max_retries + 1):
try: try:
if self._pool is None: if self._pool is None:
from redis import ConnectionPool
self._pool = ConnectionPool(**self.config) self._pool = ConnectionPool(**self.config)
self._redis = Redis(connection_pool=self._pool) self._redis = Redis(connection_pool=self._pool)
if self.check_connection(): if self.check_connection():
@ -101,7 +99,7 @@ class RedisConn:
return False return False
def set_connection( def set_connection(
self, host: str, password: str, port: int, db: int, **kwargs self, **kwargs
) -> Redis: ) -> Redis:
""" """
Recreate Redis connection with new parameters. Recreate Redis connection with new parameters.
@ -119,10 +117,10 @@ class RedisConn:
try: try:
# Update configuration # Update configuration
self.config = { self.config = {
"host": host, "host": redis_configs.HOST,
"password": password, "password": redis_configs.PASSWORD,
"port": port, "port": redis_configs.PORT,
"db": db, "db": redis_configs.PORT,
"socket_timeout": kwargs.get("socket_timeout", self.DEFAULT_TIMEOUT), "socket_timeout": kwargs.get("socket_timeout", self.DEFAULT_TIMEOUT),
"socket_connect_timeout": kwargs.get( "socket_connect_timeout": kwargs.get(
"socket_connect_timeout", self.DEFAULT_TIMEOUT "socket_connect_timeout", self.DEFAULT_TIMEOUT

View File

@ -1,9 +1,10 @@
import arrow import arrow
from typing import Optional, List, Dict, Union, Iterator from typing import Optional, List, Dict, Union, Iterator
from response import RedisResponse
from connection import redis_cli from Controllers.Redis.response import RedisResponse
from base import RedisRow from Controllers.Redis.connection import redis_cli
from Controllers.Redis.base import RedisRow
class MainConfig: class MainConfig:

View File

@ -1,5 +1,5 @@
from typing import Dict, List, Optional from Controllers.Redis.database import RedisActions
from database import RedisActions
def example_set_json() -> None: def example_set_json() -> None:
"""Example of setting JSON data in Redis with and without expiry.""" """Example of setting JSON data in Redis with and without expiry."""
@ -7,48 +7,48 @@ def example_set_json() -> None:
data = {"name": "John", "age": 30, "city": "New York"} data = {"name": "John", "age": 30, "city": "New York"}
keys = ["user", "profile", "123"] keys = ["user", "profile", "123"]
result = RedisActions.set_json(list_keys=keys, value=data) result = RedisActions.set_json(list_keys=keys, value=data)
print("Set JSON without expiry:", result) print("Set JSON without expiry:", result.as_dict())
# Example 2: Set JSON with expiry # Example 2: Set JSON with expiry
expiry = {"hours": 1, "minutes": 30} expiry = {"hours": 1, "minutes": 30}
result = RedisActions.set_json(list_keys=keys, value=data, expires=expiry) result = RedisActions.set_json(list_keys=keys, value=data, expires=expiry)
print("Set JSON with expiry:", result) print("Set JSON with expiry:", result.as_dict())
def example_get_json() -> None: def example_get_json() -> None:
"""Example of retrieving JSON data from Redis.""" """Example of retrieving JSON data from Redis."""
# Example 1: Get all matching keys # Example 1: Get all matching keys
keys = ["user", "profile", "*"] keys = ["user", "profile", "*"]
result = RedisActions.get_json(list_keys=keys) result = RedisActions.get_json(list_keys=keys)
print("Get all matching JSON:", result) print("Get all matching JSON:", result.as_dict())
# Example 2: Get with limit # Example 2: Get with limit
result = RedisActions.get_json(list_keys=keys, limit=5) result = RedisActions.get_json(list_keys=keys, limit=5)
print("Get JSON with limit:", result) print("Get JSON with limit:", result.as_dict())
def example_get_json_iterator() -> None: def example_get_json_iterator() -> None:
"""Example of using the JSON iterator for large datasets.""" """Example of using the JSON iterator for large datasets."""
keys = ["user", "profile", "*"] keys = ["user", "profile", "*"]
for row in RedisActions.get_json_iterator(list_keys=keys): for row in RedisActions.get_json_iterator(list_keys=keys):
print("Iterating over JSON row:", row) print("Iterating over JSON row:", row.as_dict if isinstance(row.as_dict, dict) else row.as_dict())
def example_delete_key() -> None: # def example_delete_key() -> None:
"""Example of deleting a specific key.""" # """Example of deleting a specific key."""
key = "user:profile:123" # key = "user:profile:123"
result = RedisActions.delete_key(key) # result = RedisActions.delete_key(key)
print("Delete specific key:", result) # print("Delete specific key:", result)
#
def example_delete() -> None: # def example_delete() -> None:
"""Example of deleting multiple keys matching a pattern.""" # """Example of deleting multiple keys matching a pattern."""
keys = ["user", "profile", "*"] # keys = ["user", "profile", "*"]
result = RedisActions.delete(list_keys=keys) # result = RedisActions.delete(list_keys=keys)
print("Delete multiple keys:", result) # print("Delete multiple keys:", result)
def example_refresh_ttl() -> None: def example_refresh_ttl() -> None:
"""Example of refreshing TTL for a key.""" """Example of refreshing TTL for a key."""
key = "user:profile:123" key = "user:profile:123"
new_expiry = {"hours": 2, "minutes": 0} new_expiry = {"hours": 2, "minutes": 0}
result = RedisActions.refresh_ttl(key=key, expires=new_expiry) result = RedisActions.refresh_ttl(key=key, expires=new_expiry)
print("Refresh TTL:", result) print("Refresh TTL:", result.as_dict())
def example_key_exists() -> None: def example_key_exists() -> None:
"""Example of checking if a key exists.""" """Example of checking if a key exists."""
@ -58,9 +58,10 @@ def example_key_exists() -> None:
def example_resolve_expires_at() -> None: def example_resolve_expires_at() -> None:
"""Example of resolving expiry time for a key.""" """Example of resolving expiry time for a key."""
from base import RedisRow from Controllers.Redis.base import RedisRow
redis_row = RedisRow() redis_row = RedisRow()
redis_row.redis_key = "user:profile:123" redis_row.set_key("user:profile:123")
print(redis_row.keys)
expires_at = RedisActions.resolve_expires_at(redis_row) expires_at = RedisActions.resolve_expires_at(redis_row)
print("Resolve expires at:", expires_at) print("Resolve expires at:", expires_at)
@ -77,11 +78,11 @@ def run_all_examples() -> None:
print("\n3. Using JSON iterator:") print("\n3. Using JSON iterator:")
example_get_json_iterator() example_get_json_iterator()
print("\n4. Deleting specific key:") # print("\n4. Deleting specific key:")
example_delete_key() # example_delete_key()
#
print("\n5. Deleting multiple keys:") # print("\n5. Deleting multiple keys:")
example_delete() # example_delete()
print("\n6. Refreshing TTL:") print("\n6. Refreshing TTL:")
example_refresh_ttl() example_refresh_ttl()

View File

@ -1,5 +1,5 @@
from typing import Union, Dict, List, Optional, Any from typing import Union, Dict, Optional, Any
from base import RedisRow from Controllers.Redis.base import RedisRow
class RedisResponse: class RedisResponse:
@ -11,11 +11,11 @@ class RedisResponse:
""" """
def __init__( def __init__(
self, self,
status: bool, status: bool,
message: str, message: str,
data: Any = None, data: Any = None,
error: Optional[str] = None, error: Optional[str] = None,
): ):
""" """
Initialize a Redis response. Initialize a Redis response.

View File

@ -1,18 +1,22 @@
MONGO_ENGINE=mongodb MONGO_ENGINE=mongodb
MONGO_DB=mongodb MONGO_DB=mongo_database
MONGO_HOST=mongo_service MONGO_HOST=mongo_service
MONGO_PORT=27017 MONGO_PORT=27017
MONGO_USER=mongo_user MONGO_USER=mongo_user
MONGO_PASSWORD=mongo_password MONGO_PASSWORD=mongo_password
POSTGRES_DB=POSTGRES_DB POSTGRES_DB=wag_database
POSTGRES_USER=POSTGRES_USER POSTGRES_USER=berkay_wag_user
POSTGRES_PASSWORD=POSTGRES_PASSWORD POSTGRES_PASSWORD=berkay_wag_user_password
POSTGRES_HOST=POSTGRES_HOST POSTGRES_HOST=postgres-service
POSTGRES_PORT=1155 POSTGRES_PORT=5432
POSTGRES_ENGINE=POSTGRES_ENGINE POSTGRES_ENGINE=postgresql+psycopg2
POSTGRES_POOL_PRE_PING=True POSTGRES_POOL_PRE_PING=True
POSTGRES_POOL_SIZE=20 POSTGRES_POOL_SIZE=20
POSTGRES_MAX_OVERFLOW=10 POSTGRES_MAX_OVERFLOW=10
POSTGRES_POOL_RECYCLE=600 POSTGRES_POOL_RECYCLE=600
POSTGRES_POOL_TIMEOUT=30 POSTGRES_POOL_TIMEOUT=30
POSTGRES_ECHO=True POSTGRES_ECHO=True
REDIS_HOST=redis_service
REDIS_PASSWORD=commercial_redis_password
REDIS_PORT=6379
REDIS_DB=0

View File

@ -0,0 +1,9 @@
services:
test_server:
container_name: test_server
build:
context: .
dockerfile: ApiServices/Dockerfile
env_file:
- api_env.env

View File

@ -1,4 +1,53 @@
services: services:
mongo_service:
container_name: mongo_service
image: "bitnami/mongodb:latest"
networks:
- wag-services
environment:
- MONGODB_DISABLE_ENFORCE_AUTH=true
- MONGODB_ROOT_PASSWORD=root
- MONGODB_DATABASE=mongo_database
- MONGODB_USERNAME=mongo_user
- MONGODB_PASSWORD=mongo_password
- MONGO_INITDB_ROOT_USERNAME=mongo_user
- MONGO_INITDB_ROOT_PASSWORD=mongo_password
- MONGO_INITDB_DATABASE=mongo_database
ports:
- "11777:27017"
volumes:
- mongodb-data:/bitnami/mongodb
postgres-service:
container_name: postgres-service
image: "bitnami/postgresql:latest"
networks:
- wag-services
restart: on-failure
depends_on:
- mongo_service
environment:
- POSTGRES_DB=wag_database
- POSTGRES_USER=berkay_wag_user
- POSTGRES_PASSWORD=berkay_wag_user_password
ports:
- "5444:5432"
volumes:
- postgres-data:/bitnami/postgresql
redis_service:
container_name: redis_service
image: "bitnami/redis:latest"
networks:
- wag-services
restart: on-failure
environment:
- REDIS_HOST=redis_service
- REDIS_PASSWORD=commercial_redis_password
- REDIS_PORT=6379
- REDIS_DB=0
ports:
- "11222:6379"
test_server: test_server:
container_name: test_server container_name: test_server
@ -7,3 +56,12 @@ services:
dockerfile: ApiServices/Dockerfile dockerfile: ApiServices/Dockerfile
env_file: env_file:
- api_env.env - api_env.env
networks:
- wag-services
networks:
wag-services:
volumes:
postgres-data:
mongodb-data: