Repository navigation
fix(database): use naive datetime to match DB column type - #4
Conversation
Changed datetime.now(timezone.utc) to datetime.now() in models and CRUD to resolve asyncpg error: can't subtract offset-naive and offset-aware datetimes The DB columns are TIMESTAMP WITHOUT TIME ZONE, so the app must use naive datetimes to be compatible.
📝 WalkthroughWalkthroughThe changes switch from UTC-aware datetimes ( Changes
Estimated code review effort🎯 2 (Simple) | ⏱️ ~8 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@app/database/crud.py`:
- Line 84: Add a new helper function utc_now_naive() in app/database/models.py
that returns datetime.now(timezone.utc).replace(tzinfo=None), export it, then
update the created_at and updated_at Column defaults in models.py to use
utc_now_naive; next, import utc_now_naive into app/database/crud.py and replace
all usages of datetime.now() (specifically the assignments to device.updated_at
and the five other assignments referenced) with utc_now_naive() so both model
defaults and the six CRUD updates consistently use UTC-naive timestamps.
In `@app/database/models.py`:
- Around line 84-85: The created_at and updated_at Field default_factories use
datetime.now() which yields local-naive timestamps; replace them to use a
UTC-naive helper (e.g., utc_now()) that returns
datetime.now(timezone.utc).replace(tzinfo=None) and update the Field defaults
for created_at and updated_at to call that helper; also replace the six similar
occurrences in app/database/crud.py to use the same utc_now() helper so all
stored datetimes are UTC-naive and consistent across deployments.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: d9da8e29-0330-4098-8b08-667c0a849479
📒 Files selected for processing (2)
app/database/crud.pyapp/database/models.py
| setattr(device, key, value) | ||
|
|
||
| device.updated_at = datetime.now(timezone.utc) | ||
| device.updated_at = datetime.now() |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find app -type f -name "*.py" | head -20Repository: Jonathan0823/nexusbus
Length of output: 519
🏁 Script executed:
ls -la app/database/ 2>/dev/null || echo "Directory not found"Repository: Jonathan0823/nexusbus
Length of output: 416
🏁 Script executed:
head -30 app/database/models.py 2>/dev/null || echo "File not found"Repository: Jonathan0823/nexusbus
Length of output: 973
🏁 Script executed:
wc -l app/database/crud.py 2>/dev/null || echo "File not found"Repository: Jonathan0823/nexusbus
Length of output: 89
🏁 Script executed:
cat app/database/models.pyRepository: Jonathan0823/nexusbus
Length of output: 6901
🏁 Script executed:
cat app/database/crud.pyRepository: Jonathan0823/nexusbus
Length of output: 10021
🏁 Script executed:
rg -i "utc_now" app/ 2>/dev/null || echo "No utc_now functions found"Repository: Jonathan0823/nexusbus
Length of output: 91
🏁 Script executed:
rg -i "def.*now" app/database/ 2>/dev/null || echo "No now functions found"Repository: Jonathan0823/nexusbus
Length of output: 448
🏁 Script executed:
rg "timezone" app/database/ -A 2 -B 2 2>/dev/null || echo "No timezone usage found"Repository: Jonathan0823/nexusbus
Length of output: 291
The suggested helper function utc_now_naive() does not exist in the codebase and must be created first.
The concern about datetime.now() using server-local time is valid and needs fixing. However, the suggested fix is incomplete—it references a helper that hasn't been defined yet and doesn't address the same issue in app/database/models.py where created_at and updated_at defaults also use datetime.now().
To fix this properly:
- Add a
utc_now_naive()helper toapp/database/models.pythat returnsdatetime.now(timezone.utc).replace(tzinfo=None)(or equivalent) - Update both models.py defaults and all six crud.py assignments (lines 84, 114, 142, 254, 284, 312) to use this helper
- Ensure the helper is exported and imported consistently
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/database/crud.py` at line 84, Add a new helper function utc_now_naive()
in app/database/models.py that returns
datetime.now(timezone.utc).replace(tzinfo=None), export it, then update the
created_at and updated_at Column defaults in models.py to use utc_now_naive;
next, import utc_now_naive into app/database/crud.py and replace all usages of
datetime.now() (specifically the assignments to device.updated_at and the five
other assignments referenced) with utc_now_naive() so both model defaults and
the six CRUD updates consistently use UTC-naive timestamps.
| created_at: datetime = Field(default_factory=lambda: datetime.now()) | ||
| updated_at: datetime = Field(default_factory=lambda: datetime.now()) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, check the file exists and read the relevant lines with context
cat -n app/database/models.py | head -100Repository: Jonathan0823/nexusbus
Length of output: 4110
🏁 Script executed:
# Check git history for changes to these lines
git log --oneline -n 20 -- app/database/models.pyRepository: Jonathan0823/nexusbus
Length of output: 130
🏁 Script executed:
# Get the diff of the most recent changes to see what changed
git diff HEAD~1 HEAD -- app/database/models.py || git show HEAD:app/database/models.py | head -200Repository: Jonathan0823/nexusbus
Length of output: 6897
🏁 Script executed:
# Search for database column definitions and response models
rg -n "TIMESTAMP|WITHOUT TIME ZONE|ModbusDeviceResponse|PollingTargetResponse" app/database/Repository: Jonathan0823/nexusbus
Length of output: 195
🏁 Script executed:
# Check the previous commit to see what the original implementation was
git show 0afd866 | head -150Repository: Jonathan0823/nexusbus
Length of output: 3409
🏁 Script executed:
# Look for database schema/migrations to understand column types
find . -type f -name "*.sql" -o -name "*migration*" -o -name "*schema*" | head -20Repository: Jonathan0823/nexusbus
Length of output: 94
🏁 Script executed:
# Check if there are any alembic migrations or database config
fd -e py -e sql | grep -E "(migration|alembic|schema)" | head -20Repository: Jonathan0823/nexusbus
Length of output: 231
🏁 Script executed:
# Search for where created_at/updated_at might be used to understand impact
rg "created_at|updated_at" -B 2 -A 2 app/ --max-count=30Repository: Jonathan0823/nexusbus
Length of output: 2760
🏁 Script executed:
# Check the migration files to understand database column definitions
cat migrations/001_initial_setup.py | head -100Repository: Jonathan0823/nexusbus
Length of output: 2429
🏁 Script executed:
# Check the CRUD operations to see how updated_at is being used
cat app/database/crud.py | head -80Repository: Jonathan0823/nexusbus
Length of output: 2426
🏁 Script executed:
# Check if there's any documentation about timestamp expectations
find . -type f -name "*.md" | xargs grep -l "timestamp\|timezone\|utc" 2>/dev/null | head -5Repository: Jonathan0823/nexusbus
Length of output: 209
🏁 Script executed:
# Look for any tests that might clarify expected behavior
find tests -name "*.py" 2>/dev/null | xargs grep -l "created_at\|updated_at" 2>/dev/null | head -3Repository: Jonathan0823/nexusbus
Length of output: 47
🏁 Script executed:
# Search for the database schema definition to confirm TIMESTAMP WITHOUT TIME ZONE
rg "TIMESTAMP" . --type sql --type pyRepository: Jonathan0823/nexusbus
Length of output: 47
🏁 Script executed:
# Check if there's any existing helper function for datetime handling
rg "utc|UTC|timezone" app/database/ -B 2 -A 2Repository: Jonathan0823/nexusbus
Length of output: 291
🏁 Script executed:
# Verify the imports at the top of models.py to understand what's available
head -10 app/database/models.pyRepository: Jonathan0823/nexusbus
Length of output: 295
🏁 Script executed:
# Check app/database/crud.py to see all uses of datetime.now() that were changed
rg "datetime\.now\(\)" app/database/crud.py -B 2 -A 2Repository: Jonathan0823/nexusbus
Length of output: 899
🏁 Script executed:
# Look at the full git commit to see the exact change from UTC to local time
git show 0afd866 app/database/models.py | head -80Repository: Jonathan0823/nexusbus
Length of output: 2719
🏁 Script executed:
# Check if there are any tests that verify timestamp behavior
find . -name "*.py" -path "*/test*" -exec grep -l "datetime\|timestamp" {} \;Repository: Jonathan0823/nexusbus
Length of output: 91
🏁 Script executed:
# Verify the database schema files to understand TIMESTAMP WITHOUT TIME ZONE usage
find . -name "*.py" -path "*migration*" -exec grep -l "TIMESTAMP" {} \;Repository: Jonathan0823/nexusbus
Length of output: 47
🏁 Script executed:
# Check the test file that references datetime
cat tests/unit/test_cache.pyRepository: Jonathan0823/nexusbus
Length of output: 6101
🏁 Script executed:
# Look for any datetime serialization or comparison in the codebase
rg "strftime|isoformat|fromisoformat" app/ --type pyRepository: Jonathan0823/nexusbus
Length of output: 654
🏁 Script executed:
# Check if there's any timezone-related configuration in the app
rg "TZ|timezone|UTC|utc" app/ --type py -B 1 -A 1 | head -50Repository: Jonathan0823/nexusbus
Length of output: 2367
🏁 Script executed:
# Check if there are any other places where datetime is created for the database
rg "datetime\.now\(\)" app/ --type py -B 2 -A 2Repository: Jonathan0823/nexusbus
Length of output: 2328
🏁 Script executed:
# Verify the database schema definition in migrations
cat migrations/001_initial_setup.py | grep -A 50 "modbus_devices"Repository: Jonathan0823/nexusbus
Length of output: 2042
🏁 Script executed:
# Check cache.py to see how it properly uses UTC
cat app/core/cache.py | head -50Repository: Jonathan0823/nexusbus
Length of output: 1570
🏁 Script executed:
# Verify the actual database schema to confirm TIMESTAMP WITHOUT TIME ZONE
cat migrations/base.py | head -80Repository: Jonathan0823/nexusbus
Length of output: 2503
🏁 Script executed:
# Check if there's any SQLAlchemy type definition for the datetime columns
rg "DateTime\|TIMESTAMP" migrations/ app/ --type py -B 2 -A 2Repository: Jonathan0823/nexusbus
Length of output: 47
🏁 Script executed:
# Check the complete CRUD file to understand the full context
wc -l app/database/crud.pyRepository: Jonathan0823/nexusbus
Length of output: 89
🏁 Script executed:
# Search for any datetime comparisons that might be causing the asyncpg error
rg "created_at|updated_at" app/database/crud.py -B 3 -A 3Repository: Jonathan0823/nexusbus
Length of output: 1275
🏁 Script executed:
# Get a clearer picture: count total occurrences of datetime.now() without timezone in database code
rg "datetime\.now\(\)" app/database/ --type py | wc -lRepository: Jonathan0823/nexusbus
Length of output: 67
🏁 Script executed:
# Double-check: are there any database schema definitions showing TIMESTAMP WITHOUT TIME ZONE explicitly?
find . -type f \( -name "*.sql" -o -name "*.py" \) -exec grep -l "TIMESTAMP WITHOUT TIME ZONE\|without time zone" {} \;Repository: Jonathan0823/nexusbus
Length of output: 47
Use UTC-naive timestamps, not local-naive, to preserve correct timestamp semantics.
datetime.now() creates local-wall-clock naive datetimes, which means on a non-UTC deployment (e.g., Asia/Bangkok +07:00), created_at and updated_at will be stored as local time instead of UTC—changing what the values semantically represent. The DB requires naive datetimes, but that doesn't mean you should use local time. Extract a UTC helper that returns datetime.now(timezone.utc).replace(tzinfo=None) so stored timestamps remain consistent across deployments and DST changes.
Suggested fix
from datetime import datetime, timezone
from typing import Optional, Tuple
VALID_FRAMERS: Tuple[str, ...] = ("RTU", "SOCKET", "ASCII")
VALID_REGISTER_TYPES: Tuple[str, ...] = ("holding", "input", "coil", "discrete")
+
+
+def utc_now_naive() -> datetime:
+ """Return the current UTC time without timezone info for TIMESTAMP WITHOUT TIME ZONE columns."""
+ return datetime.now(timezone.utc).replace(tzinfo=None)
class ModbusDevice(SQLModel, table=True):
...
- created_at: datetime = Field(default_factory=lambda: datetime.now())
- updated_at: datetime = Field(default_factory=lambda: datetime.now())
+ created_at: datetime = Field(default_factory=utc_now_naive)
+ updated_at: datetime = Field(default_factory=utc_now_naive)
class PollingTarget(SQLModel, table=True):
...
- created_at: datetime = Field(default_factory=lambda: datetime.now())
- updated_at: datetime = Field(default_factory=lambda: datetime.now())
+ created_at: datetime = Field(default_factory=utc_now_naive)
+ updated_at: datetime = Field(default_factory=utc_now_naive)Also update app/database/crud.py (6 occurrences):
-device.updated_at = datetime.now()
+device.updated_at = utc_now_naive()📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| created_at: datetime = Field(default_factory=lambda: datetime.now()) | |
| updated_at: datetime = Field(default_factory=lambda: datetime.now()) | |
| created_at: datetime = Field(default_factory=utc_now_naive) | |
| updated_at: datetime = Field(default_factory=utc_now_naive) |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@app/database/models.py` around lines 84 - 85, The created_at and updated_at
Field default_factories use datetime.now() which yields local-naive timestamps;
replace them to use a UTC-naive helper (e.g., utc_now()) that returns
datetime.now(timezone.utc).replace(tzinfo=None) and update the Field defaults
for created_at and updated_at to call that helper; also replace the six similar
occurrences in app/database/crud.py to use the same utc_now() helper so all
stored datetimes are UTC-naive and consistent across deployments.
Related issue
Summary
datetime.now(timezone.utc)todatetime.now()in database models and CRUD operationsRoot Cause
TIMESTAMP WITHOUT TIME ZONE(naive)datetime.now(timezone.utc))Scope
app/database/models.py,app/database/crud.pymetrics.py,modbus_client.py, newdocs/docker-compose.ymlTesting
create_polling_target(session, target)with UTC-aware datetimecreate_polling_target(session, target)with naive datetimecreated_at=2026-04-16 08:11:11.544190, tzinfo=NoneReviewer Focus
app/database/crud.py,app/database/models.pyNotes
Summary by CodeRabbit