1

I have the following project structure for a Flask app using flask-restx

.
├── app
│   ├── extensions.py
│   ├── __init__.py
│   └── pv_dimensioning
│       ├── controller.py
│       ├── __init__.py
│       ├── models
│       │   ├── dto.py
│       │   ├── __init__.py
│       │   └── vendor_models.py
│       ├── services
│       │   ├── calculator.py
│       │   ├── database.py
│       │   ├── data.py
│       │   ├── db_crud.py
│       │   ├── __init__.py
│       │   └── processor.py
│       └── utils
│           ├── decode_verify_jwt.py
│           ├── decorator.py
│           └── __init__.py
├── config.py
├── main.py
├── package.json
├── package-lock.json
├── Pipfile
├── Pipfile.lock
├── README.md
├── serverless.yml
└── tests
    ├── __init__.py
    ├── test_calculator.py
    ├── test_config.py
    └── test_processor.py

In controller.py, I am adding the add_argument() statements and parsing them in the api routes. In one of the add_argument() statement, I would like to add choices for the user. For getting the choices, I am querying from the database and getting a list of values available. I then convert this list to a tuple, assign it to a variable, and pass it as choices parameter in the add_argument()

My codes:

data.py

from ..models.vendor_models import AdminVendor

def data(app):
    values = AdminVendor.query.all()
    v = [value.name for value in values]
    return {'v': tuple(v)}

controller.py

from flask_restx import Resource, reqparse

parser = reqparse.RequestParser()

parser.add_argument(
    "vendor",
    choices=vendors, # <--- The values of v should be added here
    help="Select the vendor"
)

@ns.route("/")
class UserOutput(Resource):
    @ns.doc(
        "Get calculated response",
        responses={
            200: "Values returned",
            400: "Validation Error",
            403: "Not authorized"
        },
    )
    @ns.expect(parser, validation=True)
    def get(self):
        args = parser.parse_args()
        return DimensionCalculator.inputs(**args), 200

where ns is the namespace.

My __init__.py file in the app folder is as follows:

from flask import Flask

from .extensions import cors, db, ma

def create_app(app_config):
    app = Flask(__name__)

    app.config.from_object(app_config)

    register_blueprints(app)
    register_extensions(app)
    return app

def register_extensions(app):
    cors.init_app(app)
    db.init_app(app)
    ma.init_app(app)

def register_blueprints(app):
    from .pv_dimensioning import dimensioning_blueprint
    app.register_blueprint(dimensioning_blueprint)

and the entry point to the app is main.py

import os

from app import create_app
from app.extensions import db
from app.pv_dimensioning.services.data import data
from config import config_by_name

config_name = os.getenv("FLASK_CONFIG") or "default"
app_config = config_by_name[config_name]
app = create_app(app_config)

db.create_all(app=app)

with app.app_context():
    v = data(app)

print(v)

The output of print(v) is as follows:

{'v': ('Solarmodul Canadian Solar HiKu CS3L-370MS 370Wp', 'Solarmodul Longi LR4-60HIH-370M, 370Wp', 'Solarmodul Solar Fabrik mono S3 - Halfcut 360Wp', 'Solarmodul Energetica e.Classic M HC black - 360Wp', 'Solarmodul Yingli YL280P-29b-YGE 60 Cell Series 2 - poly, 280Wp', 'Solarmodul Suntech Power STP370S-B60/Wnh, 370Wp', 'Solarmodul AXITEC AXIworldpremium X HC AC-340MH/120S, 340Wp', 'Solarmodul Longi LR4-72HIH-440M, 440Wp', 'Solarmodul Seraphim SRP-330-BMB-DG 330Wp', 'Solarmodul Sharp NU-JD 440Wp')}

I want these values of v to be used in controller.py in the 'vendor' argument.

I have tried getting the values of v from main.py by adding from main import v in the controller.py, but it shows the following error

ImportError: cannot import name 'v' from 'main'

What is the mistake that I am doing?

halfer
  • 19,824
  • 17
  • 99
  • 186
some_programmer
  • 3,268
  • 4
  • 24
  • 59
  • 2
    I don't have a solution right now, but the import error makes sense as v is created in main.py after create_app has been called, thus by the time you try to import v, it does not exist, if my understanding of this is right. – Marius Kimmina Jan 18 '21 at 11:32
  • I tried using `flask-caching` as an extension, and that too isn't working – some_programmer Jan 18 '21 at 11:35

1 Answers1

1

I'm not an expert on flask_restx, but from my understanding, the choices argument takes an iterable so you should simply be able to pass in the return value of your data function.

data.py

from ..models.vendor_models import AdminVendor

def data():
    values = AdminVendor.query.all()
    v = [value.name for value in values]
    return {'v': tuple(v)}

controller.py

from flask_restx import Resource, reqparse
from .services.data import data 

parser = reqparse.RequestParser()

parser.add_argument(
    "vendor",
    choices=data()['v'],  
    help="Select the vendor")

Regarding the import error, as Mindslave points out that is most likely a circular import error see this question for a bit more detail. Generally these can be avoided by moving the import from the top of the module to within a function/class, e.g:

from flask_restx import Resource, reqparse



def load_parser():
    from .services.data import data # avoid circular import

    parser = reqparse.RequestParser()
    parser.add_argument(
        "vendor",
        choices=data()['v'],  
        help="Select the vendor")
    return parser

parse = load_parser()

As a side note, be aware that reqparse is scheduled to be removed from flask_restx, so might be worth considering a different option before you get too embedded with it:

Warning The whole request parser part of Flask-RESTX is slated for removal and will be replaced by documentation on how to integrate with other packages that do the input/output stuff better (such as marshmallow). This means that it will be maintained until 2.0 but consider it deprecated. Don’t worry, if you have code using that now and wish to continue doing so, it’s not going to go away any time too soon.

source: https://flask-restx.readthedocs.io/en/latest/parsing.html

mdmjsh
  • 915
  • 9
  • 20
  • Hey, this actually doesn't work. I tried that too, and it gives a context application error. I added the `data()` to the app doing `with app.app_context()`, it still gives the same context error – some_programmer Jan 26 '21 at 18:17
  • ah, I missed that you were using the `app.context` in your `main.py`. You should be able to do something similar, but importing `v` from main rather than data in the load_parser function. – mdmjsh Jan 27 '21 at 10:57
  • I tried adding `from main import v` in a function in `controller.py` and when I run the code, it still gives me the same error `ImportError: cannot import name 'v' from 'main'` – some_programmer Jan 27 '21 at 15:14