A python library for interacting with any Django web server based on django-rest-framework
Package is based on https://github.com/samgiles/slumber, but enhanced to support tokens and other features.
-
Support for tokens. Both
- django-rest-framework's own tokens:
rest_framework.authentication.TokenAuthentication - JWT tokens:
rest_framework_jwt.authentication.JSONWebTokenAuthentication
- django-rest-framework's own tokens:
-
Support for query arguments (e.g.
?name1=val1&name2=val2) -
Support for custom methods (e.g. ``/api/v1/object/custom/`)
django-rest-framework-client requires:
- Python 3.10+
- requests
- httpx
- respx
# Using pip
python -m pip install django-rest-framework-client
# Using uv
uv add django-rest-framework-clientExample
import pprint
from drf_client.connection import Api as RestApi
options = {
'DOMAIN': 'http://127.0.0.1:8000',
'API_PREFIX': 'api/v1',
'TOKEN_TYPE': 'jwt',
'TOKEN_FORMAT': 'JWT {token}',
'USERNAME_KEY': 'username',
'LOGIN': 'auth/login/',
'LOGOUT': 'auth/logout/',
'USE_DASHES': False, # Set to True to tell API to replace underscore ("_") with dashes ("-")
'SESSION_TRIES': 3, # Enable retry
'SESSION_TIMEOUT': None, # No timeout
'SESSION_VERIFY': False, # Do not verify SSL
}
c = RestApi(options)
ok = c.login(username="username", password="password")
if ok:
# GET some data
my_object = c.myresourcename.get()
for obj in my_object['results']:
pprint.pprint(obj)
payload = {
'data1': 'val1',
'data2': 'val2',
}
resp = c.myresourcename.post(data=payload)
# If the URL includes "-", add under parenthesis:
# GET: /api/v1/someresource/some-path/
my_object = c.someresource('some-path').get()from drf_client.helpers.base_main import BaseMain
class MyClass(BaseMain):
options = {
'DOMAIN': None,
'API_PREFIX': 'api/v1',
'TOKEN_TYPE': 'bearer',
'TOKEN_FORMAT': 'Bearer {token}',
'USERNAME_KEY': 'username',
'LOGIN': 'auth/login/',
'LOGOUT': 'auth/logout/',
'USE_DASHES': False,
"SESSION_TRIES": 3,
'SESSION_TIMEOUT': None,
'SESSION_VERIFY': False,
}DRF_CLIENT_AUTH_TOKEN=1fe171f65917db0072abc6880196989dd2a20025 \
python -m my_script.MyClass --server https://mysite.com --use-token tClient assumes by default that all urls should end with a slash (tested with the default
router: routers.DefaultRouter())
Apart from the regular Django and Rest Framework setup, this package currently relies on the following custom login and logout API functions:
class AccountSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('id', 'email', 'username')
class APILogoutViewSet(APIView):
permission_classes = (permissions.IsAuthenticated,)
def post(self, request, format=None):
logout(request)
return Response({}, status=status.HTTP_204_NO_CONTENT)
urlpatterns = [
url(r'^auth/logout/$', APILogoutViewSet.as_view(), name='api-logout'),
]This class helps write a script with a flexible template that helps avoid having to duplicate boilerplate code from script to script.
The class assumes that most scripts include the following basic flow:
- Parse arguments
- Setup LOG configuration
- Login
- Do something after logging in
The opinionated class will execute the basic main flow:
# Initialize arguments and LOG in the init function
# Add additional arguments by implementing self.add_extra_args()
self.domain = self.get_domain()
self.api = Api(self.domain)
self.before_login()
ok = self.login()
if ok:
self.after_login()Any of the above functions can be overwritten by deriving from this class.
Here is a sample script:
from drf_client.helpers.base_main import BaseMain
from drf_client.helpers.base_facade import BaseFacade
class MyScript(BaseMain):
def add_extra_args(self):
# Add extra positional argument (as example)
self.parser.add_argument('foo', metavar='foo', type=str, help='RTFM')
def before_login(self):
logger.info('-----------')
def after_login(self):
# Main function to OVERWRITE and do real work
resp = self.api.foo.bar.get()
# You can also access the API from the global Facade
resp = BaseFacade.api.foo.bar.get()
if __name__ == '__main__':
work = MyScript()
work.main()If you wish to implement coroutines to run multiple tasks in parallel, you can use the asyncio library.
import asyncio
from drf_client.helpers.base_main import BaseMain
from drf_client.helpers.base_facade import BaseFacade
class MyScript(BaseMain):
def add_extra_args(self):
# Add extra positional argument (as example)
self.parser.add_argument('foo', metavar='foo', type=str, help='RTFM')
def before_login(self):
logger.info('-----------')
async def process(self):
"""Main async test"""
# foo_bar and foo_baz are coroutines
foo_bar = await self.api.foo.bar.async_get()
foo_baz = await self.api.foo.baz.async_get()
def after_login(self):
# Main function to OVERWRITE and do real work
resp = asyncio.run(self.process())
if __name__ == '__main__':
work = MyScript()
work.main()Given the above script, you will run it with
python myscript.py -u <USERNAME> --foo bardjango-rest-framework-client uses uv for dependency management and testing.
Make sure to install uv and run uv sync to install dependencies.
To test, run coverage analysis, and lint:
uv sync
uv run pytest
uv run ruff check
uv run ty check
# Install pre-commit hooks
pre-commit install
pre-commit install-hooks- Update
pyproject.tomlwith new version - Update
CHANGELOG.mdwith description of new version - Create a new tag with same version
git tag v0.4.1 -m "v0.4.1" git push --tags - Create new release using GitHub Web Site. Github action will run automatically to deploy to PyPi.