Django Keycloak integration flow - python

Try to follow the follow steps to integrate keycloak with Django system without using the builtin contrib.auth app.
My question is:
If I am not using the built-in User objects, how do I determine if a user is authenticated or not, by using the SSO session Keycloak generated?
Step 5, associate it with a session, what does it mean?
Thanks!

This might be a little late but could be helpful to someone in the future.
The contrib.auth is useful when you want to implement Django Authentication only.
The given approach assumes you are using the users from Keycloak and don't have a Django User Object to work with. If you are looking for later case, look at this package - https://django-keycloak.readthedocs.io/en/latest/
My usecase for integration of Keycloak with React Frontend and a REST DRF Backend. For integration with keycloak, you can make a custom middleware responsible for unpacking access_tokens generated from keycloak send in the Authorization Header on each request and validate permissions based on your client config file you need to export from keycloak from your respective client.
This is the middleware I used for my usecase
import re
import logging
from django.conf import settings
from django.http.response import JsonResponse
from django.utils.deprecation import MiddlewareMixin
from keycloak import KeycloakOpenID
from keycloak.exceptions import (
KeycloakInvalidTokenError,
)
from rest_framework.exceptions import (
PermissionDenied,
AuthenticationFailed,
NotAuthenticated,
)
logger = logging.getLogger(__name__)
class KeycloakMiddleware(MiddlewareMixin):
"""
Custom KeyCloak Middleware for Authentication and Authorization
"""
def __init__(self, get_response):
"""
:param get_response:
"""
super().__init__(get_response=get_response)
self.config = settings.KEYCLOAK_CONFIG
# Read configurations
try:
self.server_url = self.config["KEYCLOAK_SERVER_URL"]
self.client_id = self.config["KEYCLOAK_CLIENT_ID"]
self.realm = self.config["KEYCLOAK_REALM"]
except KeyError as keycloak_snippet_no_exist:
raise Exception(
"KEYCLOAK_SERVER_URL, KEYCLOAK_CLIENT_ID or KEYCLOAK_REALM not found."
) from keycloak_snippet_no_exist
self.client_secret_key = self.config.get("KEYCLOAK_CLIENT_SECRET_KEY", None)
self.client_public_key = self.config.get("KEYCLOAK_CLIENT_PUBLIC_KEY", None)
self.default_access = self.config.get("KEYCLOAK_DEFAULT_ACCESS", "DENY")
self.method_validate_token = self.config.get("KEYCLOAK_METHOD_VALIDATE_TOKEN", "INTROSPECT")
self.keycloak_authorization_config = self.config.get("KEYCLOAK_AUTHORIZATION_CONFIG", None)
# Create Keycloak instance
self.keycloak = KeycloakOpenID(
server_url=self.server_url,
client_id=self.client_id,
realm_name=self.realm,
client_secret_key=self.client_secret_key,
)
# Read policies
if self.keycloak_authorization_config:
self.keycloak.load_authorization_config(self.keycloak_authorization_config)
# Django
self.get_response = get_response
#property
def keycloak(self):
"""
Getter KeyCloak Instance
"""
return self._keycloak
#keycloak.setter
def keycloak(self, value):
self._keycloak = value
#property
def config(self):
"""
Getter Config Instance
"""
return self._config
#config.setter
def config(self, value):
self._config = value
#property
def server_url(self):
"""
Getter Server URL
"""
return self._server_url
#server_url.setter
def server_url(self, value):
self._server_url = value
#property
def client_id(self):
"""
Getter Client_ID of KeyCloak Client
"""
return self._client_id
#client_id.setter
def client_id(self, value):
self._client_id = value
#property
def client_secret_key(self):
"""
Getter Client Secret Key
"""
return self._client_secret_key
#client_secret_key.setter
def client_secret_key(self, value):
self._client_secret_key = value
#property
def client_public_key(self):
"""
Getter Client Public Key
"""
return self._client_public_key
#client_public_key.setter
def client_public_key(self, value):
self._client_public_key = value
#property
def realm(self):
"""
Getter KeyClaok Realm
"""
return self._realm
#realm.setter
def realm(self, value):
self._realm = value
#property
def keycloak_authorization_config(self):
"""
Getter KeyCloak Authorization Config
"""
return self._keycloak_authorization_config
#keycloak_authorization_config.setter
def keycloak_authorization_config(self, value):
self._keycloak_authorization_config = value
#property
def method_validate_token(self):
"""
Getter Validate Token Private Method
"""
return self._method_validate_token
#method_validate_token.setter
def method_validate_token(self, value):
self._method_validate_token = value
def __call__(self, request):
"""
:param request:
:return:
"""
return self.get_response(request)
def process_view(self, request, view_func, view_args, view_kwargs):
# pylint: disable=unused-argument
"""
Validate only the token introspect.
:param request: django request
:param view_func:
:param view_args: view args
:param view_kwargs: view kwargs
:return:
"""
if hasattr(settings, "KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS"):
path = request.path_info.lstrip("/")
if any(re.match(m, path) for m in settings.KEYCLOAK_BEARER_AUTHENTICATION_EXEMPT_PATHS):
logger.debug("** exclude path found, skipping")
return None
try:
view_scopes = view_func.cls.keycloak_scopes
except AttributeError as _keycloak_attribute_error:
logger.debug(
"Allowing free acesss, since no authorization configuration (keycloak_scopes) \
found for this request route :%s",
request,
)
return None
if "HTTP_AUTHORIZATION" not in request.META:
return JsonResponse(
{"detail": NotAuthenticated.default_detail},
status=NotAuthenticated.status_code,
)
auth_header = request.META.get("HTTP_AUTHORIZATION").split()
token = auth_header[1] if len(auth_header) == 2 else auth_header[0]
# Get default if method is not defined.
required_scope = (
view_scopes.get(request.method, None)
if view_scopes.get(request.method, None)
else view_scopes.get("DEFAULT", None)
)
# DEFAULT scope not found and DEFAULT_ACCESS is DENY
if not required_scope and self.default_access == "DENY":
return JsonResponse(
{"detail": PermissionDenied.default_detail},
status=PermissionDenied.status_code,
)
try:
# >>>>>>> Added Options kwargs to verify decode permissions to django middleware
options = {
"verify_signature": True,
"verify_aud": False,
"verify_exp": True,
}
user_permissions = self.keycloak.get_permissions(
token,
method_token_info=self.method_validate_token.lower(),
key=self.client_public_key,
options=options,
)
except KeycloakInvalidTokenError as _keycloak_invalid_token_error:
return JsonResponse(
{"detail": AuthenticationFailed.default_detail},
status=AuthenticationFailed.status_code,
)
for perm in user_permissions:
if required_scope in perm.scopes:
return None
# User Permission Denied
return JsonResponse(
{"detail": PermissionDenied.default_detail},
status=PermissionDenied.status_code,
)
With this approach, you can define policy authorization straight inside your view class similar to this.
class GetMockData(APIView):
"""
Another GET API to Fetch fake Data
"""
keycloak_scopes = {"GET": "medicine:view"}
def get(self, request: HttpRequest) -> HttpResponse:
"""
V1 API to get some medicine data from the database
"""
data = get_all_medicines()
if not data:
res = CustomResponse(
success=False,
payload=None,
error=E_RANGE_MESSAGE,
status=status.HTTP_404_NOT_FOUND,
)
return res.send_response()
logger.warning("Testing a fake warning >>>>>>>> WARNING <<<<<<<<?")
data = MedicineSerializer(data, many=True).data
res = CustomResponse(success=True, payload=data)
return res.send_response()

