Async Python client library for Comdirect Banking API with automatic token refresh
Project description
Comdirect Python API Client
A modern, asynchronous Python client library for the Comdirect Banking API. Built with type safety, automatic token refresh, and comprehensive error handling.
Note: This code is AI-generated but has been thoroughly manually tested and reviewed.
Features
- ✅ Full OAuth2 + TAN Authentication Flow - Handles all 5 authentication steps automatically
- ✅ Automatic Token Refresh - Background task refreshes tokens 120s before expiration
- ✅ Optional Token Persistence - Save/restore tokens to disk to avoid reauthentication after app restart
- ✅ Type-Safe Models - Strongly typed dataclasses for all API responses
- ✅ Async/Await - Built on
httpxandasynciofor high performance - ✅ Comprehensive Logging - Detailed logging with sensitive data sanitization
- ✅ Reauth Callbacks - Custom callbacks when reauthentication is needed
- ✅ Error Handling - Specific exceptions for different failure scenarios
- ✅ Context Manager Support - Automatic resource cleanup
API Documentation References
This client implements the official Comdirect REST API:
Table of Contents
- Project Structure
- Installation
- Running Tests
- Quick Start
- API Reference
- Token Management
- Data Models
- Error Handling
- Logging
- Security Notes
Project Structure
comdirect-lib/
├── comdirect_client/ # Main package
│ ├── __init__.py # Package exports
│ ├── client.py # ComdirectClient implementation
│ ├── models.py # Data models (AccountBalance, Transaction)
│ ├── exceptions.py # Custom exceptions
│ └── token_storage.py # Token persistence
│
├── examples/
│ └── basic_usage.py # Complete usage example with real authentication
│
├── tests/
│ ├── conftest.py # Pytest fixtures and mocking setup
│ ├── test_comdirect_bdd.py # BDD step definitions (pytest-bdd)
│ └── test_token_storage.py # Token persistence tests (19 test cases)
│
├── comdirect_api.feature # Gherkin BDD specification (39 scenarios, 457 lines)
├── COMDIRECT_API.md # Detailed API documentation (815 lines)
├── pyproject.toml # Poetry dependencies and configuration
├── test.sh # Quick integration test script
├── .env.example # Environment variable template
└── README.md # This file
Key Components
client.py: Core API client with OAuth2 flow, token refresh, and API methodsmodels.py: Type-safe dataclasses (AccountBalance,Transaction,AmountValue)exceptions.py: Specific exception types for different failure scenariostoken_storage.py: Token persistence for avoiding reauthentication after restartcomdirect_api.feature: Comprehensive BDD specification (living documentation)COMDIRECT_API.md: Deep dive into Comdirect API endpoints and flows
Installation
Prerequisites
- Python 3.9+ (tested with Python 3.9-3.11)
- Poetry (recommended) or pip
Using Poetry (Recommended)
# Clone the repository
git clone <repository-url>
cd comdirect-lib
# Install production dependencies
poetry install
# Install with development dependencies (tests, linting, etc.)
poetry install --with dev
# Activate virtual environment
poetry shell
Using pip
# Install in editable mode
pip install -e .
# Install with development dependencies
pip install -e ".[dev]"
Dependencies
Production:
httpx ^0.27.0- Async HTTP clientpydantic ^2.0.0- Data validation
Development:
pytest ^8.0.0- Testing frameworkpytest-asyncio ^0.23.0- Async test supportpytest-bdd ^7.0.0- BDD testing with Gherkinpytest-mock ^3.12.0- Mocking utilitiesblack ^24.0.0- Code formattermypy ^1.8.0- Type checkerruff ^0.2.0- Fast Python linter
Running Tests
Quick Integration Test (Real API)
The fastest way to test with the real Comdirect API:
# 1. Copy environment template
cp .env.example .env
# 2. Edit .env with your Comdirect credentials
nano .env # or vim, code, etc.
# Required variables:
# COMDIRECT_CLIENT_ID=your_client_id
# COMDIRECT_CLIENT_SECRET=your_client_secret
# COMDIRECT_USERNAME=your_username
# COMDIRECT_PASSWORD=your_password
# 3. Run integration test
chmod +x test.sh
./test.sh
What happens:
- Authenticates with Comdirect API
- Waits for Push-TAN approval on your smartphone (5-60 seconds)
- Fetches account balances
- Fetches transactions for first account
- Tests automatic token refresh
Expected output:
INFO: Starting authentication flow
INFO: OAuth2 token obtained: 1a2b3c4d...
INFO: Waiting for TAN approval (P_TAN_PUSH)
INFO: TAN approved via P_TAN_PUSH
INFO: Authentication successful
INFO: Token refresh task started
Found 2 accounts:
Account 1: Girokonto - Balance: 1234.56 EUR
Account 2: Tagesgeld - Balance: 5678.90 EUR
Retrieved 15 transactions
2024-11-09: -12.50 EUR (Direct Debit)
...
BDD Tests (Mocked)
Run comprehensive BDD tests with mocked API responses:
# Install dev dependencies
poetry install --with dev
# Run all BDD tests
poetry run pytest tests/test_comdirect_bdd.py -v
# Run specific scenario
poetry run pytest tests/test_comdirect_bdd.py -k "token refresh" -v
# Run with coverage report
poetry run pytest --cov=comdirect_client --cov-report=html --cov-report=term
# View HTML coverage report
open htmlcov/index.html # macOS
xdg-open htmlcov/index.html # Linux
BDD Coverage (39 scenarios):
- ✅ Complete 5-step OAuth2 + TAN authentication flow
- ✅ Automatic token refresh (background task)
- ✅ On-demand token refresh (after 401 responses)
- ✅ Account balance retrieval
- ✅ Transaction retrieval with filters
- ✅ Pagination support
- ✅ Error handling and logging
- ✅ Reauth callback mechanism
Unit Tests
# Run all tests
poetry run pytest tests/ -v
# Run with markers
poetry run pytest tests/ -m "not slow" -v
# Verbose output
poetry run pytest tests/ -vv -s
Code Quality Tools
# Type checking
poetry run mypy comdirect_client
# Linting
poetry run ruff check .
# Auto-fix linting issues
poetry run ruff check --fix .
# Code formatting
poetry run black .
# Check formatting without changes
poetry run black --check .
Quick Start
1. Basic Usage Example
import asyncio
import os
from comdirect_client.client import ComdirectClient
async def main():
# Initialize client
async with ComdirectClient(
client_id=os.getenv("COMDIRECT_CLIENT_ID"),
client_secret=os.getenv("COMDIRECT_CLIENT_SECRET"),
username=os.getenv("COMDIRECT_USERNAME"),
password=os.getenv("COMDIRECT_PASSWORD"),
) as client:
# Authenticate (triggers Push-TAN on your smartphone)
print("Authenticating... Please approve Push-TAN on your device")
await client.authenticate()
print("✓ Authenticated!")
# Fetch account balances
balances = await client.get_account_balances()
print(f"\nFound {len(balances)} accounts:")
for balance in balances:
print(f" {balance.account_display_id}: {balance.balance.value} {balance.balance.unit}")
# Fetch transactions for first account
if balances:
transactions = await client.get_transactions(
account_id=balances[0].accountId,
transaction_state="BOOKED", # Only booked transactions
)
print(f"\nFound {len(transactions)} transactions:")
for tx in transactions[:5]: # Show first 5
# Use booking date if available, otherwise fall back to valuta date
display_date = tx.booking_date or tx.valuta_date or 'N/A'
print(f" {display_date}: {tx.amount.value} {tx.amount.unit}")
if __name__ == "__main__":
asyncio.run(main())
2. Run the Example
# Export credentials
export $(cat .env | xargs)
# Run example (requires Push-TAN approval on smartphone)
poetry run python examples/basic_usage.py
# Or use the test script
./test.sh
Expected Flow:
- Script starts → Sends authentication request
- Your smartphone receives Push-TAN notification (approve within 60s)
- After approval → Fetches account data
- Displays balances and recent transactions
API Reference
Client Initialization
from comdirect_client.client import ComdirectClient
client = ComdirectClient(
client_id: str, # OAuth2 client ID (required)
client_secret: str, # OAuth2 client secret (required)
username: str, # Comdirect username (required)
password: str, # Comdirect password (required)
base_url: str = "https://api.comdirect.de", # API base URL
token_storage_path: Optional[str] = None, # File path for token persistence
reauth_callback: Optional[Callable[[str], None]] = None, # Called when reauth needed
token_refresh_threshold_seconds: int = 120, # Refresh 120s before expiry
timeout_seconds: float = 30.0, # HTTP request timeout
)
Authentication
authenticate()
Performs the complete OAuth2 + TAN authentication flow (5 steps):
- Password credentials grant → Initial token
- Session status retrieval → Session UUID
- TAN challenge creation → Triggers Push-TAN
- TAN approval polling → Waits up to 60 seconds
- Session activation + token exchange → Banking token
await client.authenticate()
# Waits for Push-TAN approval on smartphone
# Raises TANTimeoutError if no approval within 60 seconds
# Raises AuthenticationError if credentials invalid
What happens:
- Sends authentication request to Comdirect API
- Creates TAN challenge (Push-TAN/Photo-TAN/SMS-TAN)
- User must approve TAN on smartphone within 60 seconds
- Polls for approval every 1 second
- Activates session and obtains banking token
- Starts automatic token refresh background task
is_authenticated()
Check if client has valid authentication token:
if client.is_authenticated():
print("Authenticated!")
else:
print("Not authenticated - call authenticate() first")
get_token_expiry()
Get token expiration datetime:
from datetime import datetime
expiry: Optional[datetime] = client.get_token_expiry()
if expiry:
seconds_remaining = (expiry - datetime.now()).total_seconds()
print(f"Token expires in {seconds_remaining:.0f} seconds")
refresh_token()
Manually refresh access token (usually done automatically):
success: bool = await client.refresh_token()
if success:
print("Token refreshed successfully")
else:
print("Token refresh failed - reauthentication needed")
Note: The client automatically refreshes tokens in the background 120 seconds before expiration. Manual refresh is rarely needed.
Account Balances
get_account_balances()
Retrieve balances for all accounts:
from comdirect_client.models import AccountBalance
balances: list[AccountBalance] = await client.get_account_balances()
for balance in balances:
print(f"Account ID: {balance.accountId}") # UUID
print(f"Display ID: {balance.account_display_id}") # Formatted account number
print(f"Type: {balance.account_type}") # GIRO, DEPOT, FESTGELD, etc.
print(f"Balance: {balance.balance.value} {balance.balance.unit}") # Current balance
print(f"Available: {balance.available_cash_amount.value}") # Available cash
print(f"Date: {balance.balanceDate}") # ISO date
print()
Method Signature:
async def get_account_balances(
with_attributes: bool = True,
without_attributes: Optional[str] = None
) -> list[AccountBalance]
Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
with_attributes |
bool |
True |
Include account master data in response |
without_attributes |
Optional[str] |
None |
Comma-separated list of attributes to exclude (e.g., "account") |
Query Parameter Examples:
# Include all attributes (default)
balances = await client.get_account_balances()
# Exclude account master data
balances = await client.get_account_balances(with_attributes=False)
# Custom exclusion
balances = await client.get_account_balances(
without_attributes="balance,currency"
)
Response Fields:
accountId(str) - Account UUID (use forget_transactions())account_display_id(str) - Formatted account number (e.g., "DE12345...")account_type(str) - Account type:GIRO,DEPOT,FESTGELD,CALL_MONEY, etc.balance(AmountValue) - Current balance with currencyavailable_cash_amount(AmountValue) - Available fundsbalanceDate(str) - Balance date (ISO format)
Errors:
TokenExpiredError- Authentication expiredNetworkTimeoutError- Request timed out
Transactions
get_transactions()
Retrieve transactions for a specific account:
from comdirect_client.models import Transaction
transactions: list[Transaction] = await client.get_transactions(
account_id="account-uuid-here", # Required: Account UUID
transaction_state="BOOKED", # Optional: Filter by state
transaction_direction="CREDIT_AND_DEBIT", # Optional: Filter by direction
paging_first=0, # Optional: Pagination index
)
for tx in transactions:
# Use booking date if available, otherwise fall back to valuta date
display_date = tx.booking_date or tx.valuta_date or 'N/A'
print(f"Date: {display_date}") # Booking date or valuta date (ISO format)
print(f"Amount: {tx.amount.value} {tx.amount.unit}") # Transaction amount
print(f"Type: {tx.booking_key}") # Transaction type code
print(f"Info: {tx.remittance_info}") # Payment purpose
print()
Method Signature:
async def get_transactions(
account_id: str,
transaction_state: Optional[str] = None,
transaction_direction: Optional[str] = None,
paging_first: Optional[int] = None,
with_attributes: bool = True,
without_attributes: Optional[str] = None
) -> list[Transaction]
Parameters:
| Parameter | Type | Values | Default | Description |
|---|---|---|---|---|
account_id |
str |
UUID | Required | Account UUID from AccountBalance.accountId |
transaction_state |
Optional[str] |
"BOOKED", "NOTBOOKED", "BOTH" |
None |
Filter by booking state |
transaction_direction |
Optional[str] |
"CREDIT", "DEBIT", "CREDIT_AND_DEBIT" |
None |
Filter by direction |
paging_first |
Optional[int] |
0+ | None |
Starting index for pagination |
with_attributes |
bool |
- | True |
Include account details in response |
without_attributes |
Optional[str] |
attribute names | None |
Comma-separated attributes to exclude |
Transaction States:
BOOKED- Only confirmed/booked transactionsNOTBOOKED- Only pending/unbooked transactionsBOTH- All transactions (booked + pending)
Transaction Directions:
CREDIT- Only incoming transactions (deposits)DEBIT- Only outgoing transactions (withdrawals)CREDIT_AND_DEBIT- Both incoming and outgoing
Pagination Example:
# Fetch first 50 transactions
page1 = await client.get_transactions(account_id="...", paging_first=0)
# Fetch next 50 transactions
page2 = await client.get_transactions(account_id="...", paging_first=50)
# Fetch next 50 transactions
page3 = await client.get_transactions(account_id="...", paging_first=100)
Response Fields:
booking_date(str) - Booking date (ISO format: "2024-11-09") - May be None for pending transactionsvaluta_date(str) - Value date (ISO format) - Use as fallback ifbooking_dateis not availableamount(AmountValue) - Transaction amount (positive for credit, negative for debit)booking_key(str) - Transaction type code (e.g., "DIRECT_DEBIT", "TRANSFER")remittance_info(Optional[str]) - Payment purpose/descriptioncreditor_id(Optional[str]) - Creditor identifiermandate_reference(Optional[str]) - SEPA mandate reference
💡 Tip: Handle Missing Booking Dates
For pending transactions, booking_date may be None. Always use valuta_date as a fallback:
# Recommended pattern
for tx in transactions:
# Use booking date if available, otherwise fall back to valuta date
display_date = tx.booking_date or tx.valuta_date or 'N/A'
print(f"Date: {display_date}")
⚠️ Important: Date Filtering NOT Supported
The Comdirect API does not support from_date/to_date parameters for banking transactions. To filter by date, retrieve all transactions and filter client-side:
from datetime import datetime, timedelta
# Fetch all transactions
all_transactions = await client.get_transactions(account_id="...")
# Filter client-side for last 30 days (using booking_date or valuta_date as fallback)
cutoff_date = (datetime.now() - timedelta(days=30)).strftime("%Y-%m-%d")
recent = [tx for tx in all_transactions if (tx.booking_date or tx.valuta_date or "") >= cutoff_date]
print(f"Found {len(recent)} transactions in last 30 days")
Errors:
TokenExpiredError- Authentication expiredAccountNotFoundError- Account UUID doesn't existNetworkTimeoutError- Request timed out
Token Management
Automatic Token Refresh
The client automatically refreshes access tokens in the background:
How it works:
- After successful authentication, starts background asyncio task
- Calculates refresh time:
token_expiry - 120 seconds(configurable) - Sleeps until refresh time
- Automatically calls
POST /oauth/tokenwithgrant_type=refresh_token - Updates
access_tokenandrefresh_token(both tokens rotate!) - Repeats indefinitely
Configuration:
client = ComdirectClient(
...,
token_refresh_threshold_seconds=120, # Refresh 120s before expiry (default)
)
Timeline Example:
T+0: authenticate() → token expires at T+599s
T+479s: Background task wakes up (599 - 120 = 479)
T+479s: POST /oauth/token → new token expires at T+1078s
T+958s: Next automatic refresh (1078 - 120 = 958)
...
Failure Handling:
- If refresh fails → Invokes
reauth_callback(if configured) - Clears all tokens
- Stops background refresh task
Token Persistence
The client supports optional file-based token persistence to avoid reauthentication after application restart. Tokens are stored securely with restricted file permissions (0o600 - owner read/write only).
Security Note: Token persistence stores authentication tokens to disk. Ensure the storage directory is on an encrypted filesystem and has appropriate access controls. In production, consider encrypting tokens at rest using your application's encryption layer.
Enable Token Persistence:
import asyncio
from comdirect_client.client import ComdirectClient
async def main():
# Enable token persistence by providing a storage path
async with ComdirectClient(
client_id="your_client_id",
client_secret="your_client_secret",
username="your_username",
password="your_password",
token_storage_path="/secure/path/comdirect_tokens.json",
) as client:
# First run: Authenticate and tokens are automatically saved
try:
await client.authenticate()
print("✓ Authenticated and tokens saved to disk")
except FileNotFoundError:
print("✗ Token storage directory doesn't exist")
# Subsequent runs: Tokens are automatically loaded from disk
# If tokens are valid, authenticate() returns immediately without 2FA
# If tokens are expired, they're silently discarded and new auth is needed
if __name__ == "__main__":
asyncio.run(main())
How Token Persistence Works:
- Initialization: Client loads saved tokens on startup (if they exist and aren't expired)
- Auto-Save: After authentication or token refresh, tokens are automatically saved to disk
- Expiry Validation: Expired tokens are rejected during load and reauthentication is triggered
- File Security: Token file has restricted permissions (0o600 - owner only)
- On Logout: Token file is automatically deleted via
client.close()or context manager
Persistent Storage Format:
Tokens are stored as JSON with ISO format datetime:
{
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refresh_token": "refresh_token_value_here",
"token_expiry": "2025-12-10T15:30:45.123456"
}
Recovery Scenarios:
# Scenario 1: Tokens exist and are valid
async with ComdirectClient(..., token_storage_path="...") as client:
await client.authenticate() # Returns immediately, no 2FA needed!
balances = await client.get_account_balances()
# Scenario 2: Tokens expired, need reauthentication
async with ComdirectClient(..., token_storage_path="...") as client:
await client.authenticate() # Triggers new 2FA flow
# Old expired tokens are automatically cleared
# Scenario 3: First run, no tokens saved yet
async with ComdirectClient(..., token_storage_path="...") as client:
await client.authenticate() # Normal 2FA flow
# Tokens saved to disk for next run
# Scenario 4: Storage directory doesn't exist
async with ComdirectClient(..., token_storage_path="/nonexistent/path") as client:
await client.authenticate() # Works normally, but won't persist
# TokenStorageError logged as warning
Error Handling:
from comdirect_client.exceptions import TokenStorageError
try:
async with ComdirectClient(..., token_storage_path="...") as client:
await client.authenticate()
except TokenStorageError as e:
print(f"Token storage failed: {e}")
# Token persistence unavailable, but client works normally
except FileNotFoundError:
print("Token storage directory doesn't exist - create it first")
import os
os.makedirs(os.path.dirname(token_storage_path), exist_ok=True)
Disabling Token Persistence:
# Simply omit the token_storage_path parameter (or set to None)
async with ComdirectClient(
client_id="...",
client_secret="...",
username="...",
password="...",
# No token_storage_path - tokens not persisted
) as client:
await client.authenticate()
On-Demand Token Refresh
If an API call receives 401 Unauthorized, the client automatically:
- Attempts token refresh
- Retries the original request with new token
- If refresh fails → Invokes
reauth_callbackand raisesTokenExpiredError
This provides a dual refresh strategy:
- Proactive: Background task (before expiration)
- Reactive: On 401 errors (after expiration)
Reauth Callback
Configure a callback to handle reauthentication needs:
def reauth_handler(reason: str):
"""Called when reauthentication is needed."""
print(f"Reauthentication required: {reason}")
# Send notification, trigger new auth flow, restart service, etc.
# Reasons:
# - "token_refresh_failed" - Background refresh failed
# - "automatic_refresh_failed" - Background task error
# - "api_request_unauthorized" - API returned 401 after refresh attempt
client = ComdirectClient(
...,
reauth_callback=reauth_handler,
)
Use cases:
- Send email/Slack notification to admin
- Trigger new authentication flow
- Log metrics/alerting
- Gracefully restart service
Token Lifecycle
Tokens expire every ~10 minutes (599s). The client automatically refreshes 120 seconds before expiration, ensuring seamless API calls. When a token refresh occurs, both access_token and refresh_token rotate on the Comdirect API side.
Data Models
All API responses are parsed into type-safe dataclasses defined in comdirect_client.models:
AccountBalance
from dataclasses import dataclass
from comdirect_client.models import AccountBalance, AmountValue
@dataclass
class AccountBalance:
accountId: str # Account UUID (use for get_transactions)
account_display_id: str # Formatted account number
account_type: str # Account type (GIRO, DEPOT, etc.)
balance: AmountValue # Current balance
available_cash_amount: AmountValue # Available cash
balanceDate: str # Balance date (ISO format)
Example:
balance = balances[0]
print(balance.accountId) # "B5A9F0C8-B421-..."
print(balance.account_display_id) # "DE12 3456 7890 1234 5678 90"
print(balance.account_type) # "GIRO"
print(balance.balance.value) # 1234.56
print(balance.balance.unit) # "EUR"
Transaction
@dataclass
class Transaction:
booking_date: str # Booking date (ISO format)
valuta_date: str # Value date (ISO format)
amount: AmountValue # Transaction amount
booking_key: str # Transaction type code
remittance_info: Optional[str] # Payment purpose
creditor_id: Optional[str] # Creditor identifier
mandate_reference: Optional[str] # SEPA mandate reference
# ... and more fields
Example:
tx = transactions[0]
print(tx.booking_date) # "2024-11-09"
print(tx.amount.value) # -12.50 (negative = debit)
print(tx.amount.unit) # "EUR"
print(tx.booking_key) # "DIRECT_DEBIT"
print(tx.remittance_info) # "SPC*Mandragora Bochum"
AmountValue
@dataclass
class AmountValue:
value: float # Numeric value
unit: str # Currency code (EUR, USD, etc.)
Example:
amount = balance.balance
print(f"{amount.value} {amount.unit}") # "1234.56 EUR"
Error Handling
The library provides specific exceptions for different error scenarios, all defined in comdirect_client.exceptions:
Exception Types
from comdirect_client.exceptions import (
AuthenticationError, # Invalid credentials or auth failure
ValidationError, # Invalid request parameters (422)
ServerError, # Server error on API side (500)
TANTimeoutError, # TAN approval timeout (60 seconds)
TokenExpiredError, # Token expired and refresh failed
SessionActivationError, # Session activation failed
AccountNotFoundError, # Account UUID doesn't exist
NetworkTimeoutError, # Network request timeout
)
Exception Hierarchy:
ComdirectAPIError (base)
├── AuthenticationError
├── ValidationError
├── ServerError
├── AccountNotFoundError
├── TANTimeoutError
├── SessionActivationError
├── TokenExpiredError
└── NetworkTimeoutError
Logging
The library uses Python's standard logging module with comprehensive logging throughout the codebase.
Configure Logging
import logging
# Basic configuration
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)
# Get logger for specific module
logger = logging.getLogger("comdirect_client.client")
logger.setLevel(logging.DEBUG) # Set DEBUG for detailed logs
Log Levels
| Level | Usage | Example |
|---|---|---|
| DEBUG | Detailed technical info | Request ID: 123456789 |
| INFO | High-level flow events | Authentication successful |
| WARNING | Recoverable issues | Token refresh failed - token expired |
| ERROR | Critical failures | Authentication failed: Invalid credentials |
License
MIT License - see LICENSE file for details
Disclaimer
This is an unofficial client library. Use at your own risk. The authors are not affiliated with Comdirect Bank AG.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file comdirect_client-0.1.0.tar.gz.
File metadata
- Download URL: comdirect_client-0.1.0.tar.gz
- Upload date:
- Size: 28.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.11.5 Linux/6.14.0-34-generic
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e065538cf368c56eddbeff2d8b29a78867a65360ae60f0848e2b4bce80b7e0ef
|
|
| MD5 |
8d675d91755bdeeb68bfb628bc1be21a
|
|
| BLAKE2b-256 |
f3fe538c4d8d13cdc1d768ebde39ffa866f1113f3a1c8707f6936093f7034f1b
|
File details
Details for the file comdirect_client-0.1.0-py3-none-any.whl.
File metadata
- Download URL: comdirect_client-0.1.0-py3-none-any.whl
- Upload date:
- Size: 22.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/2.2.1 CPython/3.11.5 Linux/6.14.0-34-generic
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
90982bd78772117d1986f1c390e4e6ec25b6abbf0d0b3d8677f7f798e73bc1b3
|
|
| MD5 |
ddc1b44766ef285e5b5b9d462337e9c4
|
|
| BLAKE2b-256 |
79d3ef093503915defcc83f5f3aa27fc7a3ab4f67098fd6e0382f6dd813a258d
|