Reputation: 1297
I’m using Flask’s session storage to temporarily save a list of dataclass objects. Here’s an example of the Transaction class and my TransactionMemoryRepository:
@dataclass
class Transaction:
transaction_date: datetime.date
amount: decimal.Decimal
concept: str
The repository has methods to save and retrieve transactions from the session:
from flask import session
class TransactionMemoryRepository:
@staticmethod
def save_transactions(transactions: list[Transaction]):
session['transactions'] = transactions
@staticmethod
def get_transactions() -> list[Transaction]:
tmp_transactions = session.get('transactions', [])
transactions = []
for tmp_transaction in tmp_transactions:
transaction = Transaction(
transaction_date=datetime.strptime(tmp_transaction['transaction_date'], '%a, %d %b %Y %H:%M:%S %Z'),
amount=Decimal(tmp_transaction['amount']),
concept=tmp_transaction['concept'],
category=tmp_transaction.get('category'),
id=tmp_transaction.get('id')
)
transactions.append(transaction)
return transactions
The issue is that in real execution, Flask’s session storage saves the list of Transaction objects as a list of dictionaries. This is why I need to map each dictionary back to a Transaction object when reading from the session.
However, during tests using test_request_context, the behavior is different: The session stores the objects as actual Transaction instances, which causes the read method to fail with the error:
TypeError: 'Transaction' object is not subscriptable
Here’s my test setup using pytest:
@pytest.fixture
def flask_app():
app = Flask(__name__)
app.secret_key = "test_secret_key"
return app
@pytest.fixture
def flask_request_context(flask_app):
with flask_app.test_request_context():
yield
Then, I use this fixture on my test:
def test_save_and_get_transactions(self, flask_request_context):
transactions = [
Transaction(amount=Decimal(100), concept="Concept 1",
transaction_date=datetime.now()),
Transaction(amount=Decimal(200), concept="Concept 2",
transaction_date=datetime.now())
]
TransactionMemoryRepository.save_transactions(transactions)
result = TransactionMemoryRepository.get_transactions()
#asserts ...
The issue: In production, session['transactions'] becomes a list of dictionaries, but during tests, it stores actual Transaction objects. As a result, the get_transactions() method works fine in the real application but fails in tests, because I'm accessing attributes as if they were dictionaries.
Question:
Why is there a difference between how Flask's session behaves during real execution and in tests using test_request_context? How can I ensure the session behaves the same way in both environments so that my tests reflect the actual behavior?
The temporary solution, for now, is mapping to a dict when save but this is a workaround.
I’m attaching two images to show the debugging results. You can see that during normal execution, the session returns a Transaction object with properly typed attributes, while during tests, it returns a dict.
Upvotes: 0
Views: 20
Reputation: 1129
In production, the session payload is serialized and deserialized (which converts objects into primitives like dictionaries). During tests, no serialization/deserialization takes place, and objects are stored directly in memory.
Update the repository methods (save_transactions and get_transactions) as follows:
from decimal import Decimal
from datetime import datetime
from flask import session
class TransactionMemoryRepository:
@staticmethod
def save_transactions(transactions: list['Transaction']):
# Convert Transaction instances to dictionaries before saving to session
session['transactions'] = [t.__dict__ for t in transactions]
@staticmethod
def get_transactions() -> list['Transaction']:
tmp_transactions = session.get('transactions', [])
transactions = []
for tmp_transaction in tmp_transactions:
transaction = Transaction(
transaction_date=datetime.strptime(tmp_transaction['transaction_date'], '%Y-%m-%d'),
amount=Decimal(tmp_transaction['amount']),
concept=tmp_transaction['concept']
)
transactions.append(transaction)
return transactions
In both production and test environments, a session will now always contain serialized structures (dictionary lists) to ensure consistency. And to test
def test_save_and_get_transactions(self, flask_request_context):
# Arrange
transactions = [
Transaction(amount=Decimal(100), concept="Concept 1", transaction_date=datetime.now()),
Transaction(amount=Decimal(200), concept="Concept 2", transaction_date=datetime.now())
]
# Act
TransactionMemoryRepository.save_transactions(transactions)
result = TransactionMemoryRepository.get_transactions()
# Assert
assert len(result) == 2
assert result[0].amount == Decimal(100)
assert result[0].concept == "Concept 1"
Upvotes: 0