Related

Getting UpdateNotify error when implementing a python RTD client

I'm trying to implement an RTD client using this project as an example, but without success.
Instance as RTD server the example contained in the win32com package below, and in Excel it works perfectly, but in the RTD client used as a template, it generates this error.
RTD client code
import functools
import pythoncom
import win32com.client
from win32com import universal
from win32com.client import gencache
from win32com.server.util import wrap
EXCEL_TLB_GUID = '{00020813-0000-0000-C000-000000000046}'
EXCEL_TLB_LCID = 0
EXCEL_TLB_MAJOR = 1
EXCEL_TLB_MINOR = 4
gencache.EnsureModule(EXCEL_TLB_GUID, EXCEL_TLB_LCID, EXCEL_TLB_MAJOR, EXCEL_TLB_MINOR)
universal.RegisterInterfaces(EXCEL_TLB_GUID,
EXCEL_TLB_LCID, EXCEL_TLB_MAJOR, EXCEL_TLB_MINOR,
['IRtdServer', 'IRTDUpdateEvent'])
# noinspection PyProtectedMember
class ObjectWrapperCOM:
LCID = 0x0
def __init__(self, obj):
self._impl = obj # type: win32com.client.CDispatch
def __getattr__(self, item):
flags, dispid = self._impl._find_dispatch_type_(item)
if dispid is None:
raise AttributeError("{} is not a valid property or method for this object.".format(item))
return functools.partial(self._impl._oleobj_.Invoke, dispid, self.LCID, flags, True)
# noinspection PyPep8Naming
class RTDUpdateEvent:
_com_interfaces_ = ['IRTDUpdateEvent']
_public_methods_ = ['Disconnect', 'UpdateNotify']
_public_attrs_ = ['HeartbeatInterval']
# Implementation of IRTDUpdateEvent.
HeartbeatInterval = -1
def __init__(self, event_driven=True):
self.ready = False
self._event_driven = event_driven
def UpdateNotify(self):
if self._event_driven:
self.ready = True
def Disconnect(self):
pass
class RTDClient:
MAX_REGISTERED_TOPICS = 1024
def __init__(self, class_id):
"""
:param classid: can either be class ID or program ID
"""
self._class_id = class_id
self._rtd = None
self._update_event = None
self._topic_to_id = {}
self._id_to_topic = {}
self._topic_values = {}
self._last_topic_id = 0
def connect(self, event_driven=True):
"""
Connects to the RTD server.
Set event_driven to false if you to disable update notifications.
In this case you'll need to call refresh_data manually.
"""
dispatch = win32com.client.Dispatch(self._class_id)
self._update_event = RTDUpdateEvent(event_driven)
try:
self._rtd = win32com.client.CastTo(dispatch, 'IRtdServer')
except TypeError:
# Automated makepy failed...no detailed construction available for the class
self._rtd = ObjectWrapperCOM(dispatch)
self._rtd.ServerStart(wrap(self._update_event))
def update(self):
"""
Check if there is data waiting and call RefreshData if necessary. Returns True if new data has been received.
Note that you should call this following a call to pythoncom.PumpWaitingMessages(). If you neglect to
pump the message loop you'll never receive UpdateNotify callbacks.
"""
# noinspection PyUnresolvedReferences
pythoncom.PumpWaitingMessages()
if self._update_event.ready:
self._update_event.ready = False
self.refresh_data()
return True
else:
return False
def refresh_data(self):
"""
Grabs new data from the RTD server.
"""
(ids, values) = self._rtd.RefreshData(self.MAX_REGISTERED_TOPICS)
for id_, value in zip(ids, values):
if id_ is None and value is None:
# This is probably the end of message
continue
assert id_ in self._id_to_topic, "Topic ID {} is not registered.".format(id_)
topic = self._id_to_topic[id_]
self._topic_values[topic] = value
def get(self, topic: tuple):
"""
Gets the value of a registered topic. Returns None if no value is available. Throws an exception if
the topic isn't registered.
"""
assert topic in self._topic_to_id, 'Topic %s not registered.' % (topic,)
return self._topic_values.get(topic)
def register_topic(self, topic: tuple):
"""
Registers a topic with the RTD server. The topic's value will be updated in subsequent data refreshes.
"""
if topic not in self._topic_to_id:
id_ = self._last_topic_id
self._last_topic_id += 1
self._topic_to_id[topic] = id_
self._id_to_topic[id_] = topic
self._rtd.ConnectData(id_, topic, True)
def unregister_topic(self, topic: tuple):
"""
Un-register topic so that it will not get updated.
:param topic:
:return:
"""
assert topic in self._topic_to_id, 'Topic %s not registered.' % (topic,)
self._rtd.DisconnectData(self._topic_to_id[topic])
def disconnect(self):
"""
Closes RTD server connection.
:return:
"""
self._rtd.ServerTerminate()
The example RTD Server is Python.RTD.TimeServer and it works great in Excel, but the RTD client in the above example throws this error:
File "C:\Users\XXXXXX\AppData\Local\Temp\gen_py\3.9\00020813-0000-0000-C000-000000000046x0x1x9.py", line 20963, in UpdateNotify
return self.oleobj.InvokeTypes(10, LCID, 1, (24, 0), (),)
pywintypes.com_error: (-2147352573, 'Member not found.', None, None)
I have no knowledge of COM, but in the struggle to learn.
Any suggestions from friends?
You need to implement all the methods defined by the IRTDServer interface.
https://learn.microsoft.com/en-us/dotnet/api/microsoft.office.interop.excel.irtdserver?view=excel-pia
Once you do that excel should be able to find all methods it needs to work with your server.

