From 6f560fa63394cece1657114601b4dbd6b3f19d76 Mon Sep 17 00:00:00 2001 From: Timeraider <57343973+GitTimeraider@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:46:10 +0200 Subject: [PATCH 1/2] Timezone fixes to avoid future depreciation --- DATETIME_MODERNIZATION.md | 98 +++++++++++++++++++++++++++++++++++++++ app/currency.py | 6 +-- app/models.py | 10 ++-- app/webhooks.py | 10 ++-- 4 files changed, 111 insertions(+), 13 deletions(-) create mode 100644 DATETIME_MODERNIZATION.md diff --git a/DATETIME_MODERNIZATION.md b/DATETIME_MODERNIZATION.md new file mode 100644 index 0000000..7aade5f --- /dev/null +++ b/DATETIME_MODERNIZATION.md @@ -0,0 +1,98 @@ +# DateTime Modernization Update + +## Summary +Updated the Subscription Tracker codebase to use modern Python datetime practices by replacing deprecated `datetime.utcnow()` with `datetime.now(timezone.utc)`. + +## Changes Made + +### โœ… Files Updated + +1. **`app/webhooks.py`** + - Added `timezone` import + - Updated webhook timestamp generation + - Updated last_used timestamp tracking + - Fixed Discord, Slack, and Generic webhook timestamp formats + +2. **`app/currency.py`** + - Added `timezone` import + - Updated cache age calculations for currency rates + - Fixed timezone-aware datetime comparisons + +3. **`app/models.py`** + - Added `timezone` import + - Updated database column defaults for: + - `Webhook.created_at` + - `ExchangeRate.created_at` + - `PaymentMethod.created_at` + - Fixed `ExchangeRate.save_rates()` method + +### ๐Ÿ”ง Technical Details + +#### Before (Deprecated): +```python +datetime.utcnow() # Returns naive datetime in UTC +created_at = db.Column(db.DateTime, default=datetime.utcnow) +``` + +#### After (Modern): +```python +datetime.now(timezone.utc) # Returns timezone-aware datetime in UTC +created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) +``` + +### ๐Ÿ“ Key Improvements + +1. **Timezone Awareness**: All UTC timestamps are now timezone-aware +2. **Future Compatibility**: Avoids deprecation warnings in Python 3.12+ +3. **Database Compatibility**: Lambda functions ensure fresh timestamps per record +4. **Consistency**: Uniform approach across all modules + +### ๐Ÿ” Remaining `datetime.now()` Usage + +The following `datetime.now()` calls remain unchanged as they are used for local time operations: + +- **Display formatting**: User-facing timestamp displays +- **Local date comparisons**: Subscription expiry checks relative to user's local date +- **Health check timestamps**: Application status timestamps +- **Circuit breaker timestamps**: Internal timeout tracking + +These are intentionally kept as local time since they represent user-facing functionality or internal timers. + +### ๐Ÿงช Testing + +After this update, ensure: + +1. **Webhook timestamps** appear correctly in your webhook destinations +2. **Database records** have proper created_at timestamps +3. **Currency rate caching** works correctly with age calculations +4. **No deprecation warnings** appear in Python 3.12+ environments + +### ๐Ÿš€ Migration Notes + +This is a **forward-compatible** change: +- Existing database records are unaffected +- All new records will use timezone-aware timestamps +- No data migration required +- Backward compatible with existing functionality + +### ๐Ÿ“š References + +- [Python datetime documentation](https://docs.python.org/3/library/datetime.html) +- [PEP 615 โ€“ Support for the IANA Time Zone Database](https://peps.python.org/pep-0615/) +- [Python 3.12 datetime deprecations](https://docs.python.org/3.12/whatsnew/3.12.html#deprecated) + +## Verification + +Run the application and verify: +```bash +# No deprecation warnings should appear +docker-compose up --build + +# Test webhook functionality +./test-webhook.sh discord "your-webhook-url" + +# Check database timestamps are timezone-aware +# (New records should have proper UTC timestamps) +``` + +This update ensures the Subscription Tracker remains compatible with current and future Python versions while maintaining all existing functionality. \ No newline at end of file diff --git a/app/currency.py b/app/currency.py index 5dfb5d7..d964899 100644 --- a/app/currency.py +++ b/app/currency.py @@ -1,7 +1,7 @@ import requests import json import os -from datetime import datetime, date +from datetime import datetime, date, timezone from flask import current_app import xml.etree.ElementTree as ET from decimal import Decimal, getcontext, InvalidOperation @@ -60,7 +60,7 @@ def get_exchange_rates(self, base_currency: str = 'EUR', force_refresh: bool = F if not force_refresh and primary_provider: record = ExchangeRate.query.filter_by(date=date.today(), base_currency=base_currency, provider=primary_provider).first() if record: - age_min = (datetime.utcnow() - record.created_at).total_seconds() / 60.0 + age_min = (datetime.now(timezone.utc) - record.created_at).total_seconds() / 60.0 if age_min <= refresh_minutes: try: self.last_provider = primary_provider @@ -79,7 +79,7 @@ def get_exchange_rates(self, base_currency: str = 'EUR', force_refresh: bool = F if not force_refresh: cached = ExchangeRate.query.filter_by(date=date.today(), base_currency=base_currency, provider=provider).first() if cached: - age_min = (datetime.utcnow() - cached.created_at).total_seconds() / 60.0 + age_min = (datetime.now(timezone.utc) - cached.created_at).total_seconds() / 60.0 if age_min <= refresh_minutes: try: self.last_provider = provider diff --git a/app/models.py b/app/models.py index 60e3d17..c5a16a3 100644 --- a/app/models.py +++ b/app/models.py @@ -1,4 +1,4 @@ -from datetime import datetime, date +from datetime import datetime, date, timezone from flask_login import UserMixin from werkzeug.security import generate_password_hash, check_password_hash from app import db, login_manager @@ -62,7 +62,7 @@ class Webhook(db.Model): custom_headers = db.Column(db.Text) # JSON string for custom headers is_active = db.Column(db.Boolean, default=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) + created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) last_used = db.Column(db.DateTime) # Relationship @@ -106,7 +106,7 @@ class ExchangeRate(db.Model): base_currency = db.Column(db.String(3), nullable=False, default='EUR') provider = db.Column(db.String(40), nullable=False, default='legacy') # data source identifier rates_json = db.Column(db.Text, nullable=False) # JSON string of exchange rates - created_at = db.Column(db.DateTime, default=datetime.utcnow) + created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) __table_args__ = ( db.UniqueConstraint('date', 'base_currency', 'provider', name='uq_rate_date_base_provider'), @@ -136,7 +136,7 @@ def save_rates(cls, rates, base_currency='EUR', provider='unknown'): existing_rate = cls.query.filter_by(date=today, base_currency=base_currency, provider=provider).first() if existing_rate: existing_rate.rates_json = json.dumps(rates) - existing_rate.created_at = datetime.utcnow() + existing_rate.created_at = datetime.now(timezone.utc) else: new_rate = cls( date=today, @@ -154,7 +154,7 @@ class PaymentMethod(db.Model): last_four = db.Column(db.String(4), nullable=True) notes = db.Column(db.Text, nullable=True) user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) - created_at = db.Column(db.DateTime, default=datetime.utcnow) + created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) # Relationship back to user user = db.relationship('User', backref=db.backref('payment_methods', lazy=True)) diff --git a/app/webhooks.py b/app/webhooks.py index c125c49..3aabbc2 100644 --- a/app/webhooks.py +++ b/app/webhooks.py @@ -15,7 +15,7 @@ import requests import json import logging -from datetime import datetime +from datetime import datetime, timezone from typing import Dict, List, Optional, Any from flask import current_app @@ -58,7 +58,7 @@ def send(self, message: str, title: str = None, color: str = None) -> Dict[str, response.raise_for_status() # Update last_used timestamp - self.webhook.last_used = datetime.utcnow() + self.webhook.last_used = datetime.now(timezone.utc) from app import db db.session.commit() @@ -111,7 +111,7 @@ def prepare_payload(self, message: str, title: str = None, color: str = None) -> embed = { "description": message, - "timestamp": datetime.utcnow().isoformat(), + "timestamp": datetime.now(timezone.utc).isoformat(), "footer": { "text": "Subscription Tracker" } @@ -151,7 +151,7 @@ def prepare_payload(self, message: str, title: str = None, color: str = None) -> attachment = { "text": message, - "ts": int(datetime.utcnow().timestamp()), + "ts": int(datetime.now(timezone.utc).timestamp()), "footer": "Subscription Tracker" } @@ -239,7 +239,7 @@ class GenericWebhookSender(WebhookSender): def prepare_payload(self, message: str, title: str = None, color: str = None) -> Dict[str, Any]: payload = { "text": message, - "timestamp": datetime.utcnow().isoformat() + "timestamp": datetime.now(timezone.utc).isoformat() } if title: From c01cb5f475d3173e12bec39945a70acff658b639 Mon Sep 17 00:00:00 2001 From: Timeraider <57343973+GitTimeraider@users.noreply.github.com> Date: Mon, 6 Oct 2025 17:46:34 +0200 Subject: [PATCH 2/2] Delete DATETIME_MODERNIZATION.md --- DATETIME_MODERNIZATION.md | 98 --------------------------------------- 1 file changed, 98 deletions(-) delete mode 100644 DATETIME_MODERNIZATION.md diff --git a/DATETIME_MODERNIZATION.md b/DATETIME_MODERNIZATION.md deleted file mode 100644 index 7aade5f..0000000 --- a/DATETIME_MODERNIZATION.md +++ /dev/null @@ -1,98 +0,0 @@ -# DateTime Modernization Update - -## Summary -Updated the Subscription Tracker codebase to use modern Python datetime practices by replacing deprecated `datetime.utcnow()` with `datetime.now(timezone.utc)`. - -## Changes Made - -### โœ… Files Updated - -1. **`app/webhooks.py`** - - Added `timezone` import - - Updated webhook timestamp generation - - Updated last_used timestamp tracking - - Fixed Discord, Slack, and Generic webhook timestamp formats - -2. **`app/currency.py`** - - Added `timezone` import - - Updated cache age calculations for currency rates - - Fixed timezone-aware datetime comparisons - -3. **`app/models.py`** - - Added `timezone` import - - Updated database column defaults for: - - `Webhook.created_at` - - `ExchangeRate.created_at` - - `PaymentMethod.created_at` - - Fixed `ExchangeRate.save_rates()` method - -### ๐Ÿ”ง Technical Details - -#### Before (Deprecated): -```python -datetime.utcnow() # Returns naive datetime in UTC -created_at = db.Column(db.DateTime, default=datetime.utcnow) -``` - -#### After (Modern): -```python -datetime.now(timezone.utc) # Returns timezone-aware datetime in UTC -created_at = db.Column(db.DateTime, default=lambda: datetime.now(timezone.utc)) -``` - -### ๐Ÿ“ Key Improvements - -1. **Timezone Awareness**: All UTC timestamps are now timezone-aware -2. **Future Compatibility**: Avoids deprecation warnings in Python 3.12+ -3. **Database Compatibility**: Lambda functions ensure fresh timestamps per record -4. **Consistency**: Uniform approach across all modules - -### ๐Ÿ” Remaining `datetime.now()` Usage - -The following `datetime.now()` calls remain unchanged as they are used for local time operations: - -- **Display formatting**: User-facing timestamp displays -- **Local date comparisons**: Subscription expiry checks relative to user's local date -- **Health check timestamps**: Application status timestamps -- **Circuit breaker timestamps**: Internal timeout tracking - -These are intentionally kept as local time since they represent user-facing functionality or internal timers. - -### ๐Ÿงช Testing - -After this update, ensure: - -1. **Webhook timestamps** appear correctly in your webhook destinations -2. **Database records** have proper created_at timestamps -3. **Currency rate caching** works correctly with age calculations -4. **No deprecation warnings** appear in Python 3.12+ environments - -### ๐Ÿš€ Migration Notes - -This is a **forward-compatible** change: -- Existing database records are unaffected -- All new records will use timezone-aware timestamps -- No data migration required -- Backward compatible with existing functionality - -### ๐Ÿ“š References - -- [Python datetime documentation](https://docs.python.org/3/library/datetime.html) -- [PEP 615 โ€“ Support for the IANA Time Zone Database](https://peps.python.org/pep-0615/) -- [Python 3.12 datetime deprecations](https://docs.python.org/3.12/whatsnew/3.12.html#deprecated) - -## Verification - -Run the application and verify: -```bash -# No deprecation warnings should appear -docker-compose up --build - -# Test webhook functionality -./test-webhook.sh discord "your-webhook-url" - -# Check database timestamps are timezone-aware -# (New records should have proper UTC timestamps) -``` - -This update ensures the Subscription Tracker remains compatible with current and future Python versions while maintaining all existing functionality. \ No newline at end of file