Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

object has no attribute 'drivername' if using only binds, not default DB #819

Closed
colegleason opened this issue May 13, 2020 · 4 comments
Closed
Milestone

Comments

@colegleason
Copy link

Expected Behavior

I have a flask project with two binds set in SQLALCHEMY_BINDS and with SQLALCHEMY_DATABASE_URI not specified. I would like this to work as long as __bind_key__ is properly specified for each model. I don't want to have a default DB, as I think that might lead to mistakes.

@pytest.fixture
def app():
    db_1_fd, db_1 = tempfile.mkstemp()
    db_2_fd, db_2 = tempfile.mkstemp()
    class TestingConfig(Config):
        DEBUG = True
        TESTING = True
        SQLALCHEMY_BINDS = {
            'db1': 'sqlite:///' + db_1,
            'db2': 'sqlite:///' + db_2,
        }

    app = backend.create_app(TestingConfig)
    with app.app_context():
        db.create_all(bind=['db1', 'db2'])
    yield app

    os.close(db_1_fd)
    os.close(db_2_fd)
    os.unlink(db_1)
    os.unlink(db_2)

def test_get_user(app):
    with app.app_context():
        # no user exists yet
        user = User.query.first()
        assert user == None

Actual Behavior

It attempts to call apply_driver_hacks with sa_url = None. How can I prevent apply_driver_hacks from being called when SQLALCHEMY_DATABASE_URI is not set?

=============================================================== test session starts ================================================================
platform darwin -- Python 3.7.4, pytest-5.4.2, py-1.8.1, pluggy-0.13.1
rootdir: <redacted>
collected 1 item                                                                                                                                   

backend/tests/test_<redacted>.py F                                                                                                                [100%]

===================================================================== FAILURES =====================================================================
__________________________________________________________________ test_get_user ___________________________________________________________________

self = <sqlalchemy.util._collections.ScopedRegistry object at 0x11bc48a50>

    def __call__(self):
        key = self.scopefunc()
        try:
>           return self.registry[key]
E           KeyError: 4576705984

../../.virtualenvs/<redacted>-mTJqo3Um/lib/python3.7/site-packages/sqlalchemy/util/_collections.py:1010: KeyError

During handling of the above exception, another exception occurred:

app = <Flask 'backend'>

    def test_get_user(app):
        with app.app_context():
            # no user exists yet
>           user = User.query.first()

backend/tests/test_<redacted>.py:83: 
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 
../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:518: in __get__
    return type.query_class(mapper, session=self.sa.session())
../../.virtualenvs/<redacted>-mTJqo3Um/lib/python3.7/site-packages/sqlalchemy/orm/scoping.py:78: in __call__
    return self.registry()
../../.virtualenvs/<redacted>-mTJqo3Um/lib/python3.7/site-packages/sqlalchemy/util/_collections.py:1012: in __call__
    return self.registry.setdefault(key, self.createfunc())
../../.virtualenvs/<redacted>-mTJqo3Um/lib/python3.7/site-packages/sqlalchemy/orm/session.py:3213: in __call__
    return self.class_(**local_kw)
../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:138: in __init__
    bind = options.pop('bind', None) or db.engine
../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:929: in engine
    return self.get_engine()
../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:948: in get_engine
    return connector.get_engine()
../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:559: in get_engine
    options = self.get_options(sa_url, echo)
../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:574: in get_options
    self._sa.apply_driver_hacks(self._app, sa_url, options)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ 

self = <SQLAlchemy engine=None>, app = <Flask 'backend'>, sa_url = None, options = {}

    def apply_driver_hacks(self, app, sa_url, options):
        """This method is called before engine creation and used to inject
        driver specific hacks into the options.  The `options` parameter is
        a dictionary of keyword arguments that will then be used to call
        the :func:`sqlalchemy.create_engine` function.
    
        The default implementation provides some saner defaults for things
        like pool sizes for MySQL and sqlite.  Also it injects the setting of
        `SQLALCHEMY_NATIVE_UNICODE`.
        """
>       if sa_url.drivername.startswith('mysql'):
E       AttributeError: 'NoneType' object has no attribute 'drivername'

../../.virtualenvs/<redacted>-mTJqo3Um/src/flask-sqlalchemy/flask_sqlalchemy/__init__.py:869: AttributeError

Environment

@CoburnJoe
Copy link
Contributor

I'd like to see a little more information.
How are you choosing a DB in "normal" operation outside of tests?
Is it possible to share some model code, with the bind key set?

@colegleason
Copy link
Author

Hi, here is an example of a model with the bind key set. All of my models have a bind key set for either one DB or another.

class User(db.Model):
    __bind_key__ = 'twitter'
    id = db.Column(db.String(36), nullable=False, primary_key=True)
    screen_name = db.Column(db.String(36), nullable=True)
    research_team = db.Column(db.Boolean, nullable=False, default=False)
    consent = db.Column(db.Boolean, nullable=False, default=False)
    reminders = db.Column(db.Boolean, nullable=False, default=True)
    sessions = db.relationship("UserSession")
    consent_form = db.relationship("ConsentForm")

@CoburnJoe
Copy link
Contributor

@colegleason I think the quickest way to solve this issue may be to always set a SQLALCHEMY_DATABASE_URI, even if you then choose a bind for every request...

@davidism davidism added this to the 3.0 milestone Sep 18, 2022
@davidism
Copy link
Member

Should be fixed in #1087, which changed how engines are created and configured.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Oct 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Development

No branches or pull requests

3 participants