2

The classes works when separate, now, I want to create relationships between them. However, this has caused issues when I am trying to define the relationships.

For example:

sqlalchemy.exc.ArgumentError: relationship 'transactions' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)

I've also had problems with the error 'users is not defined' from the line

user: db.Mapped['users'] = db.relationship(back_populates='portfolio_entries')

Here is the Transactions and PortfolioEntry class within models.py within the trading folder:

from .. import db
from datetime import datetime
from sqlalchemy.sql import func 

class Transaction(db.Model):
    __tablename__ = 'transactions'
    id: db.Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
    date_created: db.Mapped[datetime] = db.mapped_column(
        db.DateTime(timezone=True), 
        nullable=False, 
        server_default=func.now()
    )
    symbol: db.Mapped[str] = db.mapped_column(db.String(10), nullable=False)
    shares: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False, default=0)
    price: db.Mapped[float] = db.mapped_column(db.Float, nullable=False, default=0.0)
    transaction_type: db.Mapped[str] = db.mapped_column(db.String(4), nullable=False)

    user_id: db.Mapped[int]= db.mapped_column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    user: db.Mapped['users'] = db.relationship(back_populates='transactions')

    @property
    def cost(self):
        return abs(self.shares * self.price)

class PortfolioEntry(db.Model):
    __tablename__ = 'portfolio_entries'

    id: db.Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
    symbol: db.Mapped[str] = db.mapped_column(db.String(10), nullable=False)
    shares: db.Mapped[int] = db.mapped_column(db.Integer, nullable=False)
    cost: db.Mapped[float] = db.mapped_column(db.Float, nullable=False)
    price: db.Mapped[float] = db.mapped_column(db.Float, nullable=False)
    
    user_id: db.Mapped[int] = db.mapped_column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    user: db.Mapped['users'] = db.relationship(back_populates='portfolio_entries')

    @property
    def value(self) -> float:
        return self.shares * self.price
        
    @property
    def gain(self) -> float:
        return self.value - self.cost

Here is the Users class within models.py within the Users folder:

from .. import db
from flask_bcrypt import (
    generate_password_hash, 
    check_password_hash
)
from flask_login import UserMixin
from typing import List


class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id: db.Mapped[int] = db.mapped_column(db.Integer, primary_key=True)
    username: db.Mapped[str] = db.mapped_column(db.String(15), nullable=False)
    email: db.Mapped[str] = db.mapped_column(db.String(320), unique=True)
    password_hash: db.Mapped[str] = db.mapped_column(db.String(60), nullable=False)

    transactions: db.Mapped[List['transactions']] = db.relationship(back_populates='users')
    portfolio_entries: db.Mapped[List['portfolio_entries']] = db.relationship(back_populates='users')

    @property
    def password(self):
        raise AttributeError('writeonly attr: password')

    @password.setter
    def password(self, value):
        self.password_hash = generate_password_hash(value)

    def verify_password(self, password):
        return check_password_hash(self.password_hash, password)

Full error messages:

