How Can i pass url as a Vairable in Flask route - python-3.x

I want to pass a youtube link as a route parameter but it gives me an error
app = Flask(__name__)
#app.route('/')
def test():
return 'Hello'
#app.route("/<path:url>")
# a youtube link would be passed here
def get_data(url):
result = scrap(url)
# do something with that url
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True )
here i need a youtube link as a variable but the path:url does not give me full path so i cant use that link, is there a way so that i cant retrieve the full path?

As it's a URL you're parsing, it might be better to parse it as a query parameter rather than a route parameter, this way it wouldn't be as easy for it to get mangled by the parser.
URL Query parameters can be extracted in flask using request.args.get
So your program would look something like this,
app = Flask(__name__)
#app.route('/')
def test():
return 'Hello'
#app.route("/video")
def get_video_data():
url = request.args.get("url")
result = scrape(url)
return jsonify(result)
if __name__ == '__main__':
app.run(debug=True)
Then you can pass the URL using http://localhost:8080/video?url=youtubeurl_here

Related

Can I store method calls in a tuple without calling the methods at runtime?

This exercise is called Command Line Emailer and it comes from Automate the Boring Stuff with Python CH 12 2nd ed.
Write a program that takes an email address and string of text on the
command line and then, using selenium, logs in to your email account
and sends an email of the string to the provided address. (You might
want to set up a separate email account for this program.)
While working through this exercise, I learned that the selenium method calls for finding html elements will sometimes work, but sometimes they will throw noSuchElementException. To work around this, I created three tuples to store the selenium method calls. Using the selenium_exception_loop() function (see below), I would loop through the tuple and call one method at a time. If the method throws a noSuchElementFound exception, the loop will wait two seconds then try again.
The issue, of course, is that the selenium method calls are executed at runtime. Is there any way to store the selenium method calls in a collection without actually executing the calls during runtime? Or do I need to take an entirely different approach here?
#!python3
# 04_command_line_emailer.py -- send emails from the command line
# usage: input four command line arguments:
# sys.argv[1] = email to log into
# sys.argv[2] = password
# sys.argv[3] = email body
# sys.argv[4] = recipient email address
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException
import sys
import re
import time
class commandLineEmailer:
def __init__(self):
self.browser = webdriver.Firefox()
self.gmail_method_calls = ()
self.outlook_method_calls = ()
self.yahoo_method_calls = (
lambda: self.browser.find_element(By.CLASS_NAME, 'signin').click(),
lambda: self.browser.find_element(By.NAME, 'username').send_keys(self.return_email_info()[0]),
lambda: self.browser.find_element(By.NAME, 'signin').click(),
lambda: self.browser.find_element(By.NAME, 'password').send_keys(sys.argv[2]),
lambda: self.browser.find_element(By.NAME, 'verifyPassword').click(),
lambda: self.browser.find_element(By.CSS_SELECTOR, 'Compose').click()
)
def return_email_info(self) -> tuple:
'''
Input sys.argv[1] into regex. sys.argv[1] contains the sending email
address. return the username and the email client name.
:return: tuple, username at index 0, email client name at index 1
'''
return re.compile("(.*)(\#)(.*)(\.)").match(sys.argv[1]).group(1), \
re.compile("(.*)(\#)(.*)(\.)").match(sys.argv[1]).group(3)
# open browser session and navigate to email client
def go_to_email_client(self) -> None:
EMAIL_CLIENTS = {
'outlook': 'https://www.outlook.com/',
'gmail': 'https://www.gmail.com/',
'yahoo': 'https://yahoomail.com/'
}
self.browser.get(EMAIL_CLIENTS[self.return_email_info()[1]])
def selenium_exception_loop(self, selenium_method_collection) -> None:
'''
:param selenium_method_collection: input collection containing selenium
method calls to search for html elements. Wait two seconds between each
method call. Except the NoSuchElementException error.
:return: None
'''
for selenium_method_call in selenium_method_collection:
while True:
try:
time.sleep(2) # wait two seconds
selenium_method_call()
break
except NoSuchElementException:
continue
def sign_in_and_send(self) -> None:
'''
Retrieve the email client to log into. Call the selenium_exception_loop()
function and pass it the email client tuple containing the selenium
method calls
:return None:
'''
if self.return_email_info()[1] == 'gmail':
pass
elif self.return_email_info()[1] == 'outlook':
pass
elif self.return_email_info()[1] == 'yahoo':
self.selenium_exception_loop(self.yahoo_method_calls)
def main() -> None:
command_line_emailer = commandLineEmailer()
command_line_emailer.go_to_email_client()
command_line_emailer.sign_in_and_send()
if __name__ == "__main__":
main()
This answer was gathered from reddit, but essentially, put lambda in front of each method call, and rewrite selenium_method as selenium_method() so that it is callable, like so:
yahoo_method_calls = (
lambda: browser.find_element(By.CLASS_NAME, 'signin').click(),
lambda: browser.find_element(By.NAME, 'username').send_keys(self.return_email_info()[0]),
)
. . .
try:
time.sleep(2) # wait two seconds
selenium_method_call() # Call it
break

