I have configured a dependencies.py where I'm injecting a set of dependencies to different services by using python's binder.bind(my_config). The goal is being able to easily inject those services to each endpoint of my API. The problem arises when I pass that service as an argument to my endpoint , after having injected that service via its name. So:
import inject
from fastapi import APIRouter, HTTPException, Request, Depends
from src.services.chords import ChordsService
router = APIRouter(prefix="/chords")
#router.get("")
#inject.params(chords_service=ChordsService)
def get_chords(chords_service: ChordsService, req: Request, key: str, suffix: str = None, instrument: str = None):
params = dict(req.query_params)
return chords_service.get(params)
This does not work. I've tried changing the order of get_chords' arguments. All I'm getting is different errors, but the one that appears the most is the following:
ChordsService is not a valid pydantic field type
I've read a bit about the use of pydantic in FastAPI and I see why I get this error, but I'm stuck at trying to inject those services. Is there a way to do it? Thanks!
You could use the dependency injection from fastapi directly. I don't have an IDE, so syntax is probably wrong, but you could do something like:
#lru_cache(max_size=1)
def get_chords_service():
return ChordsService()
#router.get("")
def get_chords(chords_service: ChordsService=Depends(get_chords_service), req: Request ...
This if you want the same ChordService instance everywhere.
If you are ok getting a new one each time, it becomes much simpler (you don't even need the getter function):
#router.get("")
def get_chords(chords_service: ChordsService=Depends(), req: Request ...
You can inject dependency to APIRouter like below -
router = APIRouter(prefix="/chords",
dependencies=[Depends(ChordsService)])
See Example: https://fastapi.tiangolo.com/tutorial/bigger-applications/#another-module-with-apirouter
Related
i want the client to pass aditional information while loggin in to FastApi. I think for that i have to change the scheme for OAuth2PasswordRequestForm. Can anyone explain how to do that?
Im using the code from the FastApi tutorial right now:
https://fastapi.tiangolo.com/tutorial/security/oauth2-jwt/
If I understand your question correctly, you would like to have the user pass the info required by OAuth2PasswordRequestForm and also include some extra required information.
The easiest way to do this would probably be to create your own scheme that is a subclass of OAuth2PasswordRequestForm.
import fastapi
from fastapi import Depends
from fastapi.security import OAuth2PasswordRequestForm
app = fastapi.FastAPI()
class ExtendedOAuth2PasswordRequestForm(OAuth2PasswordRequestForm):
extra_data_field: str
#app.post("/your_endpoint")
def login_for_access_token(form_data: ExtendedOAuth2PasswordRequestForm = Depends()):
#do stuff with all of the normal OAuth2PasswordRequestForm and your extra data field...
You can add extend parameter to body request as follow:
And add the parameter to form data in frontend part:
In the python standard environment quickstart, the endpoints method test_api_key returns a 503 Service Unavailable. The error occurs in the API Explorer when run with dev_appser.py and when the API is deployed. The code for it is:
import endpoints
from protorpc import message_types
from protorpc import messages
from protorpc import remote
class TestResponse(messages.Message):
content = messages.StringField(1)
#endpoints.api(name='practice', version='v1', description='My Practice API')
class PracticeApi(remote.Service):
#endpoints.method(
message_types.VoidMessage,
TestResponse,
path='test/getApiKey',
http_method='GET',
name='test_api_key')
def test_api_key(self, request):
return TestResponse(content=request.get_unrecognized_field_info('key'))
api = endpoints.api_server([PracticeApi])
I don't have a good understanding of .get_unrecognized_field_info('key') so I am not sure what the issue is? Thanks.
Firstly, I recommend reading Google Protocol RPC Library Overview, since it's Google Cloud Endpoints uses it extensively.
#endpoints.method allows you to configure a specific method in your API. Configuration options are documented in Google Cloud Platform documentation Creating an API with Cloud Endpoints Frameworks for App Engine, in the section, Defining an API method (#endpoints.method).
If you're restricting access to the test/getApiKey/test_api_key method, then you must configure the method with the api_key_required=True option. Restricting API Access with API Keys (Frameworks) discusses that further, but your method annotation should be:
#endpoints.method(
message_types.VoidMessage,
TestResponse,
path='test/getApiKey',
http_method='GET',
name='test_api_key',
api_key_required=True
)
Notice your method accepts a request parameter representing the HTTP request (i.e. client using your API):
def test_api_key(self, request):
However, the request parameter is actually Google Protocol RPC Message (Proto RPC) Message object and as such is very well defined. If additional fields exist in the ProtoRPC request parameter, beyond what is formally defined, they are still stored with the request object but must be retrieved using the following method:
def get_unrecognized_field_info(self, key, value_default=None,
variant_default=None):
"""Get the value and variant of an unknown field in this message.
Args:
key: The name or number of the field to retrieve.
value_default: Value to be returned if the key isn't found.
variant_default: Value to be returned as variant if the key isn't
found.
Returns:
(value, variant), where value and variant are whatever was passed
to set_unrecognized_field.
"""
Message class code on GitHub is quite well documented. .
No arguments will appear in the body of a request because you've configured the method with to be called with HTTP GET:
http_method='GET'
...you're correctly using the value message_types.VoidMessage.
In terms of your error, 503 is just a generic server error, can you provide any information from the StackDriver logs? They will point you to the exact line and error in your code.
There were three things that were creating the 503 error.
Firstly, I needed to make the method or entire Api require an Api Key. In this case I just applied it to the entire Api:
#endpoints.api(name='practice', version='v1', api_key_required=True)
class PracticeApi(remote.Service):
Secondly, after I generated the Api Key in the cloud console I needed to put the Key into the openapi.json file before deploying it.
Lastly, I was still getting a validation error:
ValidationError: Expected type <type 'unicode'> for field content, found (u'My Api Key', Variant(STRING, 9)) (type <type 'tuple'>)
The get_unrecognized_field_info() function returns a tuple of (value, variant). A tuple was not expected by the response so I updated the method to only show value:
def test_api_key(self, request):
return TestResponse(content=request.get_unrecognized_field_info('key')[0])
I have a thorny problem that I can't seem to get to grips with. I am
currently writing unit tests for a django custom auth-backend. On our
system we actually have two backends: one the built-in django backend
and the custom backend that sends out requests to a Java based API
that returns user info in the form of XML. Now, I am writing unit
tests so I don't want to be sending requests outside the system like
that, I'm not trying to test the Java API, so my question is how can I
get around this and mock the side-effects in the most robust way.
The function I am testing is something like this, where the url
settings value is just the base url for the Java server that
authenticates the username and password data and returns the xml, and the service value is
just some magic for building the url query, its unimportant for
us:
#staticmethod
def get_info_from_api_with_un_pw(username, password, service=12345):
url = settings.AUTHENTICATE_URL_VIA_PASSWORD
if AUTH_FIELD == "username":
params = {"nick": username, "password": password}
elif AUTH_FIELD == "email":
params = {"email": username, "password": password}
params["service"] = service
encoded_params = urlencode([(k, smart_str(v, "latin1")) for k, v in params.items()])
try:
# get the user's data from the api
xml = urlopen(url + encoded_params).read()
userinfo = dict((e.tag, smart_unicode(e.text, strings_only=True))
for e in ET.fromstring(xml).getchildren())
if "nil" in userinfo:
return userinfo
else:
return None
So, we get the xml, parse it into a dict and if the key nil is present
then we can return the dict and carry on happy and authenticated.
Clearly, one solution is just to find a way to somehow override or
monkeypatch the logic in the xml variable, I found this answer:
How can one mock/stub python module like urllib
I tried to implement something like that, but the details there are
very sketchy and I couldn't seem to get that working.
I also captured the xml response and put it in a local file in the
test folder with the intention of finding a way to use that as a mock
response that is passed into the url parameter of the test function,
something like this will override the url:
#override_settings(AUTHENTICATE_URL_VIA_PASSWORD=(os.path.join(os.path.dirname(__file__), "{0}".format("response.xml"))))
def test_get_user_info_username(self):
self.backend = RemoteAuthBackend()
self.backend.get_info_from_api_with_un_pw("user", "pass")
But that also needs to take account of the url building logic that the
function defines, (i.e. "url + encoded_params"). Again, I could rename
the response file to be the same as the concatenated url but this is becoming
less like a good unit-test for the function and more of a "cheat", the whole
thing is just getting more and more brittle all the time with these solutions, and its really just a fixture anyway, which is also something I want to avoid if
at all possible.
I also wondered if there might be a way to serve the xml on the django development server and then point the function at that? It seems like a saner solution, but much googling gave me no clues if such a thing would be possible or advisable and even then I don't think that would be a test to run outside of the development environment.
So, ideally, I need to be able to somehow mock a "server" to
take the place of the Java API in the function call, or somehow serve
up some xml payload that the function can open as its url, or
monkeypatch the function from the test itself, or...
Does the mock library have the appropriate tools to do such things?
http://www.voidspace.org.uk/python/mock
So, there are two points to this question 1) I would like to solve my
particular problem in a clean way, and more importantly 2) what are
the best practices for cleanly writing Django unit-tests when you are
dependent on data, cookies, etc. for user authentication from a remote
API that is outside of your domain?
The mock library should work if used properly. I prefer the minimock library and I wrote a small base unit testcase (minimocktest) that helps with this.
If you want to integrate this testcase with Django to test urllib you can do it as follows:
from minimocktest import MockTestCase
from django.test import TestCase
from django.test.client import Client
class DjangoTestCase(TestCase, MockTestCase):
'''
A TestCase class that combines minimocktest and django.test.TestCase
'''
def _pre_setup(self):
MockTestCase.setUp(self)
TestCase._pre_setup(self)
# optional: shortcut client handle for quick testing
self.client = Client()
def _post_teardown(self):
TestCase._post_teardown(self)
MockTestCase.tearDown(self)
Now you can use this testcase instead of using the Django test case directly:
class MySimpleTestCase(DjangoTestCase):
def setUp(self):
self.file = StringIO.StringIO('MiniMockTest')
self.file.close = self.Mock('file_close_function')
def test_urldump_dumpsContentProperly(self):
self.mock('urllib2.urlopen', returns=self.file)
self.assertEquals(urldump('http://pykler.github.com'), 'MiniMockTest')
self.assertSameTrace('\n'.join([
"Called urllib2.urlopen('http://pykler.github.com')",
"Called file_close_function()",
]))
urllib2.urlopen('anything')
self.mock('urllib2.urlopen', returns=self.file, tracker=None)
urllib2.urlopen('this is not tracked')
self.assertTrace("Called urllib2.urlopen('anything')")
self.assertTrace("Called urllib2.urlopen('this is mocked but not tracked')", includes=False)
self.assertSameTrace('\n'.join([
"Called urllib2.urlopen('http://pykler.github.com')",
"Called file_close_function()",
"Called urllib2.urlopen('anything')",
]))
Here's the basics of the solution that I ended up with for the record. I used the Mock library itself rather than Mockito in the end, but the idea is the same:
from mock import patch
#override_settings(AUTHENTICATE_LOGIN_FIELD="username")
#patch("mymodule.auth_backend.urlopen")
def test_get_user_info_username(self, urlopen_override):
response = "file://" + os.path.join(os.path.dirname(__file__), "{0}".format("response.xml"))
# mock patch replaces API call
urlopen_override.return_value = urlopen(response)
# call the patched object
userinfo = RemoteAuthBackend.get_info_from_api_with_un_pw("user", "pass")
assert_equal(type(userinfo), dict)
assert_equal(userinfo["nick"], "user")
assert_equal(userinfo["pass"], "pass")
I have some experience with different web frameworks (Django, web.py, Pyramid and CherryPy), and I'm wondering in which one will it be easier and hopefully cleaner to implement a route dispatcher to a different "view/handler" based on the "Accept" header and the HTTP method e.g.:
Accept: application/json
POST /post/
is handled different than:
Accept: text/html
POST /post/
So the request gets routed to the particular view of the corresponding handler of the MIME "application/json" and the HTTP method "POST".
I do know how to implement something like that in CherryPy, but I lose the use of the CherryPy tools for the internal redirection of the request because I'm calling the specific method directly instead of automagically from the dispatcher. Another option is to implement a full new dispatcher from scratch, but that's the last option.
I'm aware of the alternative to use extensions in the url like /post.json or /post/.json, but I'm looking to keep the same url?
If all you are looking for is one framework that can do this easily, then use pyramid.
Pyramid view definitions are made with predicates, not just routes, and a view only matches if all predicates match. One such predicate is the accept predicate, which does exactly what you want; make view switching depending on the Accept header easy and simple:
from pyramid.view import view_config
#view_config(route_name='some_api_name', request_method='POST', accept='application/json')
def handle_someapi_json(request):
# return JSON
#view_config(route_name='some_api_name', request_method='POST', accept='text/html')
def handle_someapi_html(request):
# return HTML
I needed to do this in Django, and so I wrote a piece of middleware to make it possible: http://baltaks.com/2013/01/route-requests-based-on-the-http-accept-header-in-django
Here is the code:
# A simple middleware component that lets you use a single Django
# instance to serve multiple versions of your app, chosen by the client
# using the HTTP Accept header.
# In your settings.py, map a value you're looking for in the Accept header
# to a urls.py file.
# HTTP_HEADER_ROUTING_MIDDLEWARE_URLCONF_MAP = {
# u'application/vnd.api-name.v1': 'app.urls_v1'
# }
from django.conf import settings
class HTTPHeaderRoutingMiddleware:
def process_request(self, request):
try:
for content_type in settings.HTTP_HEADER_ROUTING_MIDDLEWARE_URLCONF_MAP:
if (request.META['HTTP_ACCEPT'].find(content_type) != -1):
request.urlconf = settings.HTTP_HEADER_ROUTING_MIDDLEWARE_URLCONF_MAP[content_type]
except KeyError:
pass # use default urlconf (settings.ROOT_URLCONF)
def process_response(self, request, response):
return response
I'm not suite sure what you mean by "internal redirection", but if you look at the code you can see that tools.accept is a really thin wrapper around lib.cptools.accept, which you can call from your own code easily. Hand it a list of Content-Types your server can send, and it will tell you which one the client prefers, or raise 406 if the types you emit and the types the client accepts don't overlap.
The problem is simple, we would like CherryPy to not log access log for a particular exposed method/API that gets called.
Basically when this API gets called, there are some parameters in the query string of the URL which are very sensitive and if leaked, would expose potential security. Naturally this is a /GET request and unfortunately it is the only way the parameters could be passed, since its a redirect(302) from an external service to this web server.
If it would not log the URL, that would serve the purpose as well.
So, is there a way that we can filter logging messages in access log by API's, URL's etc?
Thanks in advance for the help.
cherrypy uses Python's standard logging module by default, so you can just add a custom filter. This example will ignore any GET request with /foo as the path prefix:
import logging
class IgnoreURLFilter(logging.Filter):
# simple example of log message filtering
def __init__(self, ignore):
self.ignore = 'GET /' + ignore
def filter(self, record):
return self.ignore not in record.getMessage()
app = cherrypy.tree.mount( YourApplication() )
app.log.access_log.addFilter( IgnoreURLFilter('foo') )
cherrypy.engine.start()