Automatic method delegation in Python - python-3.x

I have a rather contrived code here :
backend_data = {
"admins": ["Leo", "Martin", "Thomas", "Katrin"],
"members": [
"Leo",
"Martin",
"Thomas",
"Katrin",
"Subhayan",
"Clemens",
"Thoralf"
],
"juniors": ["Orianne", "Antonia", "Sarah"]
}
class Backend:
def __init__(self, data):
self.backend_data = data
def get_all_admins(self):
return self.backend_data.get("admins")
def get_all_members(self):
return self.backend_data.get("members")
def get_all_juniors(self):
return self.backend_data.get("juniors")
class BackendAdaptor:
# Does some conversion and validation
def __init__(self, backend):
self.backend = backend
def get_all_admins(self):
return (admin for admin in self.backend.get_all_admins())
def get_all_members(self):
return (member for member in self.backend.get_all_members() if member not in self.backend.get_all_admins())
def get_all_juniors(self):
return (junior for junior in self.backend.get_all_juniors())
if __name__ == "__main__":
backend = Backend(data=backend_data)
adaptor = BackendAdaptor(backend=backend)
print(f"All admins are : {list(adaptor.get_all_admins())}")
print(f"All members are : {list(adaptor.get_all_members())}")
print(f"All juniors are : {list(adaptor.get_all_juniors())}")
So the BackendAdaptor class basically would be used to do some validation and conversion of the data that we get from the Backend .
The client should only be asked to interact with the API of the BackendAdaptor which is exactly similar to that of Backend . The adaptor class sits in middle and gets data from Backend does some validation if required and the gives back the data to client.
The issue is that the validation on the data that is getting returned from the Backend is not done for every method(For ex: there is validation done on get_all_members but not on get_all_admins and also not on get_all_juniors). The method just gives back a generator on whatever data it gets from Backend.
As is the case now i still have to implement a one liner methods for them .
Is there a way in Python to avoid this ? I am thinking in lines of magic methods like __getattribute__ ? But i have no idea on how to do this for methods.
So the best case scenario is this:
I implement the methods for which i know that i have to do some validation on Backend data
For the rest of the methods it is automatically delegated to Backend and then i just return a generator from what i get back
Any help would be greatly appreciated.

You can implement __getattr__. It is only called if a non-existing attribute is accessed. This will return some generic function with the desired functionality.
class BackendAdaptor:
def __init__(self, backend):
self.backend = backend
def __getattr__(self, name):
if not hasattr(self.backend, name):
raise AttributeError(f"'{name}' not in backend.")
return lambda: (i for i in getattr(self.backend, name)())
def get_all_members(self):
return (member for member in self.backend.get_all_members() if member not in self.backend.get_all_admins())

Related

Repeated checking of ID path parameter in FastAPI

I have the following route specs:
GET /councils/{id}
PUT /councils/{id}
DELETE /councils/{id}
In all three routes, I have to check in the database whether the council with the id exists, like this:
council = crud.council.get_by_id(db=db, id=id)
if not council:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Council not found'
)
Which adds to a lot of boilerplate in the code. Is there any way of reducing this? I have thought of creating a dependency but then I have to write different dependency function for different models in my database. Is there any standard practice for this?
Thanks.
Using a dependency is the way to go - it allows you to extract the logic around "get a valid council from the URL"; you can generalize it further if you want to map /<model>/<id> to always retrieving something from a specific model; however - you might want to validate this against a set of possible values to avoid people trying to make you load random Python identifiers in your models class.
from fastapi import Path
def get_council_from_path(council_id: int = Path(...),
db: Session = Depends(get_db),
):
council = crud.council.get_by_id(db=db, id=id)
if not council:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='Council not found'
)
return council
#app.get('/councils/{council_id}')
def get_council(council: Council = Depends(get_council_from_path)):
pass
You can generalize the dependency definition to make it reusable (I don't have anything available to test this right now, but the idea should work):
def get_model_from_path(model, db: Session = Depends(get_db)):
def get_model_from_id(id: int = Path(...)):
obj = model.get_by_id(db=db, id=id)
if not council:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='{type(model)} not found'
)
return get_model_from_id
#app.get('/councils/{id}')
def get_council(council: Council = Depends(get_model_from_path(crud.council)):
pass
Here is one solution that works. FastAPI supports classes as dependencies. Therefore I can have a class like this:
class DbObjIdValidator:
def __init__(self, name: str):
if name not in dir(crud):
raise ValueError('Invalid model name specified')
self.crud = getattr(crud, name)
def __call__(self, db: Session = Depends(get_db), *, id: Any):
db_obj = self.crud.get_by_id(db=db, id=id)
if not db_obj:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail='not found'
)
return db_obj
Considering crud module imports the crud classes for all the ORM models. My example is closely following the fastAPI cookiecutter project by Tiangolo, and the crud design pattern he followed.
Now in order to use it, for example in councils_route.py, I can do the following:
router = APIRouter(prefix='/councils')
council_id_dep = DbObjIdValidator('council') # name must be same as import name
#router.get('/{id}', response_model=schemas.CouncilDB)
def get_council_by_id(
id: UUID,
db: Session = Depends(get_db),
council: Councils = Depends(council_id_dep)
):
return council

