Understanding reflection in django for refactoring - python

I am working on my first Django project and I need to understand the way reflection is used in django.
I have the method category_autocomplete which I use with jQuery to get autocomplete for a category field.
I need autocomplete in some more places but on different things. I think it might be a good idea to make into a class for reuse.
I have started making the class but I am not sure how to proceed.
The problem is the way django uses the filter function. It has a parameter which goes like <param-name>_icontains. I can easily reproduce the lambda by using getattr and passing parameter name as a string but I cannot figure out how to use reflection to get the parameter name for the filter function.
Any idea how this can be done?
class Autocomplete():
#staticmethod
def get_json_autocomplete(self, cur_objects, func):
results = []
for cur_object in cur_objects:
results.append(func(cur_object))
return json.dumps(results)
#staticmethod
def autocomplete(self, request, class_name, attr_name):
term = request.GET.get('term', '')
data = Autocomplete.get_json_autocomplete(
#Problem here
class_name.objects.filter(attr_name=term),
lambda x: getattr(x, attr_name)
)
return HttpResponse(data, 'application/json')
def _get_json_autocomplete(cur_objects, func):
results = []
for cur_object in cur_objects:
results.append(func(cur_object))
return json.dumps(results)
def category_autocomplete(request):
term = request.GET.get('term', '')
data = _get_json_autocomplete(
Category.objects.filter(name__icontains=term),
lambda x: x.name
)
return HttpResponse(data, 'application/json')

What I believe you're looking for is **, take a look here and here.
So that part of your code could be:
def autocomplete(self, request, class_name, attr_name):
term = request.GET.get('term', '')
data = Autocomplete.get_json_autocomplete(
class_name.objects.filter(**{attr_name + '__icontains': term}),
lambda x: getattr(x, attr_name)
)
return HttpResponse(data, 'application/json')

Related

Python - list comprehension as a decorator (including self)

I have two functions:
job_status is getting a response from boto3 api.
jobs_detailsis a list comprehension that performs job_status on each element of the input list.
I want to change jobs_details into a decorator of jobs_status but below solutions throws inner() takes 1 positional argument but 2 were given error.
Appreciate any comment/alternative approach to my issue. Thanks!
import boto3
class GlueClient:
def __init__(self):
self.glue_client = boto3.client('glue')
#self.envs = envs
def jobs_list(self):
response = self.glue_client.list_jobs()
result = response["JobNames"]
while "NextToken" in response:
response = self.glue_client.list_jobs(NextToken=response["NextToken"])
result.extend(response["JobNames"])
return [e for e in result if "jobs_xyz" in e]
#WHAT IS CURRENTLY
def job_status(self, job_name):
paginator = self.glue_client.get_paginator('get_job_runs')
response = paginator.paginate(JobName=job_name)
return response
def jobs_details(self, jobs):
return [self.job_status(e) for e in jobs]
#WHAT IS EXPECTED
def pass_by_list_comprehension(func):
def inner(list_of_val):
return [func(value) for value in list_of_val ]
return inner
#pass_by_list_comprehension
def job_status(self, job_name):
paginator = self.glue_client.get_paginator('get_job_runs')
response = paginator.paginate(JobName=job_name)
return response
glue_client = GlueClient()
jobs = glue_client.jobs_list()
jobs_status = glue_client.job_status(jobs)
print(jobs)
You want something like:
import boto3
from typing import Callable
def handle_iterable_input(func):
def inner(self, list_of_val):
return [func(self, value) for value in list_of_val]
return inner
class GlueClient:
def __init__(self):
self.glue_client = boto3.client('glue')
#self.envs = envs
def jobs_list(self):
response = self.glue_client.list_jobs()
result = response["JobNames"]
while "NextToken" in response:
response = self.glue_client.list_jobs(NextToken=response["NextToken"])
result.extend(response["JobNames"])
return [e for e in result if "jobs_xyz" in e]
#handle_iterable_input
def job_status(self, job_name):
paginator = self.glue_client.get_paginator('get_job_runs')
response = paginator.paginate(JobName=job_name)
return response
glue_client = GlueClient()
jobs = glue_client.jobs_list()
jobs_status = glue_client.job_status(jobs)
print(jobs)
This is the most basic way to make your decorator handle methods properly, by explicitly handling the passing of self. Note, it assumes the function being decorated will only take a single argument.
If all you want to do is make job_status iterate through a list of job names instead of operating on just one, something like this should work:
def jobs_status(self, job_names):
paginator = self.glue_client.get_paginator('get_job_runs')
return [paginator.paginate(JobName=job_name) for job_name in job_names]
Using a decorator to change what parameters a method expects seems like a bad idea.
Also, naming your class GlueClient would imply that it is a glue client. The fact that it has an attribute named glue_client makes me suspect you could probably choose a clearer name for one or both of them. (However, I'm not familiar with the package you're using.)

