SQL Alchemy Bugs Resolutions
Issue: SQL table construnction error (multiple errors)
...
surfwatch-server | File "/app/server/main.py", line 3, in <module>
surfwatch-server | from database.database import db_init, get_db
surfwatch-server | File "/app/server/database/database.py", line 10, in <module>
surfwatch-server | from database.models.site import DailyCrowdnessPrediction, RawCrowdnessReading, Site, WeeklyCrowdnessPrediction
surfwatch-server | File "/app/server/database/models/site.py", line 84, in <module>
surfwatch-server | class WaveQualityReading(Base):
...
surfwatch-server | File "/usr/local/lib/python3.9/site-packages/sqlalchemy/orm/mapper.py", line 1652, in _configure_pks
surfwatch-server | raise sa_exc.ArgumentError(
surfwatch-server | sqlalchemy.exc.ArgumentError: Mapper Mapper[WaveQualityReading(wave_quality_readings)] could not assemble any primary key columns for mapped table 'wave_quality_readings'
...
surfwatch-server | sqlalchemy.exc.CompileError: (in table 'wave_quality_readings', column 'wave_direction'): VARCHAR requires a length on dialect mysql
...
surfwatch-server | sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper[Site(sites)]'. Original exception was: Mapper 'Mapper[WaveQualityReading(wave_quality_readings)]' has no property 'sites'. If this property was indicated from other mappers or configure events, ensure registry.configure() has been called.
| server/main.py | |
|---|---|
Primary Key Error
sqlalchemy.exc.ArgumentError: Mapper Mapper[WaveQualityReading(wave_quality_readings)] could not assemble any primary key columns for mapped table 'wave_quality_readings'
- The error indicates that SQLAlchemy is unable to determine the primary key for the
wave_quality_readingstable. - This typically happens when the model does not define a primary key column or the primary key is not correctly mapped.
String datatype in SqlAlchemy
- The
wave_directionfield is defined asMapped[float] = mapped_column(String, nullable=False), which is incorrect. - it will further cause this error:
surfwatch-server | sqlalchemy.exc.CompileError: (in table 'wave_quality_readings', column 'wave_direction'): VARCHAR requires a length on dialect mysql
- The
Stringtype should be used with a length parameter, e.g.,String(255).
Foreign Key Relationship
- A hidden issue is not exposed in the error logs, it is:
surfwatch-server | sqlalchemy.exc.InvalidRequestError: One or more mappers failed to initialize - can't proceed with initialization of other mappers. Triggering mapper: 'Mapper[Site(sites)]'. Original exception was: Mapper 'Mapper[WaveQualityReading(wave_quality_readings)]' has no property 'sites'. If this property was indicated from other mappers or configure events, ensure registry.configure() has been called.
- note in the
Sitemodel, thewave_qualityrelationship is defined aswave_quality = relationship("WaveQualityReading", back_populates="sites", ...) - but in the
WaveQualityReadingmodel, thesitesrelationship is not defined.
Primary Key Error
- SQLAlchemy requires each mapped class to have a primary key defined.
- The primary key can be defined
using
primary_key=Truein themapped_columndefinition. - If no primary key is defined, SQLAlchemy cannot uniquely identify rows in the table.
- source: SQLAlchemy Documentation
String datatype in SqlAlchemy
- In SQLAlchemy, the
Stringtype requires a length parameter when used with MySQL. - Failure to specify a length will result in a
CompileError. - source: SQLAlchemy String Type Documentation
Bidirectional relationships model binding
- In SQLAlchemy, when defining bidirectional relationships, both sides of the relationship must be defined.
- If one side of the relationship is not defined, SQLAlchemy will raise an
InvalidRequestError. - This is because SQLAlchemy needs to know how to navigate the relationship in both directions.
- source: SQLAlchemy Relationships Documentation
SQLAlchemy lazy loading
surfwatch-server | File "/app/server/main.py", line 163, in get_recommendations
surfwatch-server | return await site_controller.get_all_sites(db)
surfwatch-server | File "/app/server/controllers/site_controller.py", line 33, in get_all_sites
surfwatch-server | return [PydanticSite.model_validate(site) for site in sites]
surfwatch-server | File "/app/server/controllers/site_controller.py", line 33, in <listcomp>
surfwatch-server | return [PydanticSite.model_validate(site) for site in sites]
surfwatch-server | File "/usr/local/lib/python3.9/site-packages/pydantic/main.py", line 703, in model_validate
surfwatch-server | return cls.__pydantic_validator__.validate_python(
surfwatch-server | pydantic_core._pydantic_core.ValidationError: 1 validation error for Site
surfwatch-server | wave_quality
surfwatch-server | Error extracting attribute: StatementError: (sqlalchemy.exc.MissingGreenlet) greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?
surfwatch-server | [SQL: SELECT wave_quality_readings.site_id AS wave_quality_readings_site_id, wave_quality_readings.wave_height AS wave_quality_readings_wave_height, wave_quality_readings.wave_speed AS wave_quality_readings_wave_speed, wave_quality_readings.wave_direction AS wave_quality_readings_wave_direction, wave_quality_readings.temperature AS wave_quality_readings_temperature
surfwatch-server | FROM wave_quality_readings
surfwatch-server | WHERE %s = wave_quality_readings.site_id]
surfwatch-server | [parameters: [{'%(134176880071200 param)s': 1}]]
surfwatch-server | (Background on this error at: https://sqlalche.me/e/20/xd2s) [type=get_attribute_error, input_value=<unprintable Site object>, input_type=Site]
surfwatch-server | For further information visit https://errors.pydantic.dev/2.11/v/get_attribute_error
The "greenlet_spawn" error means that SQLAlchemy tried to do an async database operation (like loading a relationship) outside of an async context.
greenlet_spawn has not been called; can't call await_only() here. Was IO attempted in an unexpected place?
It means SQLAlchemy tried to lazy-load a relationship (like wave_quality) after your async session was already done, or outside of an async function.
- SQLAlchemy's async support requires that all database operations are done within an async context.
- Lazy loading relationships in SQLAlchemy can only be done within an async function or coroutine.
- If you try to access a lazy-loaded relationship outside of an async context, it will raise a
MissingGreenleterror. - source: SQLAlchemy Async Documentation
- load the
wave_qualityrelationship eagerly usingselectinloadin the query. - ensure that all database operations are done within an async function or coroutine.
| server/controllers/site_controller.py | |
|---|---|
Sql Alchemy relationships return list instead of single object
surfwatch-server | File "/app/server/controllers/site_controller.py", line 34, in get_all_sites
surfwatch-server | return [PydanticSite.model_validate(site) for site in sites]
surfwatch-server | File "/usr/local/lib/python3.9/site-packages/pydantic/main.py", line 703, in model_validate
surfwatch-server | return cls.__pydantic_validator__.validate_python(
surfwatch-server | pydantic_core._pydantic_core.ValidationError: 5 validation errors for Site
surfwatch-server | wave_quality.site_id
surfwatch-server | Field required [type=missing, input_value=[], input_type=InstrumentedList]
surfwatch-server | wave_quality.wave_height
surfwatch-server | Field required [type=missing, input_value=[], input_type=InstrumentedList]
surfwatch-server | wave_quality.wave_speed
surfwatch-server | Field required [type=missing, input_value=[], input_type=InstrumentedList]
surfwatch-server | wave_quality.wave_direction
surfwatch-server | Field required [type=missing, input_value=[], input_type=InstrumentedList]
surfwatch-server | wave_quality.temperature
surfwatch-server | Field required [type=missing, input_value=[], input_type=InstrumentedList]
The error occurs because the SQLAlchemy relationship wave_quality returns a list by default, but the Pydantic model expects a single object. This mismatch causes validation errors when converting the SQLAlchemy model to the Pydantic model.
Add uselist=False to the wave_quality relationship definition in the Site model to indicate a one-to-one relationship.
This change ensures that wave_quality is a single object, matching the Pydantic model's expectation.