blobberina
blobberina

Reputation: 21

Why is my local variable being accessed like it's a global variable?

I have this code that somehow treats config like a global variable even though it's a local variable. My colleague just noticed this because they were turning the script into a Lambda function and AWS kept giving them a NameError:

Error Message: name 'config' is not defined

If I run the following in my terminal, it works just fine. The question is, is it supposed to error out since config isn't defined in the get_fields_for_issuetype function, or am I missing something? Why is it working just fine?

The only difference between the Lambda code and this one is that the Lambda code has a constants.py where ProjectConfig and project_configs are defined, then are imported into the main code.

This is the code that runs fine in my terminal:

import requests
from collections import namedtuple


BASE_URL = "https://example.com"

ProjectConfig = namedtuple("ProjectConfig", "projects required_fields tab_name")

project_configs = [
        ProjectConfig(
            projects=sorted(
                [
                    "AGNT",
                    "APS",
                    "CLOD",
                ]
            ),
            required_fields=[
                "Field 1",
                "Field 2",
            ],
            tab_name="Group_1_Fields",
        ),
        ProjectConfig(
            projects=sorted(
                [
                    "ABAGNT",
                    "ABAPS",
                    "ABCLOD",
                ]
            ),
            required_fields=[
                "Field 3",
                "Field 4",
            ],
            tab_name="Group_2_Fields",
        ),
    ]
    def auth():
        email = "[email protected]"
        pwd = "***"
    
        credentials = (email, pwd)
        session = requests.Session()
        session.auth = credentials
    
        return session

     def get_fields_for_issuetype(project_key, session):
        url = (BASE_URL + f"/something/something")

        response = session.get(url)
        data = response.json()
        project = data["projects"][0]

        proj_key = project["key"]
        name = project["name"] + f" ({proj_key}) - Issue Types"
        issuetypes = project["issuetypes"]

        for issuetype in issuetypes:
            issuetype_name = issuetype["name"]

            required_fields = config.required_fields.copy()


    def main(config):
        session = auth()
    
        for project in config.projects:
            table = get_fields_for_issuetype(project, session)


    if __name__ == "__main__":
        for config in project_configs:
            main(config)

This is the code the doesn't run when turned into a Lambda function:

app.py

import requests
from constants import API_TOKEN, BASE_URL, USER_NAME, project_configs


    def auth():
        email = USERNAME
        pwd = API_TOKEN
    
        credentials = (email, pwd)
        session = requests.Session()
        session.auth = credentials
    
        return session

     def get_fields_for_issuetype(project_key, session):
        url = (BASE_URL + f"/something/something")

        response = session.get(url)
        data = response.json()
        project = data["projects"][0]

        proj_key = project["key"]
        name = project["name"] + f" ({proj_key}) - Issue Types"
        issuetypes = project["issuetypes"]

        for issuetype in issuetypes:
            issuetype_name = issuetype["name"]

            required_fields = config.required_fields.copy()


    def main(config):
        session = auth()
    
        for project in config.projects:
            table = get_fields_for_issuetype(project, session)


    def lambda_handler(event, context):
        for config in project_configs:
            main(config)

constants.py:

from collections import namedtuple

BASE_URL = "https://example.com"

ProjectConfig = namedtuple("ProjectConfig", "projects required_fields tab_name")

project_configs = [
        ProjectConfig(
            projects=sorted(
                [
                    "AGNT",
                    "APS",
                    "CLOD",
                ]
            ),
            required_fields=[
                "Field 1",
                "Field 2",
            ],
            tab_name="Group_1_Fields",
        ),
        ProjectConfig(
            projects=sorted(
                [
                    "ABAGNT",
                    "ABAPS",
                    "ABCLOD",
                ]
            ),
            required_fields=[
                "Field 3",
                "Field 4",
            ],
            tab_name="Group_2_Fields",
        ),
    ]
    

Upvotes: 2

Views: 59

Answers (2)

Nathaniel Ford
Nathaniel Ford

Reputation: 21230

This function doesn't have config in scope:

def get_fields_for_issuetype(project_key, session):
    url = (BASE_URL + f"/something/something")
    
    ...

    for issuetype in issuetypes:
        issuetype_name = issuetype["name"]

        required_fields = config.required_fields.copy()

In your local code you have this:

if __name__ == "__main__":
    for config in project_configs:
        main(config)

This puts config into a scope that the above function has access to, because it lives outside a function definition and in the same file as get_fields_for_issuetype(). You need to pass config in for this to work as you expect in your Lambda function.

You can get a sense for how this works by defining a function like this:

def f():
    def g():
        print(f"I have access to {x=}")
    x = "an outer scope variable"
    g()

Even though x is defined after the function g(), g() has access to it because the scope of x is visible to g() - they both are in the scope of function f(). Similarly, when you're working with a raw file, you can think of it as an invisible f() that is being run. In fact, the if __name__ = "__main__" is basically giving away that you're in a function named __main__. get_fields_for_issuetype can see that function because that is where it is defined.

Upvotes: 1

Silvio Mayolo
Silvio Mayolo

Reputation: 70287

if __name__ == "__main__":
    for config in project_configs:
        main(config)

This right here at the bottom. This declares a variable called config at global scope. for doesn't create its own scope, so this is still globally-scoped. When you use config in a function where such a name does not exist locally, it inherits the global one.

Upvotes: 1

Related Questions