kylebebak / Requester

Powerful, modern HTTP/REST client built on top of the Requests library
https://kylebebak.github.io/Requester/
MIT License
307 stars 10 forks source link

Cannot use env vars from within functions #22

Open aksdb opened 5 years ago

aksdb commented 5 years ago

Example:

###env
base_url = 'http://localhost:8080'
session_id = 'myid'

def apicall(path):
    return "{}{};jsessid={}".format(base_url, path, session_id)
###env

# Profile
get(apicall("/api/foo"))

The call fails because base_url and session_id are empty in the defined function. Declaring them nonlocal doesn't help. How can this be accomplished without duplicating the constants?

kylebebak commented 5 years ago

Hey there! So, I dug in a little and found that the first request fails, but the second doesn't. The first one fails because the env dict Requester creates to be used in the actual calls to requests doesn't form a closure for apicall.

In other words, apicall doesn't have access to variables defined in its enclosing scope, such as base_url and session_id.

And yes, this is counter-intuitive because you wouldn't run into this limitation if you pasted this code into the Python interpreter and ran it.

I'm not sure if there's a way to change the code that creates the env dict to ensure that closures are created for functions such as apicall... I'm looking into it.

###env
base_url = 'http://localhost:8080'
session_id = 'myid'

def apicall(path):
  return "{}{}?jsessid={}".format(base_url, path, session_id)

def _apicall(path):
  return "http://localhost:8080{}?jsessid=myid".format(path)
###env

# Profile
get(apicall("/api/foo"))

get(_apicall("/api/foo"))
kylebebak commented 5 years ago

So, this works just fine.

###env
def caller(path):
  def call():
    return "http://localhost:8080{}?jsessid=myid".format(path)
  return call

###env

# Profile
get(caller("/api/foo")())

Which means the env parsing code can create closures for functions as long as the variables they're referencing aren't assigned in the "top-level".

I'm not sure I can fix this. It requires deeper knowledge of the imp module, exec and closures than I currently have.

My suggestion is to do something like the following:

###env
def apicall(path):
  return "{}{}?jsessid={}".format('http://localhost:8080', path, 'myid')
###env

get(apicall("/api/foo"))

Or, equivalently, but more verbose and weird:

###env
base_url = 'http://localhost:8080'
session_id = 'myid'

def apicall(base_url, path, session_id):
  return "{}{}?jsessid={}".format(base_url, path, session_id)
###env

get(apicall(base_url, "/api/foo", session_id))