How to structure and call tool scripts safely across a multi-script Flask project?

I’m working on a small project that checks Magic: The Gathering deck legality:
https://git.disroot.org/hirrolot19/mtg-legality-checker

The repo has several standalone scripts (for filtering Scryfall data, building format JSONs, validating decks, etc.) and a Flask app (app.py) that uses the generated JSON files.

Right now, these helper scripts rely on relative paths like "oracle_cards.json" or "../resources/format_definitions". This makes it easy to break things when I call the scripts from different working directories or from inside the Flask app.

I’d like to improve the workflow so I can:

  • Run each script directly or import its functions from app.py

  • Avoid file path errors caused by relative locations

  • Keep it simple

What’s the best way to organize or refactor this so all scripts can be reused safely? Links to resources to learn more about whatever I’m recommended would be appreciated.

First, the simple approach: all modules loaded from files have a __file__ attribute set to the location of its source code, which is also accessible as a global variable from inside that module. You can just use pathlib or os.path functions to take that, navigate to the file you need.

A better way though is to use importlib.resources. If you’re not aware, Python’s import system is quite flexible, and is able to do things like import modules directly out of zip archives. But the __file__ approach won’t work there, since they’re not files on disk. importlib.resources uses functionality in the import system to handle all those cases.

The examples on the importlib_resources backport page actually show both approaches. Ignore pkg_resources, that’s old, and you can just use importlib.resources instead of importlib_resources in new enough Pythons.

You are running into standard Python package import issues because your folder mtg_legality_checker is a package, but your scripts are inside it, and Python needs to know the package context. Here’s a structured breakdown.


1. Calling scripts: always from project root

Your project root is ~/code/python/mtg/legality_checker. You should run scripts from there:

cd ~/code/python/mtg/legality_checker
python -m mtg_legality_checker.scripts.build_format_jsons
  • -m treats it as a module, preserving package structure.

  • Directly running python mtg_legality_checker/scripts/build_format_jsons.py will often break imports.


2. Importing modules properly

Current working structure:

~/code/python/mtg/legality_checker main*
❯ git ls-files --exclude-standard | tree --fromfile
.
├── cronjob.sh
├── docs
│   └── IMPROVEMENTS.md
├── .gitignore
├── mtg_legality_checker
│   ├── app.py
│   ├── data
│   │   ├── format_definitions
│   │   │   ├── 2015modern.json
│   │   │   ├── classiclegacy.json
│   │   │   ├── prefiremodern.json
│   │   │   ├── prehorizonsmodern.json
│   │   │   └── premodern.json
│   │   └── formats
│   │       ├── 2015modern.json
│   │       ├── alchemy.json
│   │       ├── brawl.json
│   │       ├── classiclegacy.json
│   │       ├── commander.json
│   │       ├── duel.json
│   │       ├── gladiator.json
│   │       ├── heirloomconstructed.json
│   │       ├── historic.json
│   │       ├── legacy.json
│   │       ├── modern.json
│   │       ├── oathbreaker.json
│   │       ├── oldschool.json
│   │       ├── paupercommander.json
│   │       ├── pauper.json
│   │       ├── penny.json
│   │       ├── pioneer.json
│   │       ├── predh.json
│   │       ├── prefiremodern.json
│   │       ├── prehorizonsmodern.json
│   │       ├── premodern.json
│   │       ├── standardbrawl.json
│   │       ├── standard.json
│   │       ├── timeless.json
│   │       └── vintage.json
│   ├── scripts
│   │   ├── build_filtered_formats.py
│   │   ├── build_format_jsons.py
│   │   ├── build_heirloom_format.py
│   │   ├── config.py
│   │   ├── convert_filtered_json_to_moxfield_csv.py
│   │   ├── convert_filtered_json_to_validator_format.py
│   │   ├── convert_moxfield_csv_to_json.py
│   │   ├── download_oracle_cards_from_scryfall.py
│   │   ├── fetch_filtered_scryfall_to_json.py
│   │   ├── filter_cards_by_dynamic_price.py
│   │   ├── filter_cards_by_dynamic_price_test.py
│   │   ├── filter_cards_by_rarity_and_dynamic_price.py
│   │   ├── __init__.py
│   │   ├── print_oracle_json_schema.py
│   │   ├── README.md
│   │   ├── validate_decklist_against_format.py
│   │   └── validate_decklist_against_format_test.py
│   ├── static
│   │   └── style.css
│   ├── templates
│   │   └── index.html
│   └── utils
│       ├── deck_utils.py
│       ├── deck_validator.py
│       ├── format_utils.py
│       ├── format_utils_test.py
│       ├── __init__.py
│       ├── io_utils.py
│       ├── moxfield_utils.py
│       ├── moxfield_utils_test.py
│       ├── scryfall_utils.py
│       └── update_formats.py
├── README.md
├── requirements.txt
└── run_mtg_app.sh

Inside build_format_jsons.py:

# Recommended (absolute import)
from mtg_legality_checker.utils.format_utils import load_format_definitions
from mtg_legality_checker.utils.deck_validator import validate_decklist

# Or relative import (only works when run as module)
from ..utils.format_utils import load_format_definitions

3. Avoid repeating mtg_legality_checker in imports

Two main approaches:

a) Add the package root to PYTHONPATH:

export PYTHONPATH=$PYTHONPATH:~/code/python/mtg/legality_checker
python mtg_legality_checker/scripts/build_format_jsons.py

Then inside your scripts you can do:

from utils.format_utils import load_format_definitions

b) Install your package in editable mode:

pip install -e .

You can flatten your structure so that mtg_legality_checker’s contents become the project root. Your imports would simplify:

# scripts/build_format_jsons.py
from utils.format_utils import load_format_definitions

No need for the top-level mtg_legality_checker in imports.


Tradeoffs

Pros:

  • Simpler imports.
  • Easier for quick scripts and local testing.
  • No need for PYTHONPATH tricks or -m module execution.

Cons:

  • Loses clear package namespace: mtg_legality_checker was acting as a self-contained package.
  • Harder to install via pip or editable mode (pip install -e .), because there is no top-level package to register.
  • If you later want to publish or reuse this as a library, the flattened structure could cause name collisions.
  • Relative imports become messier if you still want some internal structure (..utils.format_utils won’t make sense without a package folder).

Recommendation

  • If this is just a local project/app: flattening is fine.
  • If you plan to reuse, distribute, or keep it modular: keep mtg_legality_checker as a package and run scripts with python -m mtg_legality_checker.scripts.script_name or use editable install.