Using hydra.main on main method

Summary of the problem:
I'm running a requests call on an API endpoint, whose request params are hidden in a config file and I decided to try out hydra to retrieve those params [Reason being the request params do change as I'm working on collecting custom dataset using RapidAPI]
I have created a class called QueryParamsLocations which implements the getter methods to fetch the parameters to be later used by run_query method.
class QueryParamsLocations(QueryParams):
#hydra.main(config_path='configs', config_name='location_query')
def get_params_query_string(self, cfg: DictConfig) -> dict():
return {
'query': cfg.location_params.query,
'locale': cfg.location_params.locale,
'currency': cfg.location_params.currency
}
#hydra.main(config_path='configs', config_name='location_query')
def get_url(self, cfg: DictConfig) -> str():
return cfg.urls.location_url
#hydra.main(config_path='configs', config_name='location_query')
def get_headers(self, cfg: DictConfig) -> dict():
return {
'X-RapidAPI-Host': cfg.headers.x_rapidapi_host,
'X-RapidAPI-Key': cfg.headers.x_rapidapi_key
}
class QueryParams is an abstract class which has these 3 getter templates. run_query method is an external call to run the request.
#hydra.main(config_path='configs', config_name='location_query')
def run_query(cfg: DictConfig) -> None:
try:
LoggerFactory.get_logger('logs/logger.log', 'INFO').info('Running query for location')
qpl = QueryParamsLocations()
response = requests.request("GET", qpl.get_url(cfg), headers=qpl.get_headers(cfg), params=qpl.get_params_query_string(cfg))
print(response.json())
except Exception as e:
LoggerFactory.get_logger('logs/logger.log',
'ERROR').error(f'Error in running query: {e}')
run_query()
While running run_query without if name == 'main': and with it as well , the following error is encountered :
[2022-05-16 13:43:32,614][logs/logger.log][ERROR] - Error in running query: **decorated_main()** takes from 0 to 1 positional arguments but 2 were given
Although newer version of hydra (I'm using hydra-core==1.1.2) uses two arguments while creating cfg object however , I'm not sure as to whether there's other way of handling this as such.
Also, by searching through other threads, following was also tried - Compose API
however, from the docs, it requires an override parameter , which is not needed atm.
Would like to know if any other approach can be tried out. Happy to provide more details if needed.
Definitely use the Compose API and not hydra.main() for this use case.
You can just pass an empty array for your override list if you have nothing to override.

Validating django rest api get request using traditional forms class

I am trying to validate a DRF get request using django form as follows,
The view of django rest api
#csrf_exempt
#api_view(['GET', 'POST'])
def pkg_list(request):
if request.method == 'GET':
frm=ThisForm(request.GET)
if frm.is_valid:
print("form ok")
print(frm.cleaned_data)
else:
print("invalid")
mydata=[{"email": request.GET['reseller']}]
results=ResellerListPackages(mydata,many=True).data
return Response(results)
The view class is form is as follows,
class ThisForm(forms.Form):
reseller=forms.EmailField(max_length=255)
def clean(self):
self.cleaned_data = super().clean()
print(self.cleaned_data)
return self.cleaned_data
The form validation seems working fine , but the frm.cleaned_data is not found with the following error,
print(frm.cleaned_data)
AttributeError: 'ThisForm' object has no attribute 'cleaned_data'
Can some one point to me the correct direction. It is the first time using the DRF
Change
frm.is_valid
to
frm.is_valid()
Forms only get a cleaned_data attribute when is_valid() has been called, and you haven't called it on this new, second instance.
but in your case, you are not calling the is_valid() method.

DRF permissions best practise DRY

Whats the best way to do view permissions in DRF based on user type currently?
In my structure there are several user_types and for example TEAM_LEADER cant create a team object but can see the list of teams. Which means for the same class view i want to use different permissions for POST and GET for example.
I'm looking to do this as dry as possible and i'm trying to follow the skinny view fat models design principle(also wondering if that's good practice to follow in 2021).
models.py for the user model
class User(AbstractBaseUser):
...fields here
objects = UserManager()
USERNAME_FIELD = "email"
def __str__(self):
return self.email
def has_perm(self, perm, obj=None):
if perm.Meta.verbose_name=="worksite" and perm.request.method =="POST":
if self.user_type <= self.DEPARTMENT_MANAGER:
return True
else:
return False
return True
views.py
class DashboardPermissions(BasePermission):
message="You dont have permission for this action"
def has_permission(self, request, view):
return request.user.has_perm(view.Meta.verbose_name)
class ViewName(CreateAPIView):
permission_classes = (IsAuthenticated,DashboardPermissions)
authentication_classes = ()
serializer_class = WorksiteSerializer
queryset = Worksite.objects.all()
class Meta:
verbose_name="view_name"
def get(self, request, *args, **kwargs):
return self.list(request, *args, **kwargs)
def post(self, request, *args, **kwargs):
return self.create(request, *args, **kwargs)
Bonus question would my solution create any performance issues?
Creating your custom Permission class is good practice. So that part looks OK to me. We could debate on whether the logic should be in the Permission or the User (like you did), but that's not a big deal.
If you want to have different permissions for different endpoints within your view, simply override the get_permissions method.
# Inherited method from APIView
def get_permissions(self):
"""
Instantiates and returns the list of permissions that this view requires.
"""
return [permission() for permission in self.permission_classes]
As you can see, for ALL services, it will fetch the permissions from self.permission_classes.
To use different permissions between GET/CREATE, you could create a dict of endpoint: [...permissions] and override get_permissions to fetch the one matching the current action
permissions = {
"create": [P1, P2,],
"get": [P1,]
}
def get_permissions(self):
action = self.it_is_somewhere_in_there
return [permission() for permissions in self.permissions[action]]
#JordanKowal's answer is correct, but as mentioned in the comments,
Also then i'd be repeating the permissions dict a lot ? in order to do it for every class of view right
For this you can create a mixin class. What it essentially allows you to do is move some code/feature that is to duplicated in multiple views to a standalone class and just inherit from it as per your convenience.
To extend on Jordan's answer, here's what a mixin class would look like:
class DefaultPermissionsMixin(object):
permissions = {
"create": [IsAuthenticated, DashboardPermissions],
"get": [DashboardPermissions]
}
def get_permissions(self):
# default `get_permissions` method
# reads `self.permission_classes`
perms = super().get_permissions()
if self.action in self.permissions.keys():
return perms + [p() for p in self.permissions[self.action]]
else:
return perms
class View1(CreateAPIView, DefaultPermissionsMixin):
# ...snip...
class View2(CreateAPIView, DefaultPermissionsMixin):
# i can overwrite here per my convenience
permissions = {
"create": [DashboardPermissions],
"delete": [],
}
# i can also define permissions the default way
# that will be enabled on all actions
permission_classes = [IsAuthenticated]
# ...snip...

Call Method of another class in Flask API

I am trying to expose a data service as an API for a PHP application. I have written the API logic in a file called APILogic.py. The code in this looks like this
class APILogic(object):
def __init__(self):
# the initialization is done here
...
def APIfunction(self):
# the API logic is built here, it uses the class variables and methods
...
I have created another file for the API purpose. Its called API.py. The code in this file looks like this
import APILogic from APILogic
class MyFlask:
def __init__(self):
self.test = APILogic()
from flask import Flask
app = Flask(__name__)
my_flask = MyFlask()
#app.route("/Test")
def Test():
return my_flask.test.APIfunction
if __name__ == "__main__":
app.run(debug=True,port=9999)
When I run the code, I get the error
> TypeError: APIfunction() takes 1 positional argument but 3 were given
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
There are no arguments for the APIfunction though.
Please help.
The view function did not return a valid response. The return type must be a string, dict, tuple, Response instance, or WSGI callable, but it was a method.
Looks like you're returning the method, but it sounds like you want to return the result of that method:
#app.route("/Test")
def Test():
return my_flask.test.APIfunction()
View function should return valid response.
Sample API code
from flask import Flask
app = Flask(__name__)
#app.route("/")
def hello():
return "Hello World!"
if __name__ == "__main__":
app.run()

Resources