Python constructor overloading with zero arguments

There are many SO QAs and other articles about how to create multiple constructors.
But I couldn't find how to achieve what I'm looking for,
Consider the below methods, the requirement is to create __init__ with zero args and creds args, so at initialization it will return session based on arguments.
Since that is not possible, a classmethod is introduced to initialize the instance with creds as shown below. But as you can see, it will not work.
#classmethod
def with_keys(cls, access_key, secret_key):
"""
Initialize boto3 session based on key and secret.
"""
# Initialize variables and session <= How to do this?
# self._access_key = access_key
# self._secret_key = secret_key
# self._session = boto3.Session(
# aws_access_key_id=self._access_key,
# aws_secret_access_key=self._secret_key,
# )
return cls() # <= this will overwrite the session
def __init__(self):
"""
Initialize boto3 session based on IAM Role.
"""
self._access_key = None
self._secret_key = None
self._session = boto3.Session()
of course, I can achieve this the other way as shown below. But is it possible to do it without providing args to __init__?
#classmethod
def default(cls):
"""
Initialize boto3 session based on IAM Role/Default Credentials
"""
return cls(None, None)
def __init__(self, access_key=None, secret_key=None):
"""
Initialize boto3 session based key and secret.
"""
self._access_key = access_key
self._secret_key = secret_key
if access_key == None:
self._session = boto3.Session()
else:
self._session = boto3.Session(
aws_access_key_id=self._access_key,
aws_secret_access_key=self._secret_key,
)