Traceback (most recent call last):
  File "/workspaces/stockx/bin/flask", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 1131, in main
    cli.main()
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 1363, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 1830, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 1226, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 794, in invoke
    return callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/decorators.py", line 93, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 794, in invoke
    return callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 979, in run_command
    raise e from None
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 963, in run_command
    app: WSGIApplication = info.load_app()  # pyright: ignore
                           ^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 349, in load_app
    app = locate_app(import_name, name)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 264, in locate_app
    return find_app_by_string(module, app_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 178, in find_app_by_string
    app = attr(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/src/stockx/__init__.py", line 29, in create_app
    user = User(
           ^^^^^
  File "<string>", line 4, in __init__
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/state.py", line 566, in _initialize_instance
    manager.dispatch.init(self, args, kwargs)
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/event/attr.py", line 497, in __call__
    fn(*args, **kw)
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 4410, in _event_on_init
    instrumenting_mapper._check_configure()
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 2401, in _check_configure
    _configure_registries({self.registry}, cascade=True)
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 4218, in _configure_registries
    _do_configure_registries(registries, cascade)
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 4259, in _do_configure_registries
    mapper._post_configure_properties()
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/mapper.py", line 2418, in _post_configure_properties
    prop.init()
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/interfaces.py", line 595, in init
    self.do_init()
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/relationships.py", line 1655, in do_init
    self._setup_entity()
  File "/workspaces/stockx/lib/python3.12/site-packages/sqlalchemy/orm/relationships.py", line 1885, in _setup_entity
    raise sa_exc.ArgumentError(
sqlalchemy.exc.ArgumentError: relationship 'transactions' expects a class or a mapper argument (received: <class 'sqlalchemy.sql.schema.Table'>)

Even when I change users (tablename) to User (class name) I still receive the issue that User is undefined. Regardless of whether or not I import the class (from stockx.trading.models import Transaction)

Full error:

Traceback (most recent call last):
  File "/workspaces/stockx/bin/flask", line 8, in <module>
    sys.exit(main())
             ^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 1131, in main
    cli.main()
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 1363, in main
    rv = self.invoke(ctx)
         ^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 1830, in invoke
    return _process_result(sub_ctx.command.invoke(sub_ctx))
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 1226, in invoke
    return ctx.invoke(self.callback, **ctx.params)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 794, in invoke
    return callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/decorators.py", line 93, in new_func
    return ctx.invoke(f, obj, *args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/click/core.py", line 794, in invoke
    return callback(*args, **kwargs)
           ^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 979, in run_command
    raise e from None
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 963, in run_command
    app: WSGIApplication = info.load_app()  # pyright: ignore
                           ^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 349, in load_app
    app = locate_app(import_name, name)
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 264, in locate_app
    return find_app_by_string(module, app_name)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/lib/python3.12/site-packages/flask/cli.py", line 178, in find_app_by_string
    app = attr(*args, **kwargs)
          ^^^^^^^^^^^^^^^^^^^^^
  File "/workspaces/stockx/src/stockx/__init__.py", line 24, in create_app
    from .users.models import User
  File "/workspaces/stockx/src/stockx/users/__init__.py", line 2, in <module>
    from .models import User
  File "/workspaces/stockx/src/stockx/users/models.py", line 8, in <module>
    from stockx.trading.models import Transaction
  File "/workspaces/stockx/src/stockx/trading/models.py", line 4, in <module>
    from stockx.users.models import User
ImportError: cannot import name 'User' from partially initialized module 'stockx.users.models' (most likely due to a circular import) (/workspaces/stockx/src/stockx/users/models.py)
1
  • Take a look at pypi.org/project/sqlacodegen - see how it does it for a database schema - and does the grunt work. You might need to dig around a bit to find appropriate 1.4 version if you use that - as I think it's been updated to support 2.0. Commented Nov 18 at 6:59

1 Answer 1

0

You can find documentation for all possible relationship types here.

To define a relationship, you should distinguish between table, class, and attribute.
For a foreign key, you refer to the primary key column of the referenced table. Therefore, you use the table name followed by a period and the attribute that defines the primary key.
For the relationship, you use the name of the referenced class in the typing, and for the back_population attribute, you use the attribute in the referenced class, which represents the backreference of the relationship.

class User(db.Model, UserMixin):
    __tablename__ = 'users'

    # ...

    transactions: db.Mapped[List['Transaction']] = db.relationship(back_populates='user')
    portfolio_entries: db.Mapped[List['PortfolioEntry']] = db.relationship(back_populates='user')

    # ...


class Transaction(db.Model):
    __tablename__ = 'transactions'

    # ...

    user_id: db.Mapped[int]= db.mapped_column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    user: db.Mapped['User'] = db.relationship(back_populates='transactions')

    # ...

class PortfolioEntry(db.Model):
    __tablename__ = 'portfolio_entries'

    # ...

    user_id: db.Mapped[int] = db.mapped_column(db.Integer, db.ForeignKey('users.id'), nullable=False)
    user: db.Mapped['User'] = db.relationship(back_populates='portfolio_entries')

    # ...
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.