0

I am faced with an issue where I am trying to convert a string (in db) to a list of decimals.

My code: `

@hybrid_property
def decimals(self) -> list[Decimal]:
    return [Decimal(dec) for dec in self._decimals.replace('d', '').split(';')]


@decimals.setter
def decimals(self, decimals):
    self._decimals = ';'.join(str(dec) for dec in decimals)

`

I get an error 'decimals' is an invalid keyword argument

Stacktrace:

self = <app.models.models.Test_Db object at 0x000001F2A5EAA2F0>
kwargs = {'id': 0, 'connection': 'D', 'fron': 'loop feed', 'hasRail': False, ...}
cls_ = <class 'app.models.models.Test_Db'>, k = 'decimals'

    def _declarative_constructor(self, **kwargs):
        """A simple constructor that allows initialization from kwargs.
    
        Sets attributes on the constructed instance using the names and
        values in ``kwargs``.
    
        Only keys that are present as
        attributes of the instance's class are allowed. These could be,
        for example, any mapped columns or relationships.
        """
        cls_ = type(self)
        for k in kwargs:
            if not hasattr(cls_, k):
>               raise TypeError(
                    "%r is an invalid keyword argument for %s" % (k, cls_.__name__)
                )
E               TypeError: 'decimals' is an invalid keyword argument for Test_Db

..\env\lib\site-packages\sqlalchemy\orm\decl_base.py:1154: TypeError
snakecharmerb
  • 47,570
  • 11
  • 100
  • 153
  • 1
    @snakecharmerb Should there be some sort of a hybrid expression that needs to be added? – javaNoob2792 Dec 30 '22 at 08:29
  • Yes, I think you need an expression if you want to query using `decimals`. What is the actual RDBMS that you are targetting (the expression may have to be specific to the implementation)? – snakecharmerb Dec 30 '22 at 09:26
  • Thanks @snakecharmerb I am using mssql, thanks – javaNoob2792 Dec 30 '22 at 09:54
  • @snakecharmerb Still struggling to write the expression -- could you please help? Thanks. – javaNoob2792 Dec 30 '22 at 10:23
  • Unfortunately I don't have access to an MSSQL server :-( – snakecharmerb Dec 30 '22 at 10:25
  • @snakecharmerb @javaNoob2792 looks like an issue with using the `decimals.setter` on `__init__`, I don't think this is supported in python in general. – ljmc Dec 30 '22 at 10:26
  • @ljmc it can be worked around by using a custom `__init__`, but there will still be problems if the `decimals` attribute is used in a query (I created an answer showing how to do it before realising that querying was still broken). The OP needs an expression that will cast the string of semi-colon separated strings to an array of `NUMERIC` or similar, and then somehow use those to query I would think. – snakecharmerb Dec 30 '22 at 10:30
  • [This](https://stackoverflow.com/a/51421150/5320906) is similar, but I'm not sure how useful it is for this issue. – snakecharmerb Dec 30 '22 at 10:31
  • @javaNoob2792 are you trying to do the conversion on the DB side ? If so, either [edit] your question, or more appropriately, ask another more detailed question. – ljmc Dec 30 '22 at 10:44
  • Thanks guys. So to be clear: In my db, decimals(incorectly named) is a string (eg: "-5.0;-2.5d;0;2.5d;5.0d". I am trying to convert it to a list like [ -5.0, -2.5, 0, 2.5, 5.0 ] to be displayed in the UI and also accept the same from the UI. But while storing it back -- I am converting it to string. Lemme know if this is still confusing. – javaNoob2792 Dec 30 '22 at 11:23

1 Answers1

0

What you're trying to acheive is functionaly similar to this:

class Bar:
    def __init__(self, _a):
        self._a = _a

    @property
    def a(self):
        return self._a

    @a.setter
    def a(self, a):
        self._a = a

bar = Bar(a=1)  # TypeError: Bar.__init__() got an unexpected keyword argument 'a'

The __init__ function of Bar knows nothing about a, only _a.

Same thing happens when you're trying to __init__ your SQLAlchemy mapped class with a property rather than the mapped attribute.

Demo:

from decimal import Decimal
from sqlalchemy import Column, Integer, String, create_engine, select
from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy.orm import Session, declarative_base

Base = declarative_base()

class Foo(Base):
    __tablename__ = "foo"
    id = Column(Integer, primary_key=True)
    _decimals = Column(String, nullable=False)

    @hybrid_property
    def decimals(self) -> list[Decimal]:
        return [
            Decimal(dec) for dec in self._decimals.replace("d", "").split(";")
        ]

    @decimals.setter
    def decimals(self, decimals: list[Decimal]) -> None:
        self._decimals = ";".join(str(d) for d in decimals)


engine = create_engine("sqlite://", future=True, echo=True)

Base.metadata.create_all(engine)

with Session(bind=engine) as session:
    session.add(Foo(_decimals="d1.0;d2.0;d3.0"))
    session.commit()  # OK

with Session(bind=engine) as session:
    session.add(Foo(decimals=[Decimal("4.0"),Decimal("5.0"),Decimal("6.0"))
    session.commit()  # TypeError

with Session(bind=engine) as session:
    result = session.scalars(select(Foo)).first()
    print(result.decimals)  # [Decimal('1.0'), Decimal('2.0'), Decimal('3.0')]
ljmc
  • 4,830
  • 2
  • 7
  • 26
  • Thanks a mill for the reply @ljmc. Unfortunately this is still not working and getting the same error as above. I am getting the data from the pydantic model and creating the foo model as below: Foo(**data) Where data is {id=5, decimals=[ -5.0, -2.5, 0, 2.5, 5.0 ]} – javaNoob2792 Dec 30 '22 at 11:17
  • That's your issue, `__init__` doesn't know how to interpret the `decimals` kwarg. – ljmc Dec 30 '22 at 12:54
  • You need a translation layer between your pydantic model and the stored model, that's fairly common. – ljmc Dec 30 '22 at 12:55
  • Am afraid its "not" my issue -- since I have defined exactly another hybrid property in the same class and it works fine. Its to do something with Decimals – javaNoob2792 Dec 30 '22 at 14:03
  • Please feel free to produce an MRE of your working property and [edit] your post, but the error in your question is about `__init__` not understanding the keyword `decimals`. – ljmc Dec 30 '22 at 14:06