Flask-restful basic Authentication

I am new to Flask and I need some help for my school work.
I am trying to build a simple ToDo list system using flask-restful.
My current code looks like this:
class ToDoList(Resource):
'''TODO LIST'''
operation = ['delete']
decorators = [auth.login_required, advertise('operation')]
def post(self):
"""remove all item in the TODO list"""
operation = request.args.get('op')
if operation == 'delete':
collection2.delete_many({})
return {'Success': 'OK'}, 200
return {'Error':'Illegal Operation'}, 400
def get(self):
"""return a list of the TODO name"""
list_1 = collection2.find()
list_2 = []
for each in list_1:
list_2.append(JSONEncoder().encode(each))
return {'list':list_2}, 200
It works, but I want only the post method to require authentication, and get method without authentication so anyone can acquire the list without login. I am using the flask-restful I don't know how to give the decorators separately to each function.
I used flaskrestplus to do basic authentication. All the required authorizations are provided as an authorizations dictionary. Then they are passed to the API.
Also the authorizations can be applied at the method level using
#api.doc(security='basicAuth')
The validation logic (can be ldap validation or db validation) can be writted in a decorator called requires_Auth. This decorator is invoked using
decorators = [requires_Auth]
Complete code
from flask import Flask, request
from flask_restplus import Api, Resource
from functools import wraps
def requires_Auth(f):
#wraps(f)
def decorator(*args, **kwargs):
auth = request.authorization
if auth:
print "inside decorator", auth.username,auth.password
return f(*args, **kwargs)
else:
return "Login required!!!!",401
return decorator
authorizations = {
'basicAuth': {
'type': 'basic',
'in': 'header',
'name': 'Authorization'
}
}
api = Api(app, version='1.0',
authorizations=authorizations
)
ns = api.namespace('/', description='Authentication API')
#ns.route('/withDecorator')
class HelloWorldWithDecorator(Resource):
decorators = [requires_Auth]
#api.doc(security='basicAuth')
def get(self):
return {'hello': 'world'}
api.add_namespace(ns)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0', port=5001)
From Flask-RESTful documentation [1]:
Alternatively, you can specify a dictionary of iterables that map to HTTP methods and the decorators will only apply to matching requests.
def cache(f):
#wraps(f)
def cacher(*args, **kwargs):
# caching stuff
return cacher
class MyResource(restful.Resource):
method_decorators = {'get': [cache]}
def get(self, *args, **kwargs):
return something_interesting(*args, **kwargs)
def post(self, *args, **kwargs):
return create_something(*args, **kwargs)
In your case it would be:
method_decorators = {'post': [auth.login_required]}

