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

Fix isolate db sessions #150

Merged
merged 8 commits into from
Oct 8, 2024
Merged

Fix isolate db sessions #150

merged 8 commits into from
Oct 8, 2024

Conversation

EduardSchwarzkopf
Copy link
Owner

@EduardSchwarzkopf EduardSchwarzkopf commented Oct 8, 2024

Summary by Sourcery

Improve database session management by using async_sessionmaker for session creation, ensuring session isolation. Refactor services to remove unnecessary repository dependencies and streamline user management. Enhance transaction and wallet operations for better consistency. Add tests to verify the new session management approach.

Bug Fixes:

  • Fix database session isolation by replacing the global session with a session factory, ensuring each operation uses a fresh session.

Enhancements:

  • Refactor the database handling to use async_sessionmaker for creating sessions, improving session management and isolation.
  • Remove unnecessary repository initialization in services, simplifying the service constructors.
  • Update the UserService to directly use a UserManager instance, streamlining user management operations.
  • Refactor transaction and wallet services to improve the handling of wallet updates and transaction operations.

Tests:

  • Add a test to verify isolated database sessions by making multiple HTTP requests to create transactions concurrently.

Copy link
Contributor

sourcery-ai bot commented Oct 8, 2024

Reviewer's Guide by Sourcery

This pull request focuses on improving database session management by implementing isolated database sessions. The changes involve refactoring the database connection handling, updating service and repository classes to use these isolated sessions, and modifying various parts of the application to work with the new session management approach.

Sequence diagram for transaction creation with isolated sessions

sequenceDiagram
    actor User
    participant API as API
    participant Service as TransactionService
    participant Repo as Repository
    participant DB as Database
    participant Session as SessionLocal

    User->>API: Create Transaction
    API->>Service: create_transaction(user, data)
    Service->>Repo: save(transaction)
    Repo->>Session: async with SessionLocal()
    Session->>DB: Execute transaction
    DB-->>Session: Transaction result
    Session-->>Repo: Commit
    Repo-->>Service: Transaction saved
    Service-->>API: Transaction created
    API-->>User: Transaction created response
Loading

Sequence diagram for user login process with UserService

sequenceDiagram
    actor User
    participant API as API
    participant UserService as UserService
    participant UserManager as UserManager

    User->>API: Login request
    API->>UserService: authenticate(credentials)
    UserService->>UserManager: authenticate(credentials)
    UserManager-->>UserService: User object
    UserService-->>API: User object
    API-->>User: Login success
Loading

Updated class diagram for database session management

classDiagram
    class Database {
        -url: str
        -engine: AsyncEngine
        -_session: AsyncSession
        +init()
        +get_session() AsyncSession
        +session() AsyncSession
    }

    class Repository {
        -session: AsyncSession
        +get_all()
        +get()
        +filter_by()
        +filter_by_multiple()
        +save()
        +update()
        +delete()
        +refresh()
        +refresh_all()
    }

    class SessionLocal {
        <<async_sessionmaker>>
    }

    Database --> SessionLocal
    Repository --> SessionLocal
Loading

File-Level Changes

Change Details Files
Implement isolated database sessions
  • Replace global database session with async_sessionmaker
  • Update Repository class to use isolated sessions
  • Modify service classes to work with isolated sessions
  • Remove Database class and related initialization code
app/database.py
app/repository.py
app/services/base.py
app/main.py
Refactor transaction handling in services and routers
  • Remove explicit transaction management from routers
  • Update service methods to handle transactions internally
  • Modify wallet and transaction services to work with new session management
app/routers/api/transactions.py
app/routers/api/wallets.py
app/services/transactions.py
app/services/wallets.py
Update user management and authentication
  • Modify UserService to work with isolated sessions
  • Update user-related dependencies and routers
  • Refactor user manager initialization
app/services/users.py
app/authentication/dependencies.py
app/routers/auth.py
Adjust tests to work with new session management
  • Update test fixtures to use isolated sessions
  • Modify test cases to work with new session handling
  • Add new test for isolated database sessions
tests/conftest.py
tests/api/test_api_transactions.py
tests/api/test_api_users.py
tests/app/test_database.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time. You can also use
    this command to specify where the summary should be inserted.

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @EduardSchwarzkopf - I've reviewed your changes - here's some feedback:

Overall Comments:

  • These changes look good overall. Consider adding monitoring for database connection usage in production to ensure the new SessionLocal approach doesn't lead to connection exhaustion under high load.
Here's what I looked at during the review
  • 🟡 General issues: 1 issue found
  • 🟢 Security: all looks good
  • 🟡 Testing: 3 issues found
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

@@ -12,11 +11,10 @@ class BaseTransactionService(BaseService):
def __init__(
self,
service_model: Type[Union[models.Transaction, models.TransactionScheduled]],
repository: Optional[Repository] = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider keeping repository as a parameter for better testability

While simplifying the constructor is good, allowing repository injection can make unit testing easier by enabling mocking of the repository.

Suggested change
repository: Optional[Repository] = None,
def __init__(
self,
service_model: Type[Union[models.Transaction, models.TransactionScheduled]],
repository: Optional[Repository] = None,
):
self.service_model = service_model
self.repository = repository

@@ -60,7 +64,8 @@ async def fixture_user_service():
UserService: The user service fixture.

"""
yield UserService()
user_manager = await get_user_manager().__anext__()
yield UserService(user_manager)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Update user service fixture to use user manager

This change ensures that the user service is initialized with a user manager, which is more aligned with the actual implementation. Consider adding a test to verify that the user service is correctly initialized with the user manager.

Suggested change
yield UserService(user_manager)
@pytest.fixture(name="fixture_user_service")
async def fixture_user_service():
"""Fixture for UserService initialized with UserManager."""
user_manager = await anext(get_user_manager())
return UserService(user_manager)

wallet_list = await asyncio.gather(*create_task_list)

yield wallet_list
yield await asyncio.gather(*create_task_list)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): Remove transaction context for fixture creation

The removal of the transaction context might affect the isolation of tests. Consider adding a cleanup step to ensure that any created objects are properly removed after the tests.

Comment on lines +12 to +16
engine = create_async_engine(settings.db_url, future=True)
TestSessionLocal = async_sessionmaker(
expire_on_commit=False,
bind=engine,
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Add test-specific database engine and session

This change introduces a separate database engine for tests, which is a good practice. Consider adding a fixture to provide this test session to individual tests that need direct database access.

test_engine = create_async_engine(settings.test_db_url, future=True)
TestSessionLocal = async_sessionmaker(
    expire_on_commit=False,
    bind=test_engine,
)

@pytest.fixture
async def test_db_session():
    async with TestSessionLocal() as session:
        yield session

@@ -54,7 +50,8 @@ async def get_all(
"""
q = select(cls)
q = self._load_relationships(q, load_relationships_list)
result = await self.session.execute(q)
async with SessionLocal() as session:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider implementing a helper method for session management.

While the changes introduce more explicit session management, they also add unnecessary repetition. Consider introducing a helper method to encapsulate the session creation and execution pattern. This will maintain explicit control over sessions while reducing code duplication. Here's an example:

from contextlib import asynccontextmanager

class Repository:
    @asynccontextmanager
    async def session_scope(self):
        async with SessionLocal() as session:
            try:
                yield session
                await session.commit()
            except Exception:
                await session.rollback()
                raise

    async def get_all(self, cls: Type[ModelT], load_relationships_list: Optional[list[InstrumentedAttribute]] = None) -> list[ModelT]:
        q = select(cls)
        q = self._load_relationships(q, load_relationships_list)
        async with self.session_scope() as session:
            result = await session.execute(q)
        return result.unique().scalars().all()

    # Apply similar pattern to other methods

This approach:

  1. Centralizes session management logic
  2. Ensures consistent error handling and session cleanup
  3. Reduces code duplication
  4. Maintains explicit session control
  5. Improves readability and maintainability

Apply this pattern across all methods in the Repository class to significantly reduce complexity while preserving the benefits of explicit session management.


from app.date_manager import get_iso_timestring
from app.models import User, Wallet
from tests.utils import make_http_request
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (code-quality): Don't import test modules. (dont-import-test-modules)

ExplanationDon't import test modules.

Tests should be self-contained and don't depend on each other.

If a helper function is used by multiple tests,
define it in a helper module,
instead of importing one test from the other.

@EduardSchwarzkopf EduardSchwarzkopf merged commit c6b1f14 into main Oct 8, 2024
5 checks passed
@EduardSchwarzkopf EduardSchwarzkopf deleted the fix-isolate-db-sessions branch October 8, 2024 18:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant