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¶
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]
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)
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:
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:
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.
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}')
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.
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.
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.
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:
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.
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.
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)
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.