sqlalchemyflask-sqlalchemyhy

Hylang & SQLAlchemy: sqlalchemy.exc.ArgumentError: Mapper Mapper[Stream(stream)] could not assemble any primary key columns for mapped table 'stream'


I am trying to get Hylang and SQLAlchemy to cooperate, but I can't seem to do it. I think it has something to do with how Flask-SQLAlchemy uses metaclasses to parse the database model, but I am not 100% sure. I tried creating a dictionary of all the defcolumns (which is defined below), but that gave me the same error. Here is my code:

(require hyrule [assoc defmacro/g!])
(import os)
(import subprocess)
(import sys)
(import flask [Flask redirect render_template request url_for])
(import flask-sqlalchemy [SQLAlchemy])
(import flask-migrate [Migrate])

(setv app (Flask __name__))
(setv path (os.path.join (os.path.expanduser "~/dbs") "development.db"))
(assoc app.config "SQLALCHEMY_DATABASE_URI" f"sqlite:///{path}")
(assoc app.config "SQLALCHEMY_TRACK_MODIFICATIONS" False)
(setv db (SQLAlchemy app))
(setv migrate (Migrate app db))

(defmacro defcolumn [name _column type #* args]
  (if args
      `(do {~name (db.Column ~type #** {~@args})})
      `{~name (db.Column ~type)}))

(defclass Stream [db.Model]
  (defcolumn "id" db.Column db.Integer "primary_key" True)
  (defcolumn "name" db.Column (db.String 240) "unique" True "nullable" False)
  (defcolumn "url" db.Column (db.String 240) "unique" True "nullable" False)
  (defcolumn "currently-playing" db.Column db.Boolean "unique" False "default" False)
  (defcolumn "created" db.Column
    (db.DateTime :timezone True)
    "unique" False
    "default" (db.func.now))

  (defcolumn "updated" db.Column
    (db.DateTime :timezone True)
    "unique" False
    "default" (db.func.now))

  (defn __init__ [self name url currently-playing]
    (setv self.name name)
    (setv self.url url)
    (setv self.currently-playing currently-playing))

  (defn __repr__ [self]
    f"<Stream {self.name}>"))

And here is the error. As you can see, SQLAlchemy isn't getting the primary_key key from the kwargs I am passing in:

danieljaouen on Daniels-MBP at …/pi-radio-hy via main (?)
 hy app.hy
Traceback (most recent call last):
  File "/opt/homebrew/bin/hy", line 8, in <module>
    sys.exit(hy_main())
             ^^^^^^^^^
  File "<frozen runpy>", line 286, in run_path
  File "<frozen runpy>", line 98, in _run_module_code
  File "<frozen runpy>", line 88, in _run_code
  File "/Users/danieljaouen/src/pi-radio-hy/app.hy", line 25, in <module>
    (defclass Stream [db.Model]
     ^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/flask_sqlalchemy/model.py", line 92, in __init__
    super().__init__(name, bases, d, **kwargs)
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/flask_sqlalchemy/model.py", line 144, in __init__
    super().__init__(name, bases, d, **kwargs)
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/decl_api.py", line 196, in __init__
    _as_declarative(reg, cls, dict_)
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/decl_base.py", line 247, in _as_declarative
    return _MapperConfig.setup_mapping(registry, cls, dict_, None, {})
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/decl_base.py", line 328, in setup_mapping
    return _ClassScanMapperConfig(
           ^^^^^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/decl_base.py", line 582, in __init__
    self._early_mapping(mapper_kw)
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/decl_base.py", line 369, in _early_mapping
    self.map(mapper_kw)
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/decl_base.py", line 1957, in map
    mapper_cls(self.cls, self.local_table, **self.mapper_args),
    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<string>", line 2, in __init__
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/util/deprecations.py", line 281, in warned
    return fn(*args, **kwargs)  # type: ignore[no-any-return]
           ^^^^^^^^^^^^^^^^^^^
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 853, in __init__
    self._configure_pks()
  File "/opt/homebrew/Cellar/hy/0.28.0/libexec/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 1637, in _configure_pks
    raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: Mapper Mapper[Stream(stream)] could not assemble any primary key columns for mapped table 'stream'

This is my first time trying to translate an app from Python to Hylang, so any help here is greatly appreciated.


Solution

  • I'm not familiar with SQLAlchemy, but if I follow your code correctly, the form

    (defcolumn "id" db.Column db.Integer "primary_key" True)
    

    expands to

    (do {"id" (db.Column db.Integer #** {"primary_key" True})})
    

    which is unlikely to do anything useful, since you're constructing a literal dictionary ({"id" …}) and then throwing it away. Perhaps you mean to use (setv "id" …) instead.