How to pass callbacks and their arguments from wrapped function to decorator with Python 3.x?

I am writing a generic wrapper around a REST API. I have several functions like the one below, responsible for retrieving a user from its email address. The part of interest is how the response is processed, based on a list of expected status codes (besides HTTP 200) and callbacks associated to each expected status code:
import requests
def get_user_from_email(email):
response = requests.get('http://example.com/api/v1/users/email:%s' % email)
# define callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
expected_status_codes_and_callbacks = {
requests.codes.ok: return_as_json, # HTTP 200 == success
404: user_with_email_does_not_exist,
}
if response.status_code in expected_status_codes_and_callbacks:
callback = expected_status_codes_and_callbacks[response.status_code]
return callback(response)
else:
response.raise_for_status()
john_doe = get_user_from_email('john.doe#company.com')
print(john_doe is not None) # True
unregistered_user = get_user_from_email('unregistered.user#company.com')
print(unregistered_user is None) # True
The code above works well so I want to refactor and generalize the response processing part. I would love to end up with the following code:
#process_response({requests.codes.ok: return_as_json, 404: user_with_email_does_not_exist})
def get_user_from_email(email):
# define callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
return requests.get('https://example.com/api/v1/users/email:%s' % email)
with the process_response decorator defined as:
import functools
def process_response(extra_response_codes_and_callbacks=None):
def actual_decorator(f):
#functools.wraps(f)
def wrapper(*args, **kwargs):
response = f(*args, **kwargs)
if response.status_code in expected_status_codes_and_callbacks:
action_to_perform = expected_status_codes_and_callbacks[response.status_code]
return action_to_perform(response)
else:
response.raise_for_status() # raise exception on unexpected status code
return wrapper
return actual_decorator
My problem is the decorator complains about not having access to return_as_json and user_with_email_does_not_exist because these callbacks are defined inside the wrapped function.
If I decide to move the callbacks outside of the wrapped function, for example at the same level as the decorator itself, then the callbacks have no access to the response and email variables inside the wrapped function.
# does not work either, as response and email are not visible from the callbacks
def return_as_json(response):
print('Found user with email [%s].' % email)
return response.json()
def user_with_email_does_not_exist(response):
print('Could not find any user with email [%s]. Returning `None`.' % email),
return None
#process_response({requests.codes.ok: return_as_json, 404: user_with_email_does_not_exist})
def get_user_from_email(email):
return requests.get('https://example.com/api/v1/users/email:%s' % email)
What is the right approach here? I find the decorator syntax very clean but I cannot figure out how to pass the required parts to it (either the callbacks themselves or their input arguments like response and email).
You could convert the decorator keys into strings, and then pull the inner functions from the outer function passed to the decorator via f.func_code.co_consts. Don't do it this way.
import functools, new
from types import CodeType
def decorator(callback_dict=None):
def actual_decorator(f):
code_dict = {c.co_name: c for c in f.func_code.co_consts if type(c) is CodeType}
#functools.wraps(f)
def wrapper(*args, **kwargs):
main_return = f(*args, **kwargs)
if main_return['callback'] in callback_dict:
callback_string = callback_dict[main_return['callback']]
callback = new.function(code_dict[callback_string], {})
return callback(main_return)
return wrapper
return actual_decorator
#decorator({'key_a': 'function_a'})
def main_function(callback):
def function_a(callback_object):
for k, v in callback_object.items():
if k != 'callback':
print '{}: {}'.format(k, v)
return {'callback': callback, 'key_1': 'value_1', 'key_2': 'value_2'}
main_function('key_a')
# key_1: value_1
# key_2: value_2
Can you use classes? The solution is immediate if you can use a class.
As mentioned in the comments for my other answer, here is an answer that uses classes and decorators. It's a bit counter-intuitive because get_user_from_email is declared as a class, but ends up as a function after decorating. It does have the desired syntax however, so that's a plus. Maybe this could be a starting point for a cleaner solution.
# dummy response object
from collections import namedtuple
Response = namedtuple('Response', 'data status_code error')
def callback_mapper(callback_map):
def actual_function(cls):
def wrapper(*args, **kwargs):
request = getattr(cls, 'request')
response = request(*args, **kwargs)
callback_name = callback_map.get(response.status_code)
if callback_name is not None:
callback_function = getattr(cls, callback_name)
return callback_function(response)
else:
return response.error
return wrapper
return actual_function
#callback_mapper({'200': 'json', '404': 'does_not_exist'})
class get_user_from_email:
#staticmethod
def json(response):
return 'json response: {}'.format(response.data)
#staticmethod
def does_not_exist(response):
return 'does not exist'
#staticmethod
def request(email):
response = Response('response data', '200', 'exception')
return response
print get_user_from_email('blah')
# json response: response data
Here's an approach that uses function member data on class methods in order to map the response function to the appropriate callback. This seems like the cleanest syntax to me, but still has a class turning into a function (which could be easily avoided if desired).
# dummy response object
from collections import namedtuple
Response = namedtuple('Response', 'data status_code error')
def callback(status_code):
def method(f):
f.status_code = status_code
return staticmethod(f)
return method
def request(f):
f.request = True
return staticmethod(f)
def callback_redirect(cls):
__callback_map = {}
for attribute_name in dir(cls):
attribute = getattr(cls, attribute_name)
status_code = getattr(attribute, 'status_code', '')
if status_code:
__callback_map[status_code] = attribute
if getattr(attribute, 'request', False):
__request = attribute
def call_wrapper(*args, **kwargs):
response = __request(*args, **kwargs)
callback = __callback_map.get(response.status_code)
if callback is not None:
return callback(response)
else:
return response.error
return call_wrapper
#callback_redirect
class get_user_from_email:
#callback('200')
def json(response):
return 'json response: {}'.format(response.data)
#callback('404')
def does_not_exist(response):
return 'does not exist'
#request
def request(email):
response = Response(email, '200', 'exception')
return response
print get_user_from_email('generic#email.com')
# json response: generic#email.com
You could pass the function parameters of the outer function to the handlers:
def return_as_json(response, email=None): # email param
print('Found user with email [%s].' % email)
return response.json()
#process_response({requests.codes.ok: return_as_json, 404: ...})
def get_user_from_email(email):
return requests.get('...: %s' % email)
# in decorator
# email param will be passed to return_as_json
return action_to_perform(response, *args, **kwargs)

