I have a GET method with requested parameter in path:
#router.get('/users/{user_id}')
async def get_user_from_string(user_id: str):
return User(user_id)
Is it possible to get base url raw path (i.e., '/users/{user_id}') from the request?
I have tried to use the following way:
path = [route for route in request.scope['router'].routes if
route.endpoint == request.scope['endpoint']][0].path
But it doesn't work and I get:
AttributeError: 'Mount' object has no attribute 'endpoint'
The below solution worked fine for me
using the string replace with count parameter replaces the first occurence only. And request.path_params will return the path parameters in the sequence you take it in the request.
def get_raw_path(request):
path = request.url.path
for key, val in request.path_params.items():
path = path.replace(val, F'{{{key}}}',1)
return path
As per FastAPI documentation:
As FastAPI is actually Starlette underneath, with a layer of several
tools on top, you can use Starlette's Request object directly when you
need to.
Thus, you can use Request object to get the URL path. For instance:
from fastapi import Request
#app.get('/users/{user_id}')
def get_user(user_id: str, request: Request):
return request.url.path
Output (if the received user_id was 1):
/users/1
Update
If, however, what you need is the original route path, i.e., /users/{user_id}, you could use the below. The way it works is by getting the root_path first—which would normally be an empty string, unless you have mounted sub-application(s) to the top-level app (e.g., app.mount("/subapi", subapi)), and hence, you need the result to be prefixed with that specific path /subapi—and then append to it the route's path , which you can get from the APIRoute object. Example:
from fastapi import Request
#app.get('/users/{user_id}')
def get_user(user_id: str, request: Request):
path = request.scope['root_path'] + request.scope['route'].path
return path
Output:
/users/{user_id}
I'm working on implementing this for OpenTelemetry and the way to get the original route with the data that's available is as follows:
def get_route_from_request(req):
root_path = req.scope.get("root_path", "")
route = scope.get("route")
if not route:
return None
path_format = getattr(route, "path_format", None)
if path_format:
return f"{route_path}{path_format}"
return None
Note that the accepted answer is not returning what was asked, as it returns the path as received by the server.
None of the other answers deal with mounted apps.
And finally, answers checking the name of the endpoint are also wrong as the same function could be used in different endpoints.
Having found both the answers to not work I'll share what I use.
It's not great as if there's shared values in path params it will not work
path = request.url.path
for key, val in request.path_params.items():
path = path.replace(val, F'{{{key}}}')
You can use the APIRout object property in the request to get the actual path
example:
raw_path = request.scope['route'].path
#'/user/{id}'
Related
Could you please explain to me why in FastAPI the following works:
#app.post("/items/v1/cards{sku}")
async def create_item(sku: str):
return {"status":200,"sku":sku} # returns status:200 and sku
but, the same endpoint with questionmark in it like the one given below does not?
#app.post("/items/v1/cards?{sku}")
async def create_item(sku: str):
return {"sku":sku} # returns 404
In the first code snippet, you defined the parameter as a Path parameter and worked as expected.
#app.post('/items/v1/cards/{sku}')
async def create_item(sku: str):
return {'sku': sku}
URL Example:
http://127.0.0.1:8000/items/v1/cards/something
In the second one, however, you attempted to pass a Query parameter in the wrong way. As per the documentation:
When you declare other function parameters that are not part of the
path parameters, they are automatically interpreted as "query"
parameters.
Hence, your endpoint should look like this:
#app.post('/items/v1/cards')
async def create_item(sku: str):
return {'sku': sku}
The query is the set of key-value pairs that go after the ? in a URL,
separated by & characters.
URL Example:
http://127.0.0.1:8000/items/v1/cards?sku=something
I have a view that is defined like such:
#views.route('/issues')
def show_view():
action = request.args.get('action')
method = getattr(process_routes, action)
return method(request.args)
in my process_routes module, I would like to call this method, and pass query string values. I have the following code:
return redirect(url_for('views.show_view', action='create_or_update_success'))
I have a function in process_routes named create_or_update_success
I am getting
BuildError: ('views.show_view', {'action': 'create_or_update_success'}, None)
views is a blueprint. I can successfully call
/issues?action=create_or_update_success
In my browser.
What am I doing wrong?
The first part, views., has to reflect the first argument you give to your Blueprint() object exactly.
Don't be tempted to set that first argument to __name__, as that is likely to contain the full path of the module when inside a package. In your case I suspect that to be some_package.views rather than just views.
Use a string literal for the Blueprint() first argument instead:
views_blueprint = Blueprint('views', __name__)
so you can refer to url_for('views.show_view') without getting build errors.
I'm trying to create several URLs on my serv thanks to a loop . The issue is that each function I create in a app.route can't have the same name than the others . And I don't know how to create different function names ...
Here is the code :
json_tweets = []
for line in open('C:\Users\Benjamin\Desktop\DashboardProject\last_rated_set.json',"r"):
json_tweets.append(json.loads(line,"ISO-8859-1"))
cashtag_tab = []
for tweet in json_tweets:
if not(tweet['cashtag'] in cashtag_tab) :
cashtag_tab.append(tweet['cashtag'])
for i in range(0,(len(cashtag_tab)-1)) :
var=cashtag_tab[i]
#app.route("/"+var)
def company(var) :
finance=Share(var)
datas = finance.get_historical('2014-01-01', '2014-12-31')
datas = json.dumps(datas, default=json_util.default)
return datas
I'm getting the error AssertionError : View function mapping is overwritting an existing endpoint function : company
This fails because Flask derives the endpoint name from the function by default, but it would anyway fail later because the function company requires an argument var and the route is not parameterised. The simplest option would be just checking the value inside the handler:
#api.route('/<var>')
def company(var):
if var not in cashtag_tab:
abort(404)
If you want all the routes to be in the routing map for any reason, I once needed a similar thing and came up with something like this:
def url_family(source, methods=('GET',)):
def decorator(f):
for entry in source:
# create a handler that delegates to your function
def view_func(entry=entry, **kwargs):
return f(entry, **kwargs)
endpoint = '{0}_{1}'.format(f.__name__, entry)
url = '/{0}'.format(entry)
api.add_url_rule(url,
methods=methods,
endpoint=endpoint,
view_func=view_func)
return decorator
Then you register the handlers as:
#url_family(cashtag_tab)
def company(var):
...
Assuming that you are using flask now, you should consider Custom URL Converter. Check links below
http://flask.pocoo.org/docs/0.10/api/#flask.Flask.url_map - url_map UrlConverter API
https://exploreflask.com/views.html#url-converters - example url converter
https://stackoverflow.com/a/5872904/3451543 - RegexConverter by Philip Southam
Anyway, specifying more details on your question is always helpful to get accurate answer :)
I want to unit test my class, which is in another file named client_blogger.py.
My unit test file, is in the same directory. All of my other unit tests work, except when I try to mock one of my own methods.
## unit_test_client_blogger.py
import mock
import json
from client_blogger import BloggerClient, requests
Class TestProperties():
#pytest.fixture
def blog(self):
return BloggerClient(api_key='123', url='http://example.com')
#mock.patch('client_blogger._jload')
#mock.patch('client_blogger._send_request')
def test_gets_blog_info(self, mock_send, mock_jload):
""" Get valid blog info from API response. """
valid_blog_info = 'some valid json api response here'
parsed_response = json.loads(valid_blog_info)
correct_blog_id = '7488272653173849119'
mock_jload.return_value = valid_blog_info
id = self.blog().get_blog_info(parsed_response)
assert id == correct_blog_id
And here is the client_blogger.py file contents:
# client_blogger.py
import requests, json
class BloggerClient(object):
""" Client interface for Blogger API. """
def __init__(self, key, url):
# removed some code here for brevity
def _send_request(self, api_request):
""" Sends an HTTP get request to Blogger API.
Returns HTTP response in text format. """
# snip
def _jload(self, api_response):
""" Accepts text API response. Returns JSON encoded response. """
# snip
def get_blog_info(self):
""" Makes an API request. Returns Blog item information. """
request = '{b}/blogs/byurl?url={u}&key={k}'.format(b=self.base, u=self.url, k=self.key)
txt_response = self.send_request(request)
response = self._jload(txt_response)
return response['id']
I want to mock out self.send_request() and self._jload() method calls in the above method.
But Mock module complains: ImportError: No module named client_blogger.
The error must lie here:
#mock.patch('client_blogger._jload')
#mock.patch('client_blogger._send_request')
I've tried many variations in order to get mock.patch to find my module or class. But none of them have worked.
I've tried the following:
#mock.patch('client_blogger.BloggerClient._jload')
#mock.patch('BloggerClient._jload')
#mock.patch('._jload')
None of those work. Any idea how to mock.patch a method from my own module?
(It seems strange, because I can mock.patch other modules, just not my own :-s)
You want this:
#mock.patch('client_blogger.BloggerClient._jload')
#mock.patch('client_blogger.BloggerClient._send_request')
def test_gets_blog_info(self, mock_send, mock_jload):
""" Get valid blog info from API response. """
valid_blog_info = 'some valid json api response here'
parsed_response = json.loads(valid_blog_info)
correct_blog_id = '7488272653173849119'
mock_jload.return_value = valid_blog_info
id = self.blog().get_blog_info(parsed_response)
assert id == correct_blog_id
The BloggerClient implementation is coming from the client_blogger module, so you need to patch client_blogger.BloggerClient. You list that as one of the things you tried that doesn't work, but I just tried it, and it works fine for me. What issue did you have when you tried that?
You need to include the file name of the class in the path, before the object name.
e.g. if I have a method named foo in a class named Event in tools/event.py the path will be as follows:
patch("tools.event.Event.foo", boo)
For python3, the format is as follows:
from unittest.mock import patch
#patch('client_blogger.BloggerClient._jload')
.
.
.
Docs: https://docs.python.org/3/library/unittest.mock.html#patch
This is very, very important:
patch() is straightforward to use. The key is to do the patching in the right namespace. See the section where to patch.
I have the following hook in BottlePy:
#bottle_app.hook('before_request')
def update_session():
# do stuff
return
And some routes:
#bottle_app.route('/')
def index():
return render('index')
#bottle_app.route('/example')
def example():
return render('example')
From within update_session(), how can I determine which route is being called?
I have looked through the documentation to no avail, but surely this is possible?
The request has both a bottle.route and a route.handle entry, both contain the same value:
from bottle import request
print request['bottle.route']
This isn't documented; I had to find it in the bottle.py source. The value is a Route instance; it has both a .name and a .rule attribute you could inspect to determine which route was matched.
if request['bottle.route'].rule == '/':
# matched the `/` route.
For your specific example this is perhaps overkill, since you are only matching simple paths, but for more complex rules with regular expression rules this would work better than trying to match the request.path attribute (but it'd be a good idea to give your routes a name value).
from bottle import request
#bottle_app.hook('before_request')
def update_session():
print request.path
return
Should do what your asking for
You can then store routes in a dictionary.
my_routes = {"/":lambda: 5}
event = my_routes.get(request.path, lambda: None)
print event()