Reputation: 21
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
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
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