flask: How to store and retrieve POST calls?

#app.route('/path/<user>/<id>', methods=['POST'])
#cache.cached(key_prefix='/path/<user>', unless=True)
def post_kv(user, id):
cache.set(user, id)
return value
#app.route('/path/<user>', methods=['GET'])
#cache.cached(key_prefix='/path/<user>', unless=True)
def getkv(user):
cache.get(**kwargs)
I want to be able to make POST calls to the path described, store them, and GET their values from the user. The above code runs, but has bugs and doesn't perform as needed. Frankly, the flask-cache docs aren't helping. How can I properly implement cache.set and cache.get to perform as needed?
In Flask, implementing your custom cache wrapper is very simple.
from werkzeug.contrib.cache import SimpleCache, MemcachedCache
class Cache(object):
cache = SimpleCache(threshold = 1000, default_timeout = 100)
# cache = MemcachedCache(servers = ['127.0.0.1:11211'], default_timeout = 100, key_prefix = 'my_prefix_')
#classmethod
def get(cls, key = None):
return cls.cache.get(key)
#classmethod
def delete(cls, key = None):
return cls.cache.delete(key)
#classmethod
def set(cls, key = None, value = None, timeout = 0):
if timeout:
return cls.cache.set(key, value, timeout = timeout)
else:
return cls.cache.set(key, value)
#classmethod
def clear(cls):
return cls.cache.clear()
...
#app.route('/path/<user>/<id>', methods=['POST'])
def post_kv(user, id):
Cache.set(user, id)
return "Cached {0}".format(id)
#app.route('/path/<user>', methods=['GET'])
def get_kv(user):
id = Cache.get(user)
return "From cache {0}".format(id)
Also, the simple memory cache is for single process environments and the SimpleCache class exists mainly for the development server and is not 100% thread safe. In production environments you should use MemcachedCache or RedisCache.
Make sure you implement logic when item is not found in the cache.

Categories