Quickstart

Welcome to Arya’s API Framework!

I am Arya, the creator and maintainer of this project. To get started, this guide will show you how to create a basic API client using {JSON} Placeholder. This is is a very simple set of API endpoints that allows you to test various RESTful API request methods. For this example, the sync branch will be used, but the same process applies to the async branch as well.

A final note before I begin, but this quickstart might be a little longer than most people are familiar with. This is because this library is intended for building other libraries that are API wrappers, not to be used directly on its own.

Full Example

models.py
 1from arya_api_framework import BaseModel, Response
 2
 3# Data models
 4class Geo(BaseModel):
 5    lat: float
 6    lng: float
 7
 8class Address(BaseModel):
 9    street: str
10    suite: str
11    city: str
12    zipcode: str
13    geo: Geo
14
15class Company(BaseModel):
16    name: str
17    catchPhrase: str
18    bs: str
19
20class User(Response):
21    id: int
22    name: str
23    email: str
24    address: Address
25    phone: str
26    website: str
27    company: Company
28
29# Query models
30class AddressQuery(BaseModel):
31    city: Optional[str]
32
33class UserQuery(BaseModel):
34    username: Optional[str]
35    address: Optional[AddressQuery]
api.py
 1from arya_api_framework import SyncClient
 2from pydantic import validate_arguments
 3
 4from models import User, UserQuery, AddressQuery
 5
 6class PlaceholderClient(SyncClient, uri="https://jsonplaceholder.typicode.com"):
 7    def get_users(self):
 8        # https://jsonplaceholder.typicode.com/users
 9        return self.get('/users', response_format=User)
10
11    @validate_arguments()
12    def get_user_by_id(self, id: int):
13        # https://jsonplaceholder.typicode.com/users/<id>
14        return self.get(f'/users/{id}', response_format=User)
15
16    @validate_arguments()
17    def search_user_by_username(self, name: str):
18        # https://jsonplaceholder.typicode.com/users?username=<name>
19        query = UserQuery(username=name)
20
21        return self.get('/users', parameters=query, response_format=User)
22
23    @validate_arguments()
24    def search_user_by_city(self, city: str):
25        # https://jsonplaceholder.typicode.com/users?address.city=<city>
26        query = UserQuery(address=AddressQuery(city=city))
27
28        return self.get('/users', parameters=query, response_format=User)
29
30    @validate_arguments()
31    def search_user_by_username_and_city(self, name: str, city: str):
32        # https://jsonplaceholder.typicode.com/users?username=<name>&address.city=<city>
33        query = UserQuery(username=name, address=AddressQuery(city=city))
34
35        return self.get('/users', parameters=query, response_format=User)
main.py
 1from api import PlaceholderClient
 2
 3if __name__ == "__main__":
 4    client = PlaceholderClient()
 5
 6    users = client.get_users()
 7    print(users)
 8
 9    user = client.get_user_by_id(3)
10    print(user)
11
12    lookup = client.search_user_by_username("Bret")
13    print(lookup)
14
15    lookup = client.search_user_by_city("Gwenborough")
16    print(lookup)
17
18    lookup = client.search_user_by_username_and_city("Bret", "Gwenborough")
19    print(lookup)

Breakdown

Breaking down the above example, this is how the general development process of this example would progress.

Create a Client

First, we have to create the client class. This class is going to be the primary interface between the developer and the API:

api.py
1from arya_api_framework import SyncClient
2
3class PlaceholderClient(SyncClient, uri="https://jsonplaceholder.typicode.com"):
4    pass

To initialize this, the SyncClient.uri subclass parameter will be used. Right away, this is already a usable client for making requests with:

main.py
 1from api import PlaceholderClient
 2
 3if __name__ == "__main__":
 4    client = PlaceholderClient()
 5
 6    users = client.get('/users')
 7    print(users)
 8    # [{"id": 1, ...}, {"id": 2, ...}, ...]
 9
10    user = client.get('/users/1')
11    print(user)
12    # {"id": 1, ...}

This will access https://jsonplaceholder.typicode.com/users and get the JSON data from that endpoint.

Creating API Request Methods

Now that the client exists, we can create wrapper methods to provide a simpler interface for users to query API endpoints easier. At the same, the @validate_arguments() decorator is used in order to enforce Pydantic type validations.

api.py
 1from arya_api_framework import SyncClient
 2from pydantic import validate_arguments
 3
 4class PlaceholderClient(SyncClient, uri="https://jsonplaceholder.typicode.com"):
 5    def get_users(self):
 6        # Get a list of all users.
 7        return self.get('/users')
 8
 9    @validate_arguments()
10    def get_user_by_id(self, user_id: int):
11        # Get a single user by their ID.
12        return self.get(f'/users/{user_id}')
main.py
 1from api import PlaceholderClient
 2
 3if __name__ == "__main__":
 4    client = PlaceholderClient()
 5
 6    users = client.get_users()
 7    print(users)
 8    # [{"id": 1, ...}, {"id": 2, ...}, ...]
 9
10    user = client.get_user_by_id(1)
11    print(user)
12    # {"id": 1, ...}

This can be expanded to other endpoints as well, but currently, data is only retrieved in a JSON dict format.

Creating Data Models

The real strength of this framework comes into play when we start integrating pydantic models. This models allow for direct data validation and returning request responses as an object. To do this, let’s model the user element from the placeholder API. You can see an example of this structure here.