Object oriented programming with abstract class

I want to achieve the below:
def do_something(request):
company_name = request.get("company_name", DEFAULT_COMPANY)
data = request.get("data")
response = transform_data_according_to(data, company_name)
return response
I did the following for it:
class Transform(ABC):
def __init__(self, data):
self.data = data
#abstractmethod
def transform(self):
pass
class CompanyA(Transform):
def transform(self):
# do_transformation
return transformed_data
def do_something(request):
company_name = request.get("company_name", DEFAULT_COMPANY)
data = request.get("data")
if company_name == CompanyA:
response = CompanyA.transform(data)
return response
Instead i would like to do something like this using correct object oriented principles:
def do_something(request):
company_name = request.get("company_name", DEFAULT_COMPANY)
data = request.get("data")
response = Transform(data, company_name)
return response
I want to know where I might be thinking wrong in terms of the desired approach versus the implemented approach. Is the implemented approach correct, the if else checks can grow quite big in that case.
Thanks to teraflop
The simple, idiomatic way to do this in Python would be to look up the Transform subclass in a dictionary:
transform_classes = {
"CompanyA": CompanyA,
# ...
}
def do_something(request):
company_name = request.get("company_name", DEFAULT_COMPANY)
data = request.get("data")
transformer = transform_classes[company_name](data)
return transformer.transform()
If you prefer to be more rigorously object-oriented, you could wrap the dictionary in an object (e.g. TransformLookupByName) instead of accessing it directly.
There are also various kinds of metaprogramming magic you can use to build the dictionary automatically without having to name each subclass explicitly. For example, this will collect all of the Transform subclasses in the current source file:
transform_classes = {
k:v for k,v in globals().items()
if isinstance(v, type) and issubclass(v, Transform) and v != Transform
}

Python client authentication with decorators

I want to build a python client on top of a REST API that uses authentication with a api_token. Hence all api calls require the api_token. As it is pretty ugly to add a field
'token=...'
e.g.
a = f1(5, token='token')
b = f2(6, 12, token='token')
c = f3(2, 'a', token='token')
where internally f1 and f2 delegate to the REST api
to each function call. What I would like to have is something like:
auth = authenticate('token')
a = f1(5)
b = f2(6, 12,)
c = f3(2, 'a')
What I can do is to create a class and make all functions member functions. Hence, we would have:
auth = calculator('token')
a = auth.f1(5)
b = auth.f2(6, 12,)
c = auth.f3(2, 'a')
but that would also be somewhat ugly. I am trying to get this to work with decorators, but to no avail so far.
class authenticate:
def __init__(self, token):
self.token = token
def __call__(self, func):
def functor(*args, **kwargs):
return func(*args, **kwargs, key=self.authentication)
return functor
#authenticate
def f1(a, key):
data = a
result = requests.get(1, data, key)
return result
However, this seems to be going nowhere. I am also wondering whether this might work at all as decorators are executed at import time and the token is added at runtime.
Any suggestions on how to make this work or anyone know if there is another standard pattern for this?
So after some hacking around we came up with the following:
class authenticate:
# start empty key
key = None
#classmethod
""" add the token """
def set_key(cls, token):
cls.token = token
def __init__(self, func=None):
if func is not None:
self.func = func
else:
print('no function')
def __call__(self, *arg):
"""
add authentication to function func
"""
ret = self.func(*arg, auth_key=self.key)
return ret
#authenticate
def f1(a, key):
data = a
result = requests.get(1, data, key)
return result
Then you can run code like:
authentication_key = 'token'
print('Initiate class')
authenticate().set_key(key=authentication_key)
print('Run f1(5)')
a1 = f1(5) # no token needed!
a2 = f2(6, 12) # again no token needed as it is in the decorator
print(a1)
This works more or less as I hoped and I find it cleaner than the class methods. If anyone has a better suggestion or improvements let me know.

Python: take a web-service method using reflection

