Best practice for script-wide constants that shouldn't be initialized on module import?

Hey folks,
so this is a question about what more seasoned Python devs would consider best practice in this cas, so that I can use different patterns, if I were to face the same issue in the future. :slight_smile:
For context, we are using the confuse library to manage configurations in different yaml files, per module.
Here comes the issue:
I wrote a scraper, which didn’t use an OOP approach, but rather I tried to make it more ‘functional’-like.
The scraper looked very roughly like so:

from config import Config
SERVER_URL = Config.c["data"]["server_url"].get()
# more constants here 
# ...


def run():
  # do something


def _fetch_data():
  # logic for fetching data


# more methods that handle and store the data...

This was all good and well, until I put everything together in the pipeline script, which would look roughly like so:

from config import Config
from scrapers import scraperFoo

# some setup

config_path = Path(__file__).parent.resolve() / "config.yaml"
Config.set_file(config_path)
Config.set_env(os.environ.get("project", default="local"))

scraperFoo.run()
# do some other stuff

The issue here now is, that the scraperFoo script is imported before the Configuration environment is set, and thus it cannot find any of the configuration files, as the global-level variables are evaluated on import.

I had multiple ideas on how this could be solved, but I didn’t know which is considered to be the most Pythonic, so I hope you guys can shed some light on this.

# Solution 1: simple Getters
def _get_server_path():
  return Config.c["data"]["server_url"].get()

# Solution 2: initialization method

SERVER_URL = None

def _initialize():
  global SERVER_URL
  SERVER_URL = Config.c["data"]["server_url"].get()
  # set other constants as well


def run():
  _initialize()

These are the two solutions I found that made the most sense to me, however I am not super happy with either of them.

Solution 1:
I feel like getters aren’t as readable in f-Strings and setting a helper variable seems like a bandaid solution to me

Solution 2:
I’ve read that using the global keyword is discouraged as a bad practice, however I do not know, if this also applies to values that are supposedly constant.

I know that I am probably overthinking this quite a bit, but I would nonetheless like to know how a more experienced dev would approach this sort of problem in Python.

Thank you in advance! :slight_smile:

I would use either functools.cached for a top-level function, or functools.cached_property if it were in a class:

@functools.cached
def server_url():
    return Config.c["data"]["server_url"].get()

I believe that this was thread-safe in earlier Pythons but is not in later Pythons, so if this being called from multiple threads on the first call, you might want to add a lock, but in your application, I doubt it’s an issue, it’s being initialized during the startup.

Note also that functools.cached doesn’t exist in earlier Pythons: in that case, @functools.lru_cache(None) has exactly the same effect.