Flask: get the context on the filter

I'm a beginner at the flask, I want to practice filter, I've read the official documentation, but it doesn't help.
I want to create a firstof filter, i.e., Outputs the first argument variable that is not “false” .(same as Django's firstof).
<h1>Hi, {{ "username, 'guest'"| firstof }} </h1>
#app.route('/')
def index():
return render_template('index.html') # expect output: Hi, guest
# return render_template('index.html', username='Carson') # Hi, Carson
I try,
#app.template_filter('firstof')
def firstof_filter(s: str):
"""
USAGE::
{{ "var1, var2, 'default_val'" | firstof }}
"""
for data in s.split(','):
data = data.strip()
if data[0] == data[-1] and data[0] in ("'", '"'):
return data[1:-1] # a constant
""" # does any similar grammar like below?
if hasattr(app.current.context, data):
return app.current.context[data]
"""
It would be nice if you could provide more links about flask-filter. thanks!
I found an answer by myself.
It isn't easy to achieve it with Flask directly.
because
# flask.templating.py
def render_template(template_name_or_list, **context):
ctx = _app_ctx_stack.top
ctx.app.update_template_context(context) # it does not store the context in the ctx. it just adds something to the context
return _render(
ctx.app.jinja_env.get_or_select_template(template_name_or_list),
# ctx.app.jinja_env is an environment (It combined Flask and Jinja, pretty like the Jinja.Environment)
# get_or_select_template, will run our function and no passing the context so that is why it so hard to do it.
context,
ctx.app,
)
def _render(template, context, app):
# It seems we can't do anything when the process already here.
before_render_template.send(app, template=template, context=context)
rv = template.render(context)
template_rendered.send(app, template=template, context=context)
return rv
But we get a clue that is we can change the Jinja.Environment.
you may reference the Custom Filters first before you go ahead.
Solution & demo
from flask import render_template
from jinja2 import contextfilter
from flask.helpers import locked_cached_property
from flask import Flask
from jinja2.runtime import Context
from flask.templating import Environment
from typing import Dict
from pathlib import Path
#contextfilter
def firstof_filter(ctx: Context, s: str):
ctx: Dict = ctx.parent
for data in s.split(','):
data = data.strip()
if data[0] == data[-1] and data[0] in ("'", '"'):
return data[1:-1] # a constant
if data in ctx:
return ctx[data]
class MyFlask(Flask):
#locked_cached_property
def jinja_env(self):
env: Environment = self.create_jinja_environment()
env.filters['firstof'] = firstof_filter # well, this way is not smart, you are better build a register to get all the filters that you have defined.
return env
app = MyFlask(__name__, template_folder=Path('./templates'))
#app.route('/')
def test():
return render_template('test.html', username='Carson') + '\n' + render_template('test.html')
app.run(debug=True)
where test.html
<h1>Hi, {{ "username, 'guest'"| firstof }} </h1>
p.s.
env.filters['firstof'] = firstof_filter Well, this way is not smart. You are better to build a register to get all the filters that you have defined.
you can reference Django: django.template.library.py -> class Library to collect all filters ...
output
Hi, Carson
Hi, guest

PokeAPI & Flask: Combining different properties on API

API beginner here,
I'm creating an API that calls both a character name and their description with one ENDPOINT. So far I have the data appearing at separate endpoints.
Goal: Getting output format ('http://127.0.0.1:5000/v1/pokemon/string:name') to look similar to this:
{ "name": "pichu", "description": "It is not yet skilled at storing electricity.It may send out a jolt if amused or startled."}
from flask import Flask, jsonify, Response
from flask_restful import Resource, Api, fields, marshal_with
import requests, json
app = Flask(__name__)
api = Api(app)
# Prints all Pokemon via JSON
#app.route('/v1/pokemon/all', methods=['GET'])
def poke_names():
data = []
name_url = "https://pokeapi.co/api/v2/pokemon?limit=151"
while True:
resp = requests.get(name_url)
json = resp.json()
data.extend(json.get('results', []))
name_url = json.get('next')
if not name_url: break
return jsonify(data)
#app.route('/v1/pokemon/<string:name>/title', methods=['GET'])
def get_poke(name):
return jsonify({'name': name})
#flavor Text ie pokemon description
#app.route('/v1/pokemon/<int:pokemon_id>', methods=['GET'])
def get_description(pokemon_id):
descrip_url = f"https://pokeapi.co/api/v2/pokemon-species/{pokemon_id}"
r = requests.get(descrip_url)
json_blob = r.json()
flav_text = extract_descriptive_text(json_blob)
return jsonify({'description': flav_text})
def extract_descriptive_text(json_blob, language='en'):
text = []
for f in json_blob['flavor_text_entries']:
if f['language']['name'] == language:
text.append(f['flavor_text'])
return text
#error occurs below
#app.route('/v1/pokemon/<string:name>')
def all_poke_data(name, flav_text):
return jsonify({'name':name, 'description':flav_text})
if __name__ == "__main__":
app.run(debug=True)
Error: When I try to combine them I get
TypeError: all_poke_data() missing 1 required positional argument: 'flav_text'
Thank you for any pointers provided!
Figured it out!
Just add this function, which is a combination of both route functions
#app.route('/v1/pokemon/<string:name>')
def get_poke(name):
descrip_url = f"https://pokeapi.co/api/v2/pokemon-species/{name}"
r = requests.get(descrip_url)
json_blob = r.json()
flav_text = extract_descriptive_text(json_blob)
return jsonify({'name': name},{'description': flav_text})

Python3 Flask: "Function() missing 1 required positional argument: 'test_output'"

I'm just starting with FLASK and stumbled upon this error:
test() missing 1 required positional argument: 'test_output'
Why is this happening? The functions is declared before calling it and usually this error means that the argument while calling the function is missing.
def foo(x):
print (x)
foo(x)
So this is working, why doesn't it work in my script with flask?
Here is my "full" code:
from flask import Flask, jsonify, request
from flask_pymongo import PyMongo
app = Flask(__name__)
#app.config ['MONGO_DBNAME'] = 'DBfoo'
app.config['MONGO_URI'] = 'mongodb://127.0.0.1:27017/Test'
mongo = PyMongo(app)
#app.route('/TestRouteGet', methods=['GET', 'POST'])
def test(test_output):
print (test_output)
def get_Test():
collection = mongo.db.TestCol
if request.method == 'POST':
test_input = "Test BlaBla"
test(test_input)
if __name__ == '__main__':
app.run(debug=True)
When sending a POST request I should have the test_output ("Test Blabla") printed in the console.
Thanks for your time.
Your function expects test_output argument, you should get this argument from the url string http://flask.pocoo.org/docs/1.0/quickstart/#url-building
#app.route('/TestRouteGet/<test_output>', methods=['GET', 'POST'])
Then you can make a request to /TestRouteGet/insert_your_string and your function will print received string.

how to implement a nested route in flask that each do different things

#app.route('/sera/<mount_type>')
#app.route('/sera', methods=['POST'])
def return_pages():
if request.method == 'POST':
usin = request.form.get('serval')
global mount_type
mount_type = usin
#this section runs independend of the search box
if mount_type == 'acongagua':
return render_template('result.html',aa=acongagua.names, ac=acongagua.location, ad=acongagua.metre_height, ae=acongagua.feet_height)
elif mount_type == 'adams':
return render_template('result.html',aa=adams.names, ac=adams.location, ad=adams.metre_height, ae=adams.feet_height)
else:
return 'YOU HAVE ENTERED AN INCORRECT VALUE'
if __name__ == '__main__':
app.run(debug=True, use_reloader=True)
that is the python flask code i'm trying to run the first if statement should run when the form is filled and should have its values passed to the seconf if statement
You can use redirect to redirect the request, and pass the value as URL paramater:
from flask import redirect
#app.route('/sera/<mount_type>')
#app.route('/sera', methods=['POST'])
def return_pages():
if request.method == 'POST':
usin = request.form.get('serval')
return redirect(url_for('return_pages', mount_type=usin)) # <--
...

Resources