#๐Ÿ”’ Ways to avoid side effects when importing

59 messages ยท Page 1 of 1 (latest)

north fulcrum
#

I am new to python coming from a java background so I am having a lot of head scratching moments. One of them is avoiding side effects when importing scripts. I started using Flask for a project and I have a piece of code that looks like this:

db = SQLAlchemy()

class Users(db.Model, UserMixin):
    user_id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), nullable=False, unique=True)
    password = db.Column(db.String(80), nullable=False)

    def get_id(self):
        return self.user_id

now, here db is just smack dab in the start of the script just sitting there menacingly. I don't really like that, not sure if it's just something normal in python? Right now I started doing this:

def get_user_descriptor(db: SQLAlchemy):


   class Users(db.Model, UserMixin):
       user_id = db.Column(db.Integer, primary_key=True)
       username = db.Column(db.String(50), nullable=False, unique=True)
       password = db.Column(db.String(80), nullable=False)
       root_folder = db.Column(db.String(50), nullable=False, unique=True)

       def get_id(self):
           return self.user_id
   return Users

and from there I use it as I see fit. This, however, also feels a little weird.
Is it just me? And if it's not, what is an appropriate way to deal with this?

spice zephyrBOT
#

@north fulcrum

Python help channel opened

Remember to:

  • Ask your Python question, not if you can ask or if there's an expert who can help.
  • Show a code sample as text (rather than a screenshot) and the error message, if you've got one.
  • Explain what you expect to happen and what actually happens.

:warning: Do not pip install anything that isn't related to your question, especially if asked to over DMs.

north fulcrum
#

Ways to avoid side effects when importing

errant elm
#

You don't bind the SQLAlchemy object (flask db.init_app(app)), so it's effectively side-effect free

north fulcrum
#

well, yeah but it's also kind of a weird workaround I feel

errant elm
#

side-effects usually means it doesn't make any changes outside of the module, like changing a value or writing to stdout or a file

north fulcrum
#

ah you mean in the first code

errant elm
#

every time you import it, the result won't change because of some outside state

#

no matter what the order of imports is

north fulcrum
#

well imagine for a second that there is no init_app and I have to initialize something in place

errant elm
#

then that would have side-effects.

north fulcrum
#

not talking specifically about the SQLAlchemy object, but in general

north fulcrum
errant elm
#

make a non-pure function you can call during startup?

north fulcrum
#

okay but then everything is contained inside that function

errant elm
#

anything to speed up the import time

#

I said non-pure function. That's basically what db.init_app() is.

north fulcrum
#

Imagine I am using a library where the object I need access to gets initialized in the constructor

#

or the init method I suppose

#

it just gets initialized straight up

#

if I place that object inside a function to avoid side effects

#

I can't catch a reference to that object outside of the function

#

or should I just place a variable at the beginning of the script, set it to None and initialize it ? Is that what you mean

visual flint
#

so really no need to put the class definition inside a function

north fulcrum
visual flint
errant elm
visual flint
#

but in this case i can't see that being a problem

errant elm
#

it's also technically possible to inherit from a function call

north fulcrum
#

foo.py:

from flask import Flask

app = Flask(__name__)

@app.route("/foo")
def foo():
    pass

bar.py:

from foo import app

@app.route("/bar")
def bar():
    pass

Now, imagine I have to switch out the app object in bar.py for some unit test or some other reason entirely. I went through some options:

Option one: I tried putting the bar function in a class:

from flask import Flask


class Bar:
    def __init__(self,app:Flask):
        self.app = app       
    @app.route("/bar")
    def bar(self):
        pass

This did not work, because I can't use the decorator.
I want through some other things but finally, I landed on this:

from flask import Flask


def bar_descriptor(app:Flask):
    class Bar:

        @app.route("/bar")
        def bar(self):
            pass
    return Bar

  

This works, but feels weird.

#

*forgot to remove self.app from the init method

visual flint
#

you can use decorators on methods in classes
it's just that this decorator isn't made to work that way
this decorator is made to run when python loads the code to register it in a kind of event handler
but when the decorator is applied to a normal method of a class i don't think it will run until something calls that method on the class or a class instance

north fulcrum
#

and this is quite literally my use case

torpid raft
#

a syntax error? it looks like valid syntax to me...?

north fulcrum
visual flint
#

it's even quite normal to put decorators on class methods for different reasons
but those decorators are often made to be used that way from the beginning

torpid raft
north fulcrum
#
from flask import Flask


class Bar:
    def __init__(self,app:Flask):
       self.app = app
    @app.route("/bar")
    def bar(self):
        pass

This code generates this compiler error:

Traceback (most recent call last):
  File "/home/iexavl/PycharmProjects/testing/one.py", line 4, in <module>
    class Bar:
  File "/home/iexavl/PycharmProjects/testing/one.py", line 7, in Bar
    @app.route("/bar")
NameError: name 'app' is not defined
visual flint
errant elm
errant elm
#

!d flask.Blueprint

spice zephyrBOT
#

class flask.Blueprint(name, import_name, static_folder=None, static_url_path=None, template_folder=None, url_prefix=None, subdomain=None, url_defaults=None, root_path=None, cli_group=_sentinel)```
torpid raft
visual flint
north fulcrum
#

well this does look like it will solve the issue in my case which is nice

#

although I am still wondering if there is some general principle way of dealing with this

visual flint
#

many times decorators are not stored in variables but imported directly from a module
in that case you can use the decorator directly in the file that has imported it

north fulcrum
#

right, but what if there is another case like this and blueprints aren't there to save me

visual flint
north fulcrum
#

right. So to summarize I should declare whatever I am going to use like:
db = SQLAlchemy() (globally in the script) and initialize it when I am initializing the Flask app
And for the decorators, I should use blueprints (which are also global in the script)

visual flint
north fulcrum
#

honestly can't say I am a fan of that, but I suppose it's fine

#

thanks

spice zephyrBOT
#
Python help channel closed using Discord native close action

This help channel has been closed. Feel free to create a new post in #1035199133436354600. To maximize your chances of getting a response, check out this guide on asking good questions.