<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: GREVE Malick</title>
    <description>The latest articles on DEV Community by GREVE Malick (@greve_malick_6bf326604dee).</description>
    <link>https://dev.to/greve_malick_6bf326604dee</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4056713%2F97ca96db-c6e6-4cdb-a540-ca0a367cc5e4.jpeg</url>
      <title>DEV Community: GREVE Malick</title>
      <link>https://dev.to/greve_malick_6bf326604dee</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/greve_malick_6bf326604dee"/>
    <language>en</language>
    <item>
      <title>Under the Hood: How My API Abstraction Layer Executes Requests (Django + DRF)</title>
      <dc:creator>GREVE Malick</dc:creator>
      <pubDate>Mon, 03 Aug 2026 19:10:52 +0000</pubDate>
      <link>https://dev.to/greve_malick_6bf326604dee/under-the-hood-how-my-api-abstraction-layer-executes-requests-django-drf-4p9d</link>
      <guid>https://dev.to/greve_malick_6bf326604dee/under-the-hood-how-my-api-abstraction-layer-executes-requests-django-drf-4p9d</guid>
      <description>&lt;p&gt;Before diving into the code, here is a quick overview of how the system works:&lt;/p&gt;

&lt;p&gt;In my abstraction layer, API specifications are registered in the database. At runtime, the core logic queries these details while python services (powered by requests) handle the actual call construction and execution.&lt;/p&gt;

&lt;p&gt;To keep things digestible, this post will focus strictly on two core components:&lt;/p&gt;

&lt;p&gt;API Registration logic&lt;/p&gt;

&lt;p&gt;Dynamic URL construction&lt;/p&gt;

&lt;p&gt;(Note: I'll cover methods, endpoint parameters, headers, and authentication like OAuth 2.0 / API Keys in Part 2!)&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The Core Data Model (API)
To store the configuration for each registered API, I built a dedicated Django model. It acts as the single source of truth for base URLs, authentication requirements, access rights, and quota management.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is what it looks like:&lt;br&gt;
`class API(models.Model):&lt;br&gt;
    name = models.CharField(max_length=255)&lt;br&gt;
    description = models.TextField(blank=True, null=True)&lt;br&gt;
    url = models.URLField(validators=[URLValidator()])&lt;br&gt;
    auth_required = models.BooleanField(default=True)&lt;br&gt;
    created_by = models.ForeignKey(User, on_delete=models.CASCADE, related_name='user_apis', null=True, blank=True)&lt;br&gt;
    api_key_encrypted = models.CharField(blank=True, null=True)&lt;br&gt;
    is_blocked = models.BooleanField(default=False)&lt;br&gt;
    is_active = models.BooleanField(default=True)&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 🆕 NEW FIELD: Quota cost per call
quota_cost = models.PositiveIntegerField(
    default=1,
    help_text="Number of credits consumed per call to this API"
)

def can_be_accessed_by(self, user):
    """Only creator or admin can access an API."""
    if not user or not user.is_authenticated:
        return False
    if user.is_superuser:
        return True
    return self.created_by == user

def __str__(self):
    return f"{self.name} (Coût: {self.quota_cost} crédit{'s' if self.quota_cost &amp;gt; 1 else ''})"`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;URL Construction &amp;amp; Parameter Injection (build_url)
The heart of the execution logic relies on a dynamic URL builder. It takes the base API URL, injects dynamic path parameters (e.g., /users/{user_id}), and encodes query string parameters cleanly using Python's urllib.parse.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`from urllib.parse import quote_plus, urlencode&lt;/p&gt;

&lt;p&gt;def build_url(api_url: str, endpoint_path: str, parameters: list, user_params: dict) -&amp;gt; str:&lt;br&gt;
    # Clean up trailing/leading slashes to avoid double-slash issues (e.g., api.com//v1)&lt;br&gt;
    url = api_url.rstrip("/") + "/" + endpoint_path.lstrip("/")&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# 1. Replace path parameters (e.g., {user_id} -&amp;gt; 42)
for param in parameters:
    if param.param_type == 'path':
        if param.name not in user_params:
            raise ValueError(f"Missing required path parameter: '{param.name}'")
        value = quote_plus(str(user_params[param.name]))
        url = url.replace(f"{{{param.name}}}", value)

# 2. Build and append query parameters
query_params = {
    param.name: user_params.get(param.name, param.default_value)
    for param in parameters
    if param.param_type == 'query' and user_params.get(param.name) is not None
}

if query_params:
    connector = '&amp;amp;' if '?' in url else '?'
    url += connector + urlencode(query_params)

return url`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;ol&gt;
&lt;li&gt;The Execution Entry Point (ExecuteEndpointView)
Once the URL logic is ready, the execution flow is triggered through a dedicated Django REST Framework view. Notice how access control and quota checks are enforced at the permission layer before any execution prep happens:&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;`from rest_framework.views import APIView&lt;br&gt;
from rest_framework.response import Response&lt;br&gt;
from rest_framework import status&lt;br&gt;
from django.shortcuts import get_object_or_404&lt;/p&gt;

&lt;p&gt;class ExecuteEndpointView(APIView):&lt;br&gt;
    """&lt;br&gt;
    POST /api/v1/apis//endpoints//execute/&lt;br&gt;
    """&lt;br&gt;
    authentication_classes = [APIKeyAuthentication]&lt;br&gt;
    permission_classes = [IsAPIKeyAuthenticated, HasSufficientQuota]&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def post(self, request, api_id, endpoint_id):
    # 1. Fetch and validate access to the target API
    api = get_object_or_404(API, pk=api_id, is_active=True, is_blocked=False)
    if not api.can_be_accessed_by(request.user):
        return Response({"detail": "Forbidden."}, status=status.HTTP_403_FORBIDDEN)

    # 2. Build URL &amp;amp; execute request
    # (URL construction -&amp;gt; Headers injection -&amp;gt; HTTP Call via requests)`
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Well, I think I've covered enough for today—I don't want to make this article too long!&lt;/p&gt;

&lt;p&gt;Want to check out the full code? The open-source repository link is in my bio.&lt;/p&gt;

&lt;p&gt;Want to follow the rest of the series? Feel free to hit the follow button so you don't miss Part 2!&lt;/p&gt;

&lt;p&gt;Thanks to everyone for reading, and happy coding!&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>architecture</category>
    </item>
  </channel>
</rss>
