Getting Bad request: Invalid (Lazy) Media object trying to access media served through API

What I’m trying to do:
Serve a file (which is on a data table) through an HTTP endpoint using a temporary link (the ‘url’ property of the object.

What I’ve tried and what’s not working:
I keep getting “Bad request: Invalid (Lazy) Media object” as a response when trying to access the file. This happens in the same browser session and I’ve also performed the request using Python (using the session class so that everything is done through the same session).

It’d seem this is a bug since I cloned the apps from this post campopianoa’s example and it doesn’t work either (I updated the API URL in the app that gets the images pointing to the one serving it)

For now, this post by Meredydd works as an alternative but this method makes the URLs permanent, which is something I’m trying to avoid.

I’ve opted into accelerated tables and tried opting out and repeating but the result is the same. I don’t know if this may cause this behavior, it’d seem not.

I’m not sure I can share the app since it’s for work but you guys can try out the API. The code’s below, otherwise please try cloning campopianoa’s example since it shows the issue perfectly (if someone can get it to work, please let me know how as that’d solve the issue!)

Code Sample:

import requests

# Create session
s = requests.Session()
url = 'https://credicorp-research.anvil.app/_/api/content?from=20220101&to=20221231&api_key=4215db96dcf3'
r = s.get(url)
# Gets data correctly
data = r.json()
# Get first temp-file link to test
file_url = data[0]['media'][0]
print(file_url)
# Prints what seems to be a correct temp file url
f = s.get(file_url)
f.content
# Getting b'Bad request: Invalid (Lazy) Media object'

Hopefully I haven’t overlooked something simple but I’ve been cracking me head trying to figure this out and I haven’t been able to. Thanks everyone!

Media URLs are, by default, only valid in the session in which they’re generated. That’s most likely what you’re running into.

I haven’t done it myself, but if you wanted to serve media via an API endpoint, I suspect you’d need to return a properly formatted HTTP response that contains the actual media bytes. Your API endpoint would become the URL you’d use for the media.

1 Like

Thanks for the response!

I’m aware of that and sadly I’m pretty sure I’m making all the requests in the same session so I don’t think that’s the problem :confused:

Disclaimer: the below is based on my understanding of what Anvil means by session, which might be totally off. But it matches what I’ve seen in my own apps.

If the client code you were running was in the same session (app instance) as the server, you wouldn’t need to use an API to fetch the media. You’d just use a server call, and everything would just work.

Since you’re using an API call, I’m assuming that you’re trying to use the media outside of an Anvil app (e.g. to display an image in an email, on a static website, etc). The temporary url won’t work in those situations.

If you give more detail on where the code you posted is running, folks on the forum who know more about how the media urls work internally might be able to offer ideas.

Also for what it’s worth, Bad request: Invalid (Lazy) Media object is the exact error message given when you request a media URL outside of the session (app instance) in which it was created. So you may want to investigate further as to why you think you’re making the request in the same session (you might be, but sanity checks for the simple causes of problems are usually the first thing I do when debugging anything).

I think I understand what you’re saying… to test it, I tried producing the URL and calling it from the same function as that’d be -if I got you correctly- one same session.

The function I created was the following:

@anvil.server.http_endpoint('/test_get_contents', methods=['GET'])
def get_file():
  # creates url
  url = app_tables.media.search()[0]['media'].url
  # calls url
  r = requests.get(url)
  return str(r.content, 'utf-8')
  # still returns "Bad request: Invalid (Lazy) Media object"

Any thoughts? after this I’m leaning towards reporting a bug
I left it running and it can be called at https://credicorp-research.anvil.app/_/api/test_get_contents

Thanks for the help!

We’re getting into hazy territory for me here, but by using requests to fetch via the url, I think you’re implicitly breaking out of the Anvil session. The requests library isn’t going to pass the appropriate session cookie to Anvil automatically.

In an Anvil app, the browser takes care of passing the session cookie to the server with every request. If you’re using requests, you must manage the session cookie yourself.

In what context are you trying to use this API?

Is it going to be called from Python code not running in an Anvil app? In that case, you’ll have to manage the session cookie if you want the temporary media url to work.

Is it going to be called from something embedded (e.g. an email)? In that case, the temporary media url will not work, and your API must return the actual media contents.

1 Like

Got it… So to test this, I created an endpoint just for returning the URL of a file, so that I could call it in my browser and then copy and paste it in the same window but the same keeps happening. Based on what you’ve shared, I’m thinking now the issue might be that if I copy and paste the URL -even in the same window- I might be breaking into a new “session”. If that’s true, I’m missing how could that URL ever be called?

@anvil.server.http_endpoint('/test_get_url', methods=['GET'])
def get_file():
  return app_tables.media.search()[0]['media'].url

My use case

What I’m trying to do is to serve some records to feed a third-party system on demand, but I’m trying to avoid for the file links to be permanent so that they don’t get shared using the URL (they should be used once to download the files when the record is served)

Based on what Meredydd said in the original post, I understood this is the intended use case of those links.

I am also puzzled by @campopianoa’s demonstration not working, I’m assuming it worked at some point but looking at why it isn’t working at the moment, it’s because it’s encountering the same issue :confused:

I think I’m going to have to code a function that produces temporary links, I was hoping to avoid that with the use of media URLs.

Many thanks for the help @jshaffstall !! Really appreciate it :slight_smile:

In that original post, Meredydd’s API does not return the URL of the media object, it returns the media object directly (presumably Anvil takes care of making sure the HTTP response is properly formatted).

@anvil.server.http_endpoint('/image/:row_id')
def get_image(row_id, **p):
  row = app_tables.images.get_by_id(row_id)
  if row is not None:
    # Returning a Media object serves it as the HTTP response
    return row['image_column']
  else:
    return anvil.server.HttpResponse(status='404')

Have you tried doing that?

I’ve used media URLs in an Anvil app before, so can guarantee that they work when called within the same session. My use case was creating a BBCode parser that generated HTML, so I needed urls for the media objects. That generated HTML was used inside the Anvil app, so was in the same session.

I’m referring to the last post on that thread made by Meredydd in regards to permalinks (what I want to avoid):

And no, I haven’t tried that because of what I said before, I’m trying to avoid permalinks and Meredydds method (which was fine for the user in that thread) is actually a way to create permalinks.

Meredydd’s method shows how to serve media objects via an HTTP API, which is what you want.

You also want for those links to not be permanent, so you’re going to need to build more code into your version that enables your own sort of temporary links. The media urls will not work for this purpose, because of the session limitation.

But the starting point will be Meredydd’s code, with your own “session” management added on.

1 Like

BTW It it’s not too much trouble, out of curiosity, I’d love to see how you’ve used media URLs. Because for the life of me I don’t seem to be able to figure it out.

Tried this (from campopianoa’s demonstration, avoiding the use of the requests library) and I get An exception was raised. Check the application logs for details. which in the logs is HttpErrorStatus: HTTP error 400 when calling anvil.http.request(url)

@anvil.server.http_endpoint('/test', methods=['GET'], authenticate_users=True)
def get_file():
  url = app_tables.media.search()[0]['media'].url
  r = anvil.http.request(url)
  return str(r.get_bytes(), 'utf-8')

Cheers!

Media urls are intended to be used on the client, not the server (as far as I can tell). It’s the client (running in a browser) that will be able to send the session cookie. On the server, you already have the media object, you don’t need to use the URL to get its contents, just use media.get_bytes() (e.g. if the media is a DOCX file and you want to use a Python library to edit the file).

For my part, my bbcode display is happening in the client, so it works well. I generate HTML based on parsing bbcode, and the media url is used in that context (this code could be on the server or client, I’ve done it both places in various iterations of the app):

  media_url = data['media'].url     
  
  result = '<img src="'+media_url+'"'

The resulting HTML is displayed in an Anvil form (e.g. client side), so is in the session. Any third party usage (which includes any variety of a requests library) doesn’t share the session, so won’t work.

Other tabs in the same browser as the Anvil app do share the same session, so you can safely copy and paste the URL into a separate tab where the Anvil app is running. But HTTP API requests get their own sessions distinct from the Anvil app, so those URLs won’t work when copied into new tabs.

1 Like

As I understand it…

Anvil fires up a new instance of your Server code, to service the incoming call. That starts a new session, through which Anvil’s infrastructure calls your endpoint.

Once the return completes, the app’s Server code instance ends, and with it, the temporary session.

By the time the caller acts on the URL, the session is dead, and can’t respond to the URL.

Perhaps you could return the actual binary data (the content of the media object)?

1 Like

Thanks! I opted to create my own function which creates temporary links. The reason being that the API connects to a PowerBI as well and in that case, the files are not needed (that’d make refreshing the PowerBI much faster)

But yes, ultimately that would’ve worked as well. Thanks!

For what it’s worth, I’m coming across the same issue with the way that anvil handles media objects on a “per session” basis - trying to minimise client-side processing by doing some work with custom HTML including links to media objects server-side, then doing a simple fetch of that html into the browser page (use case is for areas with poor signal/low quality phones). Doing the work server-side for the links results in them breaking as soon as you go off-device, forcing you to make the links client-side, which is tedious.

On my above, it appears after I build the links in the server that they are available on other devices for a short while - haven’t quantified this yet, but some minutes. I’m experimenting with a scheduled background task to refresh the links every 10 minutes server-side.