models.py
 1from arya_api_framework import Response
 2
 3class User(Response):
 4    id: int
 5    name: str
 6    username: str
 7    email: str
 8    address: dict
 9    phone: str
10    website: str
11    company: dict

The Response is a direct subclass of the standard pydantic BaseModel. By creating your own custom response format from this model, we gain access to more information about the request, such as the base URI of the request, and the time at which the request was received.

api.py
 1from arya_api_framework import SyncClient
 2from pydantic import validate_arguments
 3
 4from models import User
 5
 6class PlaceholderClient(SyncClient, uri="https://jsonplaceholder.typicode.com"):
 7    def get_users(self):
 8        # Get a list of all users.
 9        return self.get('/users', response_format=User)
10
11    @validate_arguments()
12    def get_user_by_id(self, user_id: int):
13        # Get a single user by their ID.
14        return self.get(f'/users/{user_id}', response_format=User)

By passing the User response model into the response_format parameter, the client will automatically attempt to load the request’s response into the User model.

main.py
 1from api import PlaceholderClient
 2
 3if __name__ == "__main__":
 4    client = PlaceholderClient()
 5
 6    users = client.get_users()
 7    print(users)
 8    # [User(id=1 ...), User(id=2 ...), ...]
 9
10    user = client.get_user_by_id(1)
11    print(user)
12    # User(id=1 ...)

However, in this case, if you try to access the address field of the User model, you will just receive a raw dict in return.

Complete Data Models

If you want full object-oriented representation of your response, you can do so by creating further models and marking the related fields as being of that data type:

models.py
 1from arya_api_framework import Response, BaseModel
 2
 3class Geo(BaseModel):
 4    lat: float
 5    lng: float
 6
 7class Address(BaseModel):
 8    street: str
 9    suite: str
10    city: str
11    zipcode: str
12    geo: Geo
13
14class Company(BaseModel):
15    name: str
16    catchPhrase: str
17    bs: str
18
19class User(Response):
20    id: int
21    name: str
22    email: str
23    address: Address
24    phone: str
25    website: str
26    company: Company

Note here that models which are not going to be direct responses from the api are BaseModel subclasses, while the actual responses are Response subclasses. With this, the same previous example will retrieve complete object-oriented representation for the data responses.

Model Queries

While it is not necessary to do so, you can also use data models as arguments when making a request.

queries.py
 1from typing import Optional
 2
 3from arya_api_framework import BaseModel
 4
 5class AddressQuery(BaseModel):
 6    city: Optional[str]
 7
 8class UserQuery(BaseModel):
 9    username: Optional[str]
10    address: Optional[AddressQuery]

By doing so, you now have the option to search by those fields provided in the query.

Note

As an alternative to creating separate classes specifically for the queries, you could also mark the fields of the standard data models as Optional[<type>] instead, and then use those models for the queries.

api.py
 1from arya_api_framework import SyncClient
 2from pydantic import validate_arguments
 3
 4from models import User
 5from queries import UserQuery, AddressQuery
 6
 7class PlaceholderClient(SyncClient, uri="https://jsonplaceholder.typicode.com"):
 8    def get_users(self):
 9        # Get a list of all users.
10        return self.get('/users', response_format=User)
11
12    @validate_arguments()
13    def get_user_by_id(self, user_id: int):
14        # Get a single user by their ID.
15        return self.get(f'/users/{user_id}', response_format=User)
16
17    @validate_arguments()
18    def search_user_by_username(self, name: str):
19        # Get a list of users with the given username.
20        query = UserQuery(username=name)
21
22        return self.get('/users', parameters=query, response_format=User)
23
24    @validate_arguments()
25    def search_user_by_city(self, city: str):
26        # Get a list of users with an address in the given city.
27        query = UserQuery(
28            address=AddressQuery(
29                city=city
30            )
31        )
32
33        return self.get('/users', parameters=query, response_format=User)
34
35    @validate_arguments()
36    def search_user_by_username_and_city(self, name: str, city: str):
37        # Get a list of users with a given username and an address in the given city.
38        query = UserQuery(
39            username=name,
40            address=AddressQuery(
41                city=city
42            )
43        )
44
45        return self.get('/users', parameters=query, response_format=User)
main.py
 1from api import PlaceholderClient
 2
 3if __name__ == "__main__":
 4    client = PlaceholderClient()
 5
 6    users = client.get_users()
 7    print(users)
 8    # [User(id=1 ...), User(id=2 ...), ...]
 9
10    user = client.get_user_by_id(1)
11    print(user)
12    # User(id=1 ...)
13
14    lookup = client.search_user_by_username("Samantha")
15    print(lookup)
16    # [User(id=3 ...)]
17
18    lookup = client.search_user_by_city("McKenziehaven")
19    print(lookup)
20    # [User(id=3 ...)]
21
22    lookup = client.search_user_by_username_and_city("Bret", "Gwenborough")
23    print(lookup)
24    # [User(id=1 ...)]

Closing Thoughts

From here on out, it’s up to you! This library is left very open-ended for a reason. This is intended to be utilized as a generalized base for crafting API libraries, so the implementation of data validation, individual request methods, rate limits, etc. is all up to you. Keep in mind that a complete documentation of all of the features can be found in the API Reference on the home page.