In my previous post, Creating an API with python: Part 12: Database Character Encoding and Connections, I made changes to the database code and schema to enable utf-8 encoding and prevent database connections from dropping. In this post, I will add pagination to the FastAPI API. Pagination is important for the scalability of the API. Without it, the returned datasets would get larger and larger as the database grows, meaning the response times would get slower and slower, and the return data more unwieldy to work with.
Prerequisites
These prerequisites are assumed for this post:
- Creating an API with python: Part 1: GET Endpoints
- Creating an API with python: Part 2: MariaDB Database
- Creating an API with python: Part 3: POST Endpoints
- Creating an API with python: Part 4: DELETE Endpoints
- Creating an API with python: Part 5: Authentication
- Creating an API with python: Part 6: HTTPS and Proxying
- Creating an API with python: Part 7: CORS
- Creating an API with python: Part 8: Multiple Account Support
- Creating an API with python: Part 9: Authentication Scopes
- Creating an API with python: Part 10: Integration Tests
- Creating an API with python: Part 11: Running as a Service
- Creating an API with python: Part 12: Database Character Encoding and Connections
As a reminder, the code for the FastAPI API is now on GitHub, with the project name taglink-api
. You can checkout the latest copy of the code here: https://github.com/liz-allthecoding/taglink-api. Note that all the code changes for this blog entry can be found here: https://github.com/liz-allthecoding/taglink-api/pull/1/files. so I won’t be adding all required code changes here.
Step 1: Update config.yaml
Update config.yaml
with a new config key, api_url
. The value should be the url where your API is hosted, e.g. “https://some.domain/api”.
Step 2: Add a Pagination Decorator
Update manager/manager.py
with a decorator that will wrap get functions and generate a response dict that includes the results and pagination data. The code added will be:
def pagination(endpoint: str): def deco(func): @wraps(func) def inner(*args, **kwargs): results = func(*args, **kwargs) pag_prev = None pag_next = None api_url = CONFIG.get('api_url') count = len(results) offset = kwargs.get('offset', DEFAULT_OFFSET) limit = kwargs.get('limit', DEFAULT_LIMIT) params = deepcopy(kwargs) for key, value in kwargs.items(): if value is None: del params[key] if params.get('offset'): del params['offset'] if offset != 0: prev_offset = offset - limit if prev_offset <= 0: prev_offset = 0 params['offset'] = prev_offset encoded_params = urlencode(params) pag_prev = f'{api_url}/{endpoint}/?{encoded_params}' next_offset = offset + limit if count == limit: params['offset'] = next_offset encoded_params = urlencode(params) pag_next = f'{api_url}/{endpoint}/?{encoded_params}' pagination_dict = {'count': count, 'prev': pag_prev, 'next': pag_next} return {'results': results, 'pagination': pagination_dict} return inner return deco
Step 3: Refactor Manager GETs
Refactor the code in manager/manager.py
to use the pagination decorator. I found it useful to have separate wrapper functions that are decorated with the pagination decorator, which then call the get_xs
function underneath. This is so your database retrievals can be separated from the retrievals that need response data. You will also need to refactor the get_xs
functions to take offset and limit parameters, and to use them in the database query. For example, the new function get_links_with_pagination
is decorated with @pagination
and calls get_links
underneath. The get_links
function now adds the limit and offset to its query. In addition, where functions call other functions, you need to add loops to get all the data, as by default only 200 records (the default limit) will be returned otherwise.
Step 4: Refactor main.py
The get_xs
endpoints need refactoring to add offset and limit params, and pass them to the new get_xs_with_pagination
manager functions. Note that a default offset and limit should be specified in the event that no default or limit are passed to the API.
Step 5: Refactor the Integration Tests
Refactor the integration tests to include tests for pagination, and to take the new response format into account. Note that the response format for get_xs
endpoints will now be of this structure:
{ "results": [], "pagination": {'count': 0, 'prev': "/some/url", 'next': "/some/url"}} }
Note that the get_xs
functions in test_base.py
will need refactoring with offset and limit params, in order to be able to call the API with those params.
Step 6: Run the Integration Tests
Run the integration tests to verify that the API (and the tests) are working correctly.
-
Ensure that the service is running:
$ sudo systemctl start taglink-api
- On your server, change to the code directory (
~/vboxshare/fastapi
should be replaced with the path to your FastAPI python code):$ cd ~/vboxshare/fastapi
-
Run the integration tests:
$ ./integration_test.sh
Conclusion
You should now have a FastAPI API that supports pagination. This means that as your dataset gets larger, your response times won't get slower and slower as your API tries to return more and more data.
Thanks for reading!
Recent Comments