Good day!
I trying to go the method of web-service using reflection. Here is an example of code:
...
api = cf.SomeServiceAPI()
#Test1
def test_SomeMethod(self):
result = self.sender('SomeMethod', [setofvalue])
self.assertEqual(result, "Success", msg=result)
def sender(self, methodname, setofvalue):
result = self.api.service.SomeMethod(setofvalue)
return result
Please help me understand how to apply the method using method's name?
Thanks!
looks like this is a duplication of this question.
You should use:
getattr(object, 'method_name')
You can use getattr(clazzA, methodname)(setofvalue) where clazzA is the object, methodname is the name of the method in string and setofvalue is the parameter you want to pass into the method.
Here is an example of your requested behavior:
class A:
def some_method(self, arg):
print ("in: ", arg)
#Test1
def test_Some_method():
result = sender('some_method', "method")
def sender(methodname, setofvalue):
clazzA = A()
result = getattr(clazzA, methodname)(setofvalue)
return result
test_Some_method()
>>>'in: method'
I solved the task.
...
api = cf.SomeServiceAPI()
m1 = api.service.__getattr__('SomeMethod')
#Test1
def test_SomeMethod(self):
result = self.sender(self.m1, [setofvalue])
self.assertEqual(result, "Success", msg=result)
def sender(self, methodname, setofvalue):
result = method(setofvalue)
return result

Format canonical URL structure correctly with URL Processors

EDIT: Could you read my question more carefully? This question is specific and not duplicated as has been said. Obviously I read this question before post.
In this demo code from the docs, the fragment after the lang parameter will be static. So, in English /en/about, and for example, in Portuguese /pt/about. Well, the correct, should be /pt/sobre.
Any idea about the correct way to make that with URL Processors?
from flask import Flask, g
app = Flask(__name__)
#app.url_defaults
def add_language_code(endpoint, values):
if 'lang_code' in values or not g.lang_code:
return
if app.url_map.is_endpoint_expecting(endpoint, 'lang_code'):
values['lang_code'] = g.lang_code
#app.url_value_preprocessor
def pull_lang_code(endpoint, values):
g.lang_code = values.pop('lang_code', None)
#app.route('/<lang_code>/')
def index():
...
#app.route('/<lang_code>/about')
def about():
Ok, you already have language prefix. So you need have several translations for next part.
The easiest way it use several routes or converters:
#app.route('/<lang_code>/about')
#app.route('/<lang_code>/sobre')
def about():
pass
or
#app.route('/<lang_code>/<any(about,sobre):about>')
def about(about):
pass
But it hard to support and add new languages.
Second way is change route method to translate special words or add special translate processor converter, last more interesting and implicit:
from werkzeug.routing import AnyConverter
languages = ['en', 'pt']
def translate_url(word, language):
if language == 'pt' and word == 'about':
return 'sobre'
return word
class TranslateConverter(AnyConverter):
def __init__(self, map, item):
AnyConverter.__init__(self, map, *[translate_url(item, language)
for language in languages])
app.url_map.converters['tr'] = TranslateConverter
#app.route('/<lang_code>/<tr(about):about>')
def about(about):
pass
But this example have next issue:
/en/about
/en/sorbe
/pt/about
/pt/sorbe
is valid urls, but you can also try use own Rule class (Flask.url_rule_class) where in match method you can process this cases:
from werkzeug.routing import AnyConverter, Rule
class TranslateConverter(AnyConverter):
def __init__(self, map, item):
self.language_pairs = {language: translate_url(item, language)
for language in languages}
AnyConverter.__init__(self, map, *tuple(self.language_pairs.values()))
class TranslateCorrelationRule(Rule):
def match(self, path):
result = Rule.match(self, path)
if result is None:
return result
lang_code = result.get('lang_code')
if lang_code is None:
return result
for name, value in self._converters.items():
if not isinstance(value, TranslateConverter):
continue
if value.language_pairs[lang_code] != result[name]:
return
return result
app.url_map.converters['tr'] = TranslateConverter
app.url_rule_class = TranslateCorrelationRule
If you will simplify url_for usage for this examples you can use next sample:
#app.url_value_preprocessor
def pull_lang_code(endpoint, values):
if not values:
return
g.lang_code = values.pop('lang_code', None)
for key, value in values.items():
if key.startswith('_'):
values.pop(key)
class TranslateCorrelationRule(Rule):
def _update_translate_values(self, values):
lang_code = values.get('lang_code', getattr(g, 'lang_code', None))
if lang_code is None:
return values
values = values.copy()
for argument in self.arguments:
if argument in values:
continue
converter = self._converters[argument]
if not isinstance(converter, TranslateConverter):
continue
values[argument] = converter.language_pairs[lang_code]
return values
def suitable_for(self, values, method=None):
return Rule.suitable_for(self, self._update_translate_values(values),
method)
def build(self, values, append_unknown=True):
return Rule.build(self, self._update_translate_values(values),
append_unknown)

Categories