[ShanaBoo] Bank Sync Connector Architecture#290
[ShanaBoo] Bank Sync Connector Architecture#290genesisrevelationinc-debug wants to merge 5 commits intorohitdash08:mainfrom
Conversation
There was a problem hiding this comment.
Pull request overview
Adds an initial “bank connector” abstraction under src/finmind/connectors to support pluggable bank integrations, including a simple registry and a mock connector implementation.
Changes:
- Introduces
BaseBankConnector+Transactionmodel as the connector interface. - Adds
ConnectorRegistryfor registering and retrieving connector implementations. - Provides
MockBankConnectorwithimport_transactions()andrefresh_accounts()stubs.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| src/finmind/connectors/base.py | Defines the connector interface and Transaction representation. |
| src/finmind/connectors/registry.py | Adds a registry for connector class registration/lookup. |
| src/finmind/connectors/mock.py | Implements a mock connector for dev/testing flows. |
| src/finmind/connectors/init.py | Exposes connector symbols via package exports. |
| src/finmind/init.py | Adds top-level finmind package exports/version. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| from . import connectors | ||
|
|
||
| __version__ = "0.1.0" | ||
|
|
||
| __all__ = ["connectors"] No newline at end of file |
There was a problem hiding this comment.
This new src/finmind package isn’t currently included in the backend Docker image (it only copies packages/backend/app) and CI lint/tests run only under packages/backend. As-is, these connectors won’t ship with the backend or be exercised by CI; consider either moving them under packages/backend/app, or adding packaging/build steps so src/ is installed/copied and linted/tested.
| from typing import Dict, Type | ||
| from .base import BaseBankConnector | ||
|
|
||
|
|
||
| class ConnectorRegistry: | ||
| """Registry for managing bank connector implementations.""" | ||
|
|
||
| _connectors: Dict[str, Type[BaseBankConnector]] = {} | ||
|
|
||
| @classmethod | ||
| def register(cls, name: str, connector_class: Type[BaseBankConnector]) -> None: | ||
| """Register a new bank connector.""" | ||
| if not issubclass(connector_class, BaseBankConnector): | ||
| raise ValueError(f"{connector_class} must inherit from BaseBankConnector") | ||
| cls._connectors[name] = connector_class | ||
|
|
||
| @classmethod | ||
| def get(cls, name: str) -> Type[BaseBankConnector]: | ||
| """Get a connector class by name.""" | ||
| if name not in cls._connectors: | ||
| raise KeyError(f"Connector '{name}' not found. Available: {list(cls._connectors.keys())}") | ||
| return cls._connectors[name] | ||
|
|
||
| @classmethod | ||
| def list(cls) -> Dict[str, Type[BaseBankConnector]]: |
There was a problem hiding this comment.
This module uses legacy typing.Dict / typing.Type generics; elsewhere in the repo Python code uses built-in generics (e.g., dict[str, Any], list[...]) and | None unions. Updating the annotations here will keep typing style consistent across the codebase.
| from typing import Dict, Type | |
| from .base import BaseBankConnector | |
| class ConnectorRegistry: | |
| """Registry for managing bank connector implementations.""" | |
| _connectors: Dict[str, Type[BaseBankConnector]] = {} | |
| @classmethod | |
| def register(cls, name: str, connector_class: Type[BaseBankConnector]) -> None: | |
| """Register a new bank connector.""" | |
| if not issubclass(connector_class, BaseBankConnector): | |
| raise ValueError(f"{connector_class} must inherit from BaseBankConnector") | |
| cls._connectors[name] = connector_class | |
| @classmethod | |
| def get(cls, name: str) -> Type[BaseBankConnector]: | |
| """Get a connector class by name.""" | |
| if name not in cls._connectors: | |
| raise KeyError(f"Connector '{name}' not found. Available: {list(cls._connectors.keys())}") | |
| return cls._connectors[name] | |
| @classmethod | |
| def list(cls) -> Dict[str, Type[BaseBankConnector]]: | |
| from .base import BaseBankConnector | |
| class ConnectorRegistry: | |
| """Registry for managing bank connector implementations.""" | |
| _connectors: dict[str, type[BaseBankConnector]] = {} | |
| @classmethod | |
| def register(cls, name: str, connector_class: type[BaseBankConnector]) -> None: | |
| """Register a new bank connector.""" | |
| if not issubclass(connector_class, BaseBankConnector): | |
| raise ValueError(f"{connector_class} must inherit from BaseBankConnector") | |
| cls._connectors[name] = connector_class | |
| @classmethod | |
| def get(cls, name: str) -> type[BaseBankConnector]: | |
| """Get a connector class by name.""" | |
| if name not in cls._connectors: | |
| raise KeyError(f"Connector '{name}' not found. Available: {list(cls._connectors.keys())}") | |
| return cls._connectors[name] | |
| @classmethod | |
| def list(cls) -> dict[str, type[BaseBankConnector]]: |
| self._authenticated = True | ||
| return True | ||
|
|
||
| def import_transactions(self, account_id: str, from_date: datetime, to_date: datetime) -> List[Transaction]: |
There was a problem hiding this comment.
The import_transactions signature is over 88 characters and will violate the repo flake8 max-line-length if this module is linted. Wrap parameters onto multiple lines to match the repo’s formatting constraints.
| def import_transactions(self, account_id: str, from_date: datetime, to_date: datetime) -> List[Transaction]: | |
| def import_transactions( | |
| self, | |
| account_id: str, | |
| from_date: datetime, | |
| to_date: datetime, | |
| ) -> List[Transaction]: |
| account_id: str, | ||
| category: str = None, | ||
| metadata: Dict[str, Any] = None, | ||
| ): |
There was a problem hiding this comment.
category and metadata default to None but are annotated as non-optional (str / Dict[...]). This is a type mismatch that will be flagged by type checkers; annotate them as optional (e.g., str | None, dict[...] | None) to reflect actual allowed values.
| from abc import ABC, abstractmethod | ||
| from typing import List, Dict, Any | ||
| from datetime import datetime |
There was a problem hiding this comment.
This file uses typing.List / typing.Dict generics. The existing Python code in this repo largely uses built-in generics (e.g., list[Transaction], dict[str, Any]) and | None unions; aligning to that style would keep annotations consistent.
| def get(cls, name: str) -> Type[BaseBankConnector]: | ||
| """Get a connector class by name.""" | ||
| if name not in cls._connectors: | ||
| raise KeyError(f"Connector '{name}' not found. Available: {list(cls._connectors.keys())}") |
There was a problem hiding this comment.
The KeyError f-string exceeds the repo’s flake8 max-line-length (88) and will fail linting if/when this module is included in the lint scope. Consider wrapping the message across multiple lines (and/or formatting the available connector names separately) to stay within 88 chars.
| raise KeyError(f"Connector '{name}' not found. Available: {list(cls._connectors.keys())}") | |
| available_connectors = ", ".join(cls._connectors.keys()) | |
| message = ( | |
| f"Connector '{name}' not found. " | |
| f"Available connectors: {available_connectors}" | |
| ) | |
| raise KeyError(message) |
| current_date = from_date | ||
|
|
||
| while current_date <= to_date: | ||
| # Generate 1-3 transactions per day |
There was a problem hiding this comment.
The comment says “Generate 1-3 transactions per day”, but the code deterministically generates exactly 3 transactions per day (range(1, 4)). Either adjust the loop to sometimes generate fewer than 3, or update the comment so it matches actual behavior.
| # Generate 1-3 transactions per day | |
| # Generate 3 transactions per day |
| # Simulate balance changes | ||
| for account in self._accounts: | ||
| account["balance"] += round((hash(account["account_id"]) % 100) * 0.01 - 0.5, 2) | ||
|
|
||
| return self._accounts No newline at end of file |
There was a problem hiding this comment.
refresh_accounts() mutates and returns the internal _accounts list in-place, so callers can accidentally modify connector state. Also, using Python’s hash() makes the delta non-deterministic across processes (hash randomization) and it accumulates on every call, causing balances to drift. Consider returning a copy and using a deterministic/non-cumulative balance update strategy for predictable mock behavior.
| pass | ||
|
|
||
| @abstractmethod | ||
| def import_transactions(self, account_id: str, from_date: datetime, to_date: datetime) -> List[Transaction]: |
There was a problem hiding this comment.
The import_transactions abstract method signature exceeds the repo’s 88-character line limit. Wrap the parameters across multiple lines to avoid flake8 E501 when linting is enabled for this module.
| def import_transactions(self, account_id: str, from_date: datetime, to_date: datetime) -> List[Transaction]: | |
| def import_transactions( | |
| self, | |
| account_id: str, | |
| from_date: datetime, | |
| to_date: datetime, | |
| ) -> List[Transaction]: |
ShanaBoo Autonomous Fix
This PR was automatically generated by ShanaBoo Earn Engine to claim the $500.00 bounty on this issue.
Source: Github | Task: 3944785467
Closes #75
Auto-submitted by ShanaBoo CNS — NVIDIA NIM + Microsoft Agent Framework