Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-django-rest-framework-client for openSUSE:Factory checked in at 2024-02-23 16:45:47 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-django-rest-framework-client (Old) and /work/SRC/openSUSE:Factory/.python-django-rest-framework-client.new.1770 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-django-rest-framework-client" Fri Feb 23 16:45:47 2024 rev:5 rq:1149659 version:0.10.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-django-rest-framework-client/python-django-rest-framework-client.changes 2022-05-12 23:01:46.316910518 +0200 +++ /work/SRC/openSUSE:Factory/.python-django-rest-framework-client.new.1770/python-django-rest-framework-client.changes 2024-02-23 16:46:57.342874916 +0100 @@ -1,0 +2,30 @@ +Thu Feb 22 23:17:39 UTC 2024 - Steve Kowalik <steven.kowa...@suse.com> + +- Update to 0.10.0: + * Add support to use a permanent Token. + * Fix bug using BaseFacade + * Fix bug using old Facade instead of BaseFacade + * Add isort and Black as formatter + * Add static BaseFacade class to allow access to API class and BaseMain + options + * Remove support for Python 3.8. + * Add set of `raw_*` methods that do not process results. + * Migrated to Python 3.10, Python 2 is not supported anymore + * Resource class methods respect additional `**kwargs` and `extra_headers` + parameters and pass them on to the underlying `requests` methods + * Fix to support `http://` schema in the server url + * Add USE_DASHES option to automatically replace underscores ("_") with + dashes ("-") + * Allow `delete()` method to accept optional `payload` + * Fix BaseMain Login method + * Add base_main helper + * Add method to be able to support resource names with "-" in the name + * Support Login based on usernames or email keys + * Drop support for Python 2. Test on v3.8 and v3.9 + * Remove dependency on unitest2 +- Switch to autosetup and pyproject macros. +- Drop patch python-django-rest-framework-client-no-unittest2.patch, no longer + required. +- Refresh python-django-rest-framework-client-no-mock.patch + +------------------------------------------------------------------- Old: ---- django-rest-framework-client-0.1.1.tar.gz python-django-rest-framework-client-no-unittest2.patch New: ---- django-rest-framework-client-0.10.0.tar.gz BETA DEBUG BEGIN: Old:- Switch to autosetup and pyproject macros. - Drop patch python-django-rest-framework-client-no-unittest2.patch, no longer required. BETA DEBUG END: ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-django-rest-framework-client.spec ++++++ --- /var/tmp/diff_new_pack.Qi76kk/_old 2024-02-23 16:46:57.942896674 +0100 +++ /var/tmp/diff_new_pack.Qi76kk/_new 2024-02-23 16:46:57.946896819 +0100 @@ -1,7 +1,7 @@ # # spec file for package python-django-rest-framework-client # -# Copyright (c) 2022 SUSE LLC +# Copyright (c) 2024 SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed @@ -16,54 +16,43 @@ # -%{?!python_module:%define python_module() python-%{**} python3-%{**}} -%bcond_without python2 -%define skip_python36 1 +%define skip_python39 1 Name: python-django-rest-framework-client -Version: 0.1.1 +Version: 0.10.0 Release: 0 Summary: Python client for a Django REST Framework based web site License: MIT -Group: Development/Languages/Python URL: https://github.com/dkarchmer/django-rest-framework-client -# newer versions exist on pypi, but without test packaged, see -# https://github.com/dkarchmer/django-rest-framework-client/issues/7 Source: https://github.com/dkarchmer/django-rest-framework-client/archive/v%{version}.tar.gz#/django-rest-framework-client-%{version}.tar.gz -# fake dependency, https://github.com/dkarchmer/django-rest-framework-client/pull/2 -Patch0: python-django-rest-framework-client-no-unittest2.patch -# https://github.com/dkarchmer/django-rest-framework-client/issues/7 -Patch1: python-django-rest-framework-client-no-mock.patch +Patch0: python-django-rest-framework-client-no-mock.patch +BuildRequires: %{python_module base >= 3.10} +BuildRequires: %{python_module pip} BuildRequires: %{python_module setuptools} +BuildRequires: %{python_module wheel} BuildRequires: python-rpm-macros # SECTION test requirements BuildRequires: %{python_module Django} BuildRequires: %{python_module pytest} BuildRequires: %{python_module requests-mock} BuildRequires: %{python_module requests} -%if %{with python2} -BuildRequires: python-unittest2 -%endif # /SECTION BuildRequires: fdupes Requires: python-Django Requires: python-requests BuildArch: noarch - %python_subpackages %description Python client for a Django REST Framework based web site. %prep -%setup -q -n django-rest-framework-client-%{version} -%patch0 -p1 -%patch1 -p1 +%autosetup -p1 -n django-rest-framework-client-%{version} %build -%python_build +%pyproject_wheel %install -%python_install +%pyproject_install %python_expand %fdupes %{buildroot}%{$python_sitelib} %check @@ -72,5 +61,6 @@ %files %{python_files} %doc CHANGELOG.md README.md %license LICENSE -%{python_sitelib}/* +%{python_sitelib}/drf_client +%{python_sitelib}/django_rest_framework_client-%{version}.dist-info ++++++ django-rest-framework-client-0.1.1.tar.gz -> django-rest-framework-client-0.10.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/.github/workflows/python-publish.yml new/django-rest-framework-client-0.10.0/.github/workflows/python-publish.yml --- old/django-rest-framework-client-0.1.1/.github/workflows/python-publish.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/django-rest-framework-client-0.10.0/.github/workflows/python-publish.yml 2023-10-05 03:44:46.000000000 +0200 @@ -0,0 +1,31 @@ +# This workflows will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [created] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine + - name: Build and publish + env: + TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/.travis.yml new/django-rest-framework-client-0.10.0/.travis.yml --- old/django-rest-framework-client-0.1.1/.travis.yml 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/.travis.yml 2023-10-05 03:44:46.000000000 +0200 @@ -1,9 +1,8 @@ language: python sudo: false python: -- '2.7' -- '3.4' -- '3.6' +- '3.9' +- '3.10' install: - pip install --upgrade tox-travis -r requirements-build.txt -r requirements-test.txt script: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/CHANGELOG.md new/django-rest-framework-client-0.10.0/CHANGELOG.md --- old/django-rest-framework-client-0.1.1/CHANGELOG.md 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/CHANGELOG.md 2023-10-05 03:44:46.000000000 +0200 @@ -1,3 +1,60 @@ +### v0.10.0 (2023-10-04) + + * Add support to use a permanent Token. + +### v0.9.2 (2023-08-25) + + * Fix bug using BaseFacade + +### v0.9.1 (2023-08-20) + + * Fix bug using old Facade instead of BaseFacade + +### v0.9.0 (2023-08-20) + + * Add isort and Black as formatter + * Add static BaseFacade class to allow access to API class and BaseMain options + +### v0.8.0 (2023-07-04) + + * Remove support for Python 3.8. + * Add set of `raw_*` methods that do not process results. + +### v0.7.0 (2023-05-08) + + * Migrated to Python 3.10, Python 2 is not supported anymore + * Resource class methods respect additional `**kwargs` and `extra_headers` parameters and pass them on to the underlying `requests` methods + * Fix to support `http://` schema in the server url + +### v0.6.0 (2022-10-30) + + * Add USE_DASHES option to automatically replace underscores ("_") with dashes ("-") + * Refactor to pass options to Resource class + +### v0.5.0 (2022-05-16) + + * Allow `delete()` method to accept optional `payload` + +### v0.4.1 (2022-03-13) + + * Fix BaseMain Login method + * Fix PYPI error + +### v0.3.0 (2022-03-13) + + * Add missing PYPI long description + * Add base_main helper + +### v0.2.0 (2022-03-09) + + * Add method to be able to support resource names with "-" in the name + * Support Login based on usernames or email keys + * Drop support for Python 2. Test on v3.8 and v3.9 + +### v0.1.2 (2020-06-01) + + * Remove dependency on unitest2 + ### v0.1.0 (2017-05-06) - * First release \ No newline at end of file + * First release diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/README.md new/django-rest-framework-client-0.10.0/README.md --- old/django-rest-framework-client-0.1.1/README.md 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/README.md 2023-10-05 03:44:46.000000000 +0200 @@ -1,7 +1,6 @@ -# Strato Python API Package +# Django Rest Framework Python API Package -[](https://travis-ci.org/dkarchmer/django-rest-framework-client) -[](https://pypi.python.org/pypi/django-rest-framework-client) +[](https://pypi.python.org/pypi/django-rest-framework-client) A python library for interacting with any Django web server base on django-rest-framework @@ -21,12 +20,14 @@ restframeworkclient requires the following modules. - * Python 2.7+ or 3.4+ + * Python 3.9+ * requests ## Installation -``` +```bash +python3 -m venv ~/.virtualenv/drf_client +source ~/.virtualenv/drf_client/bin/activate pip install django-rest-framework-client ``` @@ -42,13 +43,15 @@ '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 undercore ("_") with dashes ("-") } c = RestApi(options) -ok = c.login(email=email, password=password) +ok = c.login(username=username, password=password) if ok: # GET some data @@ -64,8 +67,33 @@ 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() + +``` + +### Example using Tokens + ``` +from drf_client.helpers.base_main import BaseMain +class MyClass(Main): + + 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, + } + +export DRF_CLIENT_AUTH_TOKEN=1fe171f65917db0072abc6880196989dd2a20025 +python -m my_script.MyClass --server https://mysite.com --use-token t +``` ## Django Setup @@ -95,17 +123,104 @@ ``` -## Development +## Helpers -To test, run python setup.py test or to run coverage analysis: +### BaseMain Helper + +This class helps write a script with a flexible template that helps avoid having to duplicate +boiler plate code from script to script. + +The class assumes that most scripts include the basic folliwing flow: ``` -coverage run --source=iotile_cloud setup.py test -coverage report -m +# Parse arguments +# Setup LOG configuration +# Login +# Do something after logging in ``` -You can also use py.test: +The opinionated class will execute the basic main flow: +```python + # Initialize arguments and LOG in the init function + # Add additional arguments by implemenenting 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 derving from this class. + +Here is a sample script: + +```python +from drf_client.helper.base_main import BaseMain +from drf_client.helper.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 OVERWITE 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() +``` + +Given the above script, you will run it with + +```bash +python myscript.py -u <USERNAME> --foo bar +``` + +## Development + +To test, run python setup.py test or to run coverage analysis: + +```bash +python3 -m venv .virtualenv/drf_client +source .virtualenv/drf_client/bin/activate +pip install -r requirements-test.txt +pip install -e . + py.test ``` + +## CI Deployment + +1. Update `setup.py` with new version +2. Update `CHANGELOG.md` with description of new version +2. Create new tag with same version + +``` +git tag v0.4.1 -m "v0.4.1" +git push --tags +``` + +3. Create new release using GitHub Web Site. Github action will run automatically to deploy to PyPi. + +## Manual Deployment + +```bash +pip install -r requirements-build.txt + +python setup.py sdist bdist_wheel +twine check dist/* +# Publish +twine upload dist/* +``` diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/drf_client/connection.py new/django-rest-framework-client-0.10.0/drf_client/connection.py --- old/django-rest-framework-client-0.1.1/drf_client/connection.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/drf_client/connection.py 2023-10-05 03:44:46.000000000 +0200 @@ -1,5 +1,3 @@ -__author__ = 'dkarchmer' - """ See https://gist.github.com/dkarchmer/d85e55f9ed5450ba58cb This API generically supports DjangoRestFramework based APIs @@ -15,6 +13,7 @@ 'TOKEN_FORMAT': 'JWT {token}', 'LOGIN': 'auth/api-jwt-auth/', 'LOGOUT': 'auth/logout/', + 'USE_DASHES': False, } api = RestApi(options) @@ -25,32 +24,48 @@ api.logout() """ import json -import requests import logging -import os + +import requests + from .exceptions import * -API_PREFIX = 'api/v1' -DEFAULT_HEADERS = {'Content-Type': 'application/json'} -DEFAULT_TOKEN_TYPE = 'jwt' -DEFAULT_TOKEN_FORMAT = 'JWT {token}' +API_PREFIX = "api/v1" +DEFAULT_HEADERS = {"Content-Type": "application/json"} +DEFAULT_TOKEN_TYPE = "jwt" +DEFAULT_TOKEN_FORMAT = "JWT {token}" +DEFAULT_OPTIONS = { + "DOMAIN": "http://example.com", + "API_PREFIX": "api/v1", + "TOKEN_TYPE": "jwt", + "TOKEN_FORMAT": "JWT {token}", + "LOGIN": "auth/login/", + "LOGOUT": "auth/logout/", + "USE_DASHES": False, +} logger = logging.getLogger(__name__) -class RestResource(object): +class RestResource: """ Resource provides the main functionality behind a Django Rest Framework based API. It handles the attribute -> url, kwarg -> query param, and other related behind the scenes python to HTTP transformations. It's goal is to represent a single resource which may or may not have children. """ + _store = {} + _options = {} def __init__(self, *args, **kwargs): self._store = kwargs - if 'use_token' not in self._store: - self._store['use_token'] = False + if "options" in self._store: + self._options = self._store["options"] + else: + self.options = DEFAULT_OPTIONS + if "use_token" not in self._store: + self._store["use_token"] = False def __call__(self, id=None): """ @@ -61,20 +76,20 @@ """ kwargs = { - 'token': self._store['token'], - 'use_token': self._store['use_token'], - 'token_format': self._store['token_format'], - 'base_url': self._store['base_url'] + "token": self._store["token"], + "use_token": self._store["use_token"], + "base_url": self._store["base_url"], + "options": self._options, } - new_url = self._store['base_url'] + new_url = self._store["base_url"] if id is not None: - new_url = '{0}{1}/'.format(new_url, id) + new_url = f"{new_url}{id}/" - if not new_url.endswith('/'): - new_url += '/' + if not new_url.endswith("/"): + new_url += "/" - kwargs['base_url'] = new_url + kwargs["base_url"] = new_url return self._get_resource(**kwargs) @@ -84,7 +99,9 @@ raise AttributeError(item) kwargs = self._copy_kwargs(self._store) - kwargs.update({'base_url': '{0}{1}/'.format(self._store["base_url"], item)}) + if self._options.get("USE_DASHES", False): + item = item.replace("_", "-") + kwargs.update({"base_url": "{0}{1}/".format(self._store["base_url"], item)}) return self._get_resource(**kwargs) @@ -105,12 +122,21 @@ return d.items() def _check_for_errors(self, resp, url): - if 400 <= resp.status_code <= 499: - exception_class = HttpNotFoundError if resp.status_code == 404 else HttpClientError - raise exception_class("Client Error %s: %s" % (resp.status_code, url), response=resp, content=resp.content) + exception_class = ( + HttpNotFoundError if resp.status_code == 404 else HttpClientError + ) + raise exception_class( + "Client Error %s: %s" % (resp.status_code, url), + response=resp, + content=resp.content, + ) elif 500 <= resp.status_code <= 599: - raise HttpServerError("Server Error %s: %s" % (resp.status_code, url), response=resp, content=resp.content) + raise HttpServerError( + "Server Error %s: %s" % (resp.status_code, url), + response=resp, + content=resp.content, + ) def _handle_redirect(self, resp, **kwargs): # @@@ Hacky, see description in __call__ @@ -133,7 +159,6 @@ return resp.content def _process_response(self, resp): - self._check_for_errors(resp, self.url()) if 200 <= resp.status_code <= 299: @@ -144,55 +169,101 @@ def url(self, args=None): url = self._store["base_url"] if args: - url += '?{0}'.format(args) + url += "?{0}".format(args) return url - def _get_header(self): + def _get_headers(self): headers = DEFAULT_HEADERS - if self._store['use_token']: + if self._store["use_token"]: if not "token" in self._store: - raise RestBaseException('No Token') - authorization_str = self._store['token_format'].format(token=self._store["token"]) - headers['Authorization'] = authorization_str + raise RestBaseException("No Token") + authorization_str = self._options["TOKEN_FORMAT"].format( + token=self._store["token"] + ) + headers["Authorization"] = authorization_str return headers - def get(self, **kwargs): + def raw_get(self, extra_headers: dict = None, **kwargs): + """Call get and return raw request respond.""" args = None - if 'extra' in kwargs: - args = kwargs['extra'] - resp = requests.get(self.url(args), headers=self._get_header()) + if "extra" in kwargs: + args = kwargs["extra"] + headers = ( + self._get_headers() | extra_headers + if extra_headers + else self._get_headers() + ) + + return requests.get(self.url(args), headers=headers) + + def get(self, extra_headers: dict = None, **kwargs): + """Call get and process respond.""" + resp = self.raw_get(extra_headers, **kwargs) return self._process_response(resp) - def post(self, data=None, **kwargs): - if data: - payload = json.dumps(data) - else: - payload = None - - resp = requests.post(self.url(), data=payload, headers=self._get_header()) + def raw_post(self, data: dict = None, extra_headers: dict = None, **kwargs): + """Call requests post and return raw respond.""" + payload = json.dumps(data) if data and "files" not in kwargs else data + headers = ( + self._get_headers() | extra_headers + if extra_headers + else self._get_headers() + ) + + return requests.post(self.url(), data=payload, headers=headers, **kwargs) + + def post(self, data: dict = None, extra_headers: dict = None, **kwargs): + """Call post and process respond.""" + resp = self.raw_post(data, extra_headers, **kwargs) return self._process_response(resp) - def patch(self, data=None, **kwargs): - if data: - payload = json.dumps(data) - else: - payload = None - - resp = requests.patch(self.url(), data=payload, headers=self._get_header()) + def raw_patch(self, data=None, extra_headers: dict = None, **kwargs): + """Call patch and return raw request respond.""" + payload = json.dumps(data) if data and "files" not in kwargs else data + headers = ( + self._get_headers() | extra_headers + if extra_headers + else self._get_headers() + ) + + return requests.patch(self.url(), data=payload, headers=headers, **kwargs) + + def patch(self, data=None, extra_headers: dict = None, **kwargs): + """Call patch and process respond.""" + resp = self.raw_patch(data, extra_headers, **kwargs) return self._process_response(resp) - def put(self, data=None, **kwargs): - if data: - payload = json.dumps(data) - else: - payload = None - - resp = requests.put(self.url(), data=payload, headers=self._get_header()) + def raw_put(self, data=None, extra_headers: dict = None, **kwargs): + """Call Put and return raw request respond.""" + payload = json.dumps(data) if data and "files" not in kwargs else data + headers = ( + self._get_headers() | extra_headers + if extra_headers + else self._get_headers() + ) + + return requests.put(self.url(), data=payload, headers=headers, **kwargs) + + def put(self, data=None, extra_headers: dict = None, **kwargs): + """Call Put and process respond.""" + resp = self.raw_put(data, extra_headers, **kwargs) return self._process_response(resp) - def delete(self, **kwargs): - resp = requests.delete(self.url(), headers=self._get_header()) + def raw_delete(self, data=None, extra_headers: dict = None, **kwargs): + """Call Delete and return raw request respond.""" + payload = json.dumps(data) if data and "files" not in kwargs else data + headers = ( + self._get_headers() | extra_headers + if extra_headers + else self._get_headers() + ) + + return requests.delete(self.url(), data=payload, headers=headers, **kwargs) + + def delete(self, data=None, extra_headers: dict = None, **kwargs): + """Call Delete and process respond. Return True if ok""" + resp = self.raw_delete(data, extra_headers, **kwargs) if 200 <= resp.status_code <= 299: if resp.status_code == 204: return True @@ -204,63 +275,71 @@ def _get_resource(self, **kwargs): return self.__class__(**kwargs) -class Api(object): + +class Api: token = None - token_type = DEFAULT_TOKEN_TYPE - token_format = DEFAULT_TOKEN_FORMAT resource_class = RestResource use_token = True options = None def __init__(self, options): self.options = options - if 'DOMAIN' not in options: + if "DOMAIN" not in self.options: raise RestBaseException("DOMAIN is missing in options") - if 'API_PREFIX' not in options: - options['API_PREFIX'] = API_PREFIX - self.base_url = '{0}/{1}'.format(self.options['DOMAIN'], options['API_PREFIX'] ) - if 'TOKEN_TYPE' in options: - self.token_type = options['TOKEN_TYPE'] - if 'TOKEN_FORMAT' in options: - self.token_format = options['TOKEN_FORMAT'] - + if "API_PREFIX" not in self.options: + self.options["API_PREFIX"] = API_PREFIX + self.base_url = "{0}/{1}".format( + self.options["DOMAIN"], self.options["API_PREFIX"] + ) + if "TOKEN_TYPE" not in self.options: + self.options["TOKEN_TYPE"] = DEFAULT_TOKEN_TYPE + if "TOKEN_FORMAT" not in self.options: + self.options["TOKEN_FORMAT"] = DEFAULT_TOKEN_FORMAT def set_token(self, token): self.token = token - def login(self, password, email): - assert('LOGIN' in self.options) - data = {'email': email, 'password': password} - url = '{0}/{1}'.format(self.base_url, self.options['LOGIN']) + def login(self, password, username=None): + assert "LOGIN" in self.options + # This allows us to suport both a {'email': username} and {'username": username} + # Default to 'username' which is the default DRF behavior + username_key = self.options.get("USERNAME_KEY", "username") + data = {"password": password} + data[username_key] = username + url = "{0}/{1}".format(self.base_url, self.options["LOGIN"]) payload = json.dumps(data) r = requests.post(url, data=payload, headers=DEFAULT_HEADERS) - if r.status_code == 200: + if r.status_code in [200, 201]: content = json.loads(r.content.decode()) - if self.token_type in content: - self.token = content[self.token_type] - - self.username = content['username'] - logger.info('Welcome @{0} (token: {1})'.format(self.username, self.token)) + self.token = content.get(self.options["TOKEN_TYPE"]) + if self.token is None: + # Default to "token" if token_type is not used by server + self.token = content.get("token") + self.username = username return True else: - logger.error('Login failed: ' + str(r.status_code) + ' ' + r.content.decode()) + logger.error( + "Login failed: " + str(r.status_code) + " " + r.content.decode() + ) return False def logout(self): - assert('LOGOUT' in self.options) - url = '{0}/{1}'.format(self.base_url, self.options['LOGOUT']) + assert "LOGOUT" in self.options + url = f"{self.base_url}/{self.options['LOGOUT']}" headers = DEFAULT_HEADERS - headers['Authorization'] = self.token_format.format(token=self.token) + headers["Authorization"] = self.options["TOKEN_FORMAT"].format(token=self.token) r = requests.post(url, headers=headers) if r.status_code == 204: - logger.info('Goodbye @{0}'.format(self.username)) + logger.info(f"Goodbye @{self.username}") self.username = None self.token = None else: - logger.error('Logout failed: ' + str(r.status_code) + ' ' + r.content.decode()) + logger.error( + "Logout failed: " + str(r.status_code) + " " + r.content.decode() + ) def __getattr__(self, item): """ @@ -273,15 +352,17 @@ if item.startswith("_"): raise AttributeError(item) + if self.options.get("USE_DASHES", False): + item = item.replace("_", "-") + kwargs = { - 'token': self.token, - 'base_url': self.base_url, - 'use_token': self.use_token, - 'token_format': self.token_format, + "token": self.token, + "base_url": f"{self.base_url}/{item}/", + "use_token": self.use_token, + "options": self.options, } - kwargs.update({'base_url': '{0}/{1}/'.format(kwargs['base_url'], item)}) return self._get_resource(**kwargs) def _get_resource(self, **kwargs): - return self.resource_class(**kwargs) \ No newline at end of file + return self.resource_class(**kwargs) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/drf_client/exceptions.py new/django-rest-framework-client-0.10.0/drf_client/exceptions.py --- old/django-rest-framework-client-0.1.1/drf_client/exceptions.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/drf_client/exceptions.py 2023-10-05 03:44:46.000000000 +0200 @@ -1,5 +1,3 @@ - - class RestBaseException(Exception): """ All Rest exceptions inherit from this exception. @@ -58,4 +56,4 @@ class ImproperlyConfigured(RestBaseException): """ Rest is somehow improperly configured. - """ \ No newline at end of file + """ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/drf_client/helpers/base_facade.py new/django-rest-framework-client-0.10.0/drf_client/helpers/base_facade.py --- old/django-rest-framework-client-0.1.1/drf_client/helpers/base_facade.py 1970-01-01 01:00:00.000000000 +0100 +++ new/django-rest-framework-client-0.10.0/drf_client/helpers/base_facade.py 2023-10-05 03:44:46.000000000 +0200 @@ -0,0 +1,25 @@ +"""Hold static information that can be accessed by any part of the package. + +A facade is an object that serves as a front-facing interface masking more complex +underlying or structural code. +""" +from argparse import Namespace + +from drf_client.connection import Api as RestApi + + +class BaseFacade: + """Stores key static information used across the package.""" + + api: RestApi or None = None + api_options: dict or None = None + cmd_args: Namespace or None = None + + @staticmethod + def initialize_api(api_options: dict, cmd_args: Namespace = None): + """Initialize API with the given options.""" + if BaseFacade.api is None: + # Only initialize ones + BaseFacade.api_options = api_options.copy() + BaseFacade.api = RestApi(api_options) + BaseFacade.cmd_args = cmd_args diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/drf_client/helpers/base_main.py new/django-rest-framework-client-0.10.0/drf_client/helpers/base_main.py --- old/django-rest-framework-client-0.1.1/drf_client/helpers/base_main.py 1970-01-01 01:00:00.000000000 +0100 +++ new/django-rest-framework-client-0.10.0/drf_client/helpers/base_main.py 2023-10-05 03:44:46.000000000 +0200 @@ -0,0 +1,166 @@ +import argparse +import getpass +import logging +import os +import sys +from urllib.parse import urlparse + +from .base_facade import BaseFacade + +LOG = logging.getLogger(__name__) + + +class BaseMain: + """Boiler plate code for basic scripts. + + The class assumes that most scripts include the basic following flow: + + - Parse arguments + - Setup LOG configuration + - Login + - Do something after logging in +""" + + parser = None + args = None + api = None + options = { + "DOMAIN": None, + "API_PREFIX": "api/v1", + "TOKEN_TYPE": "jwt", + "TOKEN_FORMAT": "JWT {token}", + "USERNAME_KEY": "username", + "LOGIN": "auth/login/", + "LOGOUT": "auth/logout/", + "USE_DASHES": False, + } + logging_level = logging.INFO + + def __init__(self): + """ + Initialize Logging configuration + Initialize argument parsing + Process any extra arguments + Only hard codes one required argument: --user + Additional arguments can be configured by overwriting the add_extra_args() method + Logging configuration can be changed by overwritting the config_logging() method + """ + self.parser = argparse.ArgumentParser(description=__doc__) + self.parser.add_argument( + "-u", + "--user", + dest="username", + type=str, + required=False, + help="Username used for login", + ) + self.parser.add_argument( + "-t", + "--use-token", + dest="use_token", + action="store_true", + help="Use token (expects DRF_CLIENT_AUTH_TOKEN to be defined as an env variable)", + ) + self.parser.add_argument( + "--server", + dest="server", + type=str, + required=True, + help="Server Domain Name to use", + ) + + self.add_extra_args() + + self.args = self.parser.parse_args() + self.config_logging() + self.domain = "" + + def _critical_exit(self, msg): + """Exit with an error.""" + LOG.error(msg) + sys.exit(1) + + def main(self): + """ + Main function to call to initiate execution. + 1. Get domain name and use to instantiate Api object + 2. Call before_login to allow for work before logging in + 3. Logging into the server + 4. Call after_loging to do actual work with server data + """ + self.domain = self.get_domain() + # Create a static pointer to the API for global access + BaseFacade.initialize_api(api_options=self.get_options(), cmd_args=self.args) + self.api = BaseFacade.api + self.before_login() + ok = self.login() + if ok: + self.after_login() + + # Following functions can be overwritten if needed + # ================================================ + + def get_options(self): + """Add domain to Api options.""" + options = self.options + options["DOMAIN"] = self.domain + return options + + def config_logging(self): + """ + Overwrite to change the way the logging package is configured + :return: Nothing + """ + logging.basicConfig( + level=self.logging_level, + format="[%(asctime)-15s] %(levelname)-6s %(message)s", + datefmt="%d/%b/%Y %H:%M:%S", + ) + + def add_extra_args(self): + """ + Overwrite to change the way extra arguments are added to the args parser + :return: Nothing + """ + pass + + def get_domain(self) -> str: + """ + Figure out server domain URL based on --server and --customer args + """ + if not urlparse(self.args.server).scheme: + return f"https://{self.args.server}" + return self.args.server + + def login(self) -> bool: + """ + Get password from user and login + """ + if self.args.use_token: + token = os.getenv("DRF_CLIENT_AUTH_TOKEN") + if not token: + self._critical_exit("DRF_CLIENT_AUTH_TOKEN must be defined as environment variable.") + self.api.set_token(token) + LOG.info("Bearer Token has been set.") + ok = True + else: + password = getpass.getpass() + ok = self.api.login(username=self.args.username, password=password) + if ok: + LOG.info("Welcome {0}.".format(self.args.username)) + return ok + + def before_login(self): + """ + Overwrite to do work after parsing, but before logging in to the server + This is a good place to do additional custom argument checks + :return: Nothing + """ + pass + + def after_login(self): + """ + This function MUST be overwritten to do actual work after logging into the Server + :return: Nothing + """ + LOG.warning("No actual work done") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/example.py new/django-rest-framework-client-0.10.0/example.py --- old/django-rest-framework-client-0.1.1/example.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/example.py 2023-10-05 03:44:46.000000000 +0200 @@ -2,50 +2,54 @@ import logging import sys from pprint import pprint + from drf_client.connection import Api as RestApi logging.basicConfig(stream=sys.stdout, level=logging.INFO) logger = logging.getLogger(__name__) -email = input('Email? ') +username = input("Email? ") password = getpass.getpass() options = { - 'DOMAIN': 'http://127.0.0.1:8000', - 'API_PREFIX': 'api/v1', - 'TOKEN_TYPE': 'jwt', - 'TOKEN_FORMAT': 'JWT {token}', - 'LOGIN': 'auth/login/', - 'LOGOUT': 'auth/logout/', + "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, } c = RestApi(options) -ok = c.login(email=email, password=password) +ok = c.login(username=username, password=password) if ok: - # GET some data my_objects = c.org.get() - for obj in my_objects['results']: + for obj in my_objects["results"]: pprint(obj) - logger.info('------------------------------') + logger.info("------------------------------") - logger.info('------------------------------') - logger.info('------------------------------') - my_object = c.org('arch-internal').get() + logger.info("------------------------------") + logger.info("------------------------------") + # If the URL includes "-", add under parenthesis: + # GET: /api/v1/someresource/some-path/ + my_object = c.someresource("some-path").get() pprint(my_object) - logger.info('------------------------------') - logger.info('------------------------------') + logger.info("------------------------------") + logger.info("------------------------------") payload = { - 'data1': 'val1', - 'data2': 'val2', + "data1": "val1", + "data2": "val2", } - resp = c.org.post(data=payload) + resp = c.someresource.post(data=payload) pprint(resp) - logger.info('------------------------------') + logger.info("------------------------------") c.logout() diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/requirements-build.txt new/django-rest-framework-client-0.10.0/requirements-build.txt --- old/django-rest-framework-client-0.1.1/requirements-build.txt 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/requirements-build.txt 2023-10-05 03:44:46.000000000 +0200 @@ -1,3 +1,7 @@ pip -twine -tox \ No newline at end of file +twine==3.2.0 +setuptools>=40.8.0 +wheel +tox +isort==5.12.0 +black==23.7.0 diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/requirements-test.txt new/django-rest-framework-client-0.10.0/requirements-test.txt --- old/django-rest-framework-client-0.1.1/requirements-test.txt 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/requirements-test.txt 2023-10-05 03:44:46.000000000 +0200 @@ -1,5 +1,4 @@ pytest -unittest2 coverage==3.7.1 coveralls mock diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/setup.py new/django-rest-framework-client-0.10.0/setup.py --- old/django-rest-framework-client-0.1.1/setup.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/setup.py 2023-10-05 03:44:46.000000000 +0200 @@ -1,24 +1,48 @@ +import pathlib + from setuptools import setup -setup(name='django-rest-framework-client', - version='0.1.1', - description='Python client for a DjangoRestFramework based web site', - url='https://github.com/dkarchmer/django-rest-framework-client', - author='David Karchmer', +# The directory containing this file +HERE = pathlib.Path(__file__).parent + +# The text of the README file +README = (HERE / "README.md").read_text() + +setup( + name="django-rest-framework-client", + version="0.10.0", + description="Python client for a DjangoRestFramework based web site", + long_description=README, + long_description_content_type="text/markdown", + url="https://github.com/dkarchmer/django-rest-framework-client", + author="David Karchmer", author_email="dkarch...@gmail.com", - license='MIT', + license="MIT", packages=[ - 'drf_client', + "drf_client", + "drf_client.helpers", ], install_requires=[ - 'requests', + "requests", + ], + python_requires=">=3.10,<4", + keywords=[ + "django", + "djangorestframework", + "drf", + "rest-client", ], - keywords=["django", "djangorestframework", "Rest",], classifiers=[ "Programming Language :: Python", - "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: Implementation :: PyPy", + "Development Status :: 4 - Beta", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", - "Topic :: Software Development :: Libraries :: Python Modules" + "Topic :: Software Development :: Libraries :: Python Modules", ], - zip_safe=False) \ No newline at end of file + zip_safe=False, +) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/tests/__init__.py new/django-rest-framework-client-0.10.0/tests/__init__.py --- old/django-rest-framework-client-0.1.1/tests/__init__.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/tests/__init__.py 2023-10-05 03:44:46.000000000 +0200 @@ -5,11 +5,12 @@ def get_tests(): return full_suite() + def full_suite(): - from .resources import ResourceTestCase from .api import ApiTestCase + from .resources import ResourceTestCase resourcesuite = unittest.TestLoader().loadTestsFromTestCase(ResourceTestCase) apisuite = unittest.TestLoader().loadTestsFromTestCase(ApiTestCase) - return unittest.TestSuite([resourcesuite, apisuite]) \ No newline at end of file + return unittest.TestSuite([resourcesuite, apisuite]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/tests/api.py new/django-rest-framework-client-0.10.0/tests/api.py --- old/django-rest-framework-client-0.1.1/tests/api.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/tests/api.py 2023-10-05 03:44:46.000000000 +0200 @@ -1,9 +1,10 @@ -import sys import json +import sys +import unittest + import mock import requests import requests_mock -import unittest2 as unittest from drf_client.connection import Api from drf_client.exceptions import HttpClientError, HttpServerError @@ -14,12 +15,13 @@ def setUp(self): options = { - 'DOMAIN': 'https://example.com', - 'API_PREFIX': 'api/v1', - 'TOKEN_TYPE': 'jwt', - 'TOKEN_FORMAT': 'JWT {token}', - 'LOGIN': 'auth/login/', - 'LOGOUT': 'auth/logout/', + "DOMAIN": "https://example.com", + "API_PREFIX": "api/v1", + "TOKEN_TYPE": "jwt", + "TOKEN_FORMAT": "JWT {token}", + "LOGIN": "auth/login/", + "LOGOUT": "auth/logout/", + "USE_DASHES": False, } self.api = Api(options=options) @@ -28,161 +30,153 @@ self.api = None def test_init(self): - - self.assertEqual(self.api.base_url, 'https://example.com/api/v1') + self.assertEqual(self.api.base_url, "https://example.com/api/v1") self.assertTrue(self.api.use_token) - self.assertEqual(self.api.token_type, 'jwt') + self.assertEqual(self.api.options["TOKEN_TYPE"], "jwt") + self.assertEqual(self.api.options["TOKEN_FORMAT"], "JWT {token}") def test_set_token(self): - self.assertEqual(self.api.token, None) - self.api.set_token('big-token') - self.assertEqual(self.api.token, 'big-token') + self.api.set_token("big-token") + self.assertEqual(self.api.token, "big-token") @requests_mock.Mocker() def test_login(self, m): - payload = { - 'jwt': 'big-token', - 'username': 'user1' - } - m.post('https://example.com/api/v1/auth/login/', text=json.dumps(payload)) + payload = {"jwt": "big-token", "username": "user1"} + m.post("https://example.com/api/v1/auth/login/", text=json.dumps(payload)) - ok = self.api.login(email='us...@test.com', password='pass') + ok = self.api.login(username="us...@test.com", password="pass") self.assertTrue(ok) - self.assertEqual(self.api.username, 'user1') - self.assertEqual(self.api.token, 'big-token') + self.assertEqual(self.api.username, "us...@test.com") + self.assertEqual(self.api.token, "big-token") @requests_mock.Mocker() def test_logout(self, m): - payload = { - 'jwt': 'big-token', - 'username': 'user1' - } - m.post('https://example.com/api/v1/auth/login/', text=json.dumps(payload)) - m.post('https://example.com/api/v1/auth/logout/', status_code=204) + payload = {"jwt": "big-token", "username": "user1"} + m.post("https://example.com/api/v1/auth/login/", text=json.dumps(payload)) + m.post("https://example.com/api/v1/auth/logout/", status_code=204) - ok = self.api.login(email='us...@test.com', password='pass') + ok = self.api.login(username="us...@test.com", password="pass") self.assertTrue(ok) self.api.logout() self.assertEqual(self.api.username, None) self.assertEqual(self.api.token, None) - @requests_mock.Mocker() def test_get_list(self, m): - payload = { - "result": ["a", "b", "c"] - } - m.get('https://example.com/api/v1/test/', text=json.dumps(payload)) + payload = {"result": ["a", "b", "c"]} + m.get("https://example.com/api/v1/test/", text=json.dumps(payload)) resp = self.api.test.get() - self.assertEqual(resp['result'], ['a', 'b', 'c']) + self.assertEqual(resp["result"], ["a", "b", "c"]) @requests_mock.Mocker() def test_get_detail(self, m): - payload = { - "a": "b", - "c": "d" - } - m.get('https://example.com/api/v1/test/my-detail/', text=json.dumps(payload)) + payload = {"a": "b", "c": "d"} + m.get("https://example.com/api/v1/test/my-detail/", text=json.dumps(payload)) - resp = self.api.test('my-detail').get() - self.assertEqual(resp, {'a': 'b', 'c': 'd'}) + resp = self.api.test("my-detail").get() + self.assertEqual(resp, {"a": "b", "c": "d"}) @requests_mock.Mocker() def test_get_detail_with_action(self, m): - payload = { - "a": "b", - "c": "d" - } - m.get('https://example.com/api/v1/test/my-detail/action/', text=json.dumps(payload)) - - resp = self.api.test('my-detail').action.url() - self.assertEqual(resp, 'https://example.com/api/v1/test/my-detail/action/') - resp = self.api.test('my-detail').action.get() - self.assertEqual(resp, {'a': 'b', 'c': 'd'}) + payload = {"a": "b", "c": "d"} + m.get( + "https://example.com/api/v1/test/my-detail/action/", + text=json.dumps(payload), + ) + + # resp = self.api.test('my-detail').action.url() + # self.assertEqual(resp, 'https://example.com/api/v1/test/my-detail/action/') + resp = self.api.test("my-detail").action.get() + self.assertEqual(resp, {"a": "b", "c": "d"}) + + @requests_mock.Mocker() + def test_get_with_use_dashes(self, m): + """test that we can replace underscore with dashes.""" + self.api.options["USE_DASHES"] = True + payload = {"a": "b", "c": "d"} + m.get( + "https://example.com/api/v1/test-one/my-detail/action/", + text=json.dumps(payload), + ) + + # resp = self.api.test('my-detail').action.url() + # self.assertEqual(resp, 'https://example.com/api/v1/test/my-detail/action/') + resp = self.api.test_one.my_detail.action.get() + self.assertEqual(resp, {"a": "b", "c": "d"}) @requests_mock.Mocker() def test_get_detail_with_extra_args(self, m): - payload = { - "a": "b", - "c": "d" - } - m.get('https://example.com/api/v1/test/my-detail/', text=json.dumps(payload)) + payload = {"a": "b", "c": "d"} + m.get("https://example.com/api/v1/test/my-detail/", text=json.dumps(payload)) - resp = self.api.test('my-detail').get(foo='bar') - self.assertEqual(resp, {'a': 'b', 'c': 'd'}) + resp = self.api.test("my-detail").get(foo="bar") + self.assertEqual(resp, {"a": "b", "c": "d"}) @requests_mock.Mocker() def test_post(self, m): - payload = { - "foo": ["a", "b", "c"] - } - result = { - "id": 1 - } - m.post('https://example.com/api/v1/test/', text=json.dumps(result)) + payload = {"foo": ["a", "b", "c"]} + result = {"id": 1} + m.post("https://example.com/api/v1/test/", text=json.dumps(result)) resp = self.api.test.post(payload) - self.assertEqual(resp['id'], 1) + self.assertEqual(resp["id"], 1) @requests_mock.Mocker() def test_patch(self, m): - payload = { - "foo": ["a", "b", "c"] - } - result = { - "id": 1 - } - m.patch('https://example.com/api/v1/test/my-detail/', text=json.dumps(result)) + payload = {"foo": ["a", "b", "c"]} + result = {"id": 1} + m.patch("https://example.com/api/v1/test/my-detail/", text=json.dumps(result)) - resp = self.api.test('my-detail').patch(payload) - self.assertEqual(resp['id'], 1) + resp = self.api.test("my-detail").patch(payload) + self.assertEqual(resp["id"], 1) @requests_mock.Mocker() def test_put(self, m): - payload = { - "foo": ["a", "b", "c"] - } - result = { - "id": 1 - } - m.put('https://example.com/api/v1/test/my-detail/', text=json.dumps(result)) + payload = {"foo": ["a", "b", "c"]} + result = {"id": 1} + m.put("https://example.com/api/v1/test/my-detail/", text=json.dumps(result)) - resp = self.api.test('my-detail').put(payload) - self.assertEqual(resp['id'], 1) + resp = self.api.test("my-detail").put(payload) + self.assertEqual(resp["id"], 1) @requests_mock.Mocker() def test_delete(self, m): - result = { - "id": 1 - } - m.delete('https://example.com/api/v1/test/my-detail/', text=json.dumps(result)) + result = {"id": 1} + m.delete("https://example.com/api/v1/test/my-detail/", text=json.dumps(result)) - deleted = self.api.test('my-detail').delete() + deleted = self.api.test("my-detail").delete() + self.assertTrue(deleted) + + result = {"id": 2} + m.delete("https://example.com/api/v1/test/my-detail2/", text=json.dumps(result)) + + deleted = self.api.test("my-detail2").delete(data={"foo": "bar"}) self.assertTrue(deleted) @requests_mock.Mocker() def test_post_with_error(self, m): - payload = { - "foo": ["a", "b", "c"] - } - result = { - "id": 1 - } - m.post('https://example.com/api/v1/test/', status_code=400, text=json.dumps(result)) + payload = {"foo": ["a", "b", "c"]} + result = {"id": 1} + m.post( + "https://example.com/api/v1/test/", status_code=400, text=json.dumps(result) + ) with self.assertRaises(HttpClientError): self.api.test.post(payload) - m.post('https://example.com/api/v1/test/', status_code=404, text=json.dumps(result)) + m.post( + "https://example.com/api/v1/test/", status_code=404, text=json.dumps(result) + ) with self.assertRaises(HttpClientError): self.api.test.post(payload) - m.post('https://example.com/api/v1/test/', status_code=500, text=json.dumps(result)) + m.post( + "https://example.com/api/v1/test/", status_code=500, text=json.dumps(result) + ) with self.assertRaises(HttpServerError): self.api.test.post(payload) - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/tests/helpers.py new/django-rest-framework-client-0.10.0/tests/helpers.py --- old/django-rest-framework-client-0.1.1/tests/helpers.py 1970-01-01 01:00:00.000000000 +0100 +++ new/django-rest-framework-client-0.10.0/tests/helpers.py 2023-10-05 03:44:46.000000000 +0200 @@ -0,0 +1,14 @@ +"""test Resource class.""" +import unittest + +from drf_client.helpers.base_facade import BaseFacade + + +class FacadeTestCase(unittest.TestCase): + """Test static facade class.""" + + def test_initialize_facade(self): + """Test Initializer.""" + BaseFacade.initialize_api(api_options={"DOMAIN": "https://example.com"}) + assert BaseFacade.api_options["DOMAIN"] == "https://example.com" + assert BaseFacade.api is not None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/tests/resources.py new/django-rest-framework-client-0.10.0/tests/resources.py --- old/django-rest-framework-client-0.1.1/tests/resources.py 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/tests/resources.py 2023-10-05 03:44:46.000000000 +0200 @@ -1,44 +1,48 @@ -import sys +"""test Resource class.""" import json -import mock -import requests +import unittest + import requests_mock -import unittest2 as unittest -from drf_client.connection import Api, RestResource -from drf_client import exceptions +from drf_client.connection import RestResource class ResourceTestCase(unittest.TestCase): - def setUp(self): - self.base_resource = RestResource(base_url="https://example.com/api/v1/test/", - use_token=True, - token_format='JWT {token}', - token_type='jwt', - token='my-token') + self.options = { + "DOMAIN": "https://example.com", + "API_PREFIX": "api/v1", + "TOKEN_TYPE": "jwt", + "TOKEN_FORMAT": "JWT {token}", + "USERNAME_KEY": "username", + "LOGIN": "auth/login/", + "LOGOUT": "auth/logout/", + "USE_DASHES": False, + } + self.base_resource = RestResource( + base_url="https://example.com/api/v1/test/", + use_token=True, + options=self.options, + token="my-token", + ) def test_url(self): - url = self.base_resource.url() - self.assertEqual(url, 'https://example.com/api/v1/test/') + self.assertEqual(url, "https://example.com/api/v1/test/") def test_headers(self): expected_headers = { - 'Content-Type': 'application/json', - 'Authorization': 'JWT my-token' + "Content-Type": "application/json", + "Authorization": "JWT my-token", } - headers = self.base_resource._get_header() + headers = self.base_resource._get_headers() self.assertEqual(headers, expected_headers) @requests_mock.Mocker() def test_get_200(self, m): - payload = { - "result": ["a", "b", "c"] - } - m.get('https://example.com/api/v1/test/', text=json.dumps(payload)) + payload = {"result": ["a", "b", "c"]} + m.get("https://example.com/api/v1/test/", text=json.dumps(payload)) resp = self.base_resource.get() - self.assertEqual(resp['result'], ['a', 'b', 'c']) - + self.assertEqual(resp["result"], ["a", "b", "c"]) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/django-rest-framework-client-0.1.1/tox.ini new/django-rest-framework-client-0.10.0/tox.ini --- old/django-rest-framework-client-0.1.1/tox.ini 2017-05-07 21:11:26.000000000 +0200 +++ new/django-rest-framework-client-0.10.0/tox.ini 2023-10-05 03:44:46.000000000 +0200 @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py36 +envlist = py310, py311 [testenv] deps = @@ -13,4 +13,4 @@ commands = coverage run --source=drf_client -m py.test coverage report -m -usedevelop = true \ No newline at end of file +usedevelop = true ++++++ python-django-rest-framework-client-no-mock.patch ++++++ --- /var/tmp/diff_new_pack.Qi76kk/_old 2024-02-23 16:46:58.018899430 +0100 +++ /var/tmp/diff_new_pack.Qi76kk/_new 2024-02-23 16:46:58.022899575 +0100 @@ -1,23 +1,14 @@ -diff -upr django-rest-framework-client-0.1.1.orig/tests/api.py django-rest-framework-client-0.1.1/tests/api.py ---- django-rest-framework-client-0.1.1.orig/tests/api.py 2022-05-12 09:32:27.295052321 +0200 -+++ django-rest-framework-client-0.1.1/tests/api.py 2022-05-12 09:32:27.299052347 +0200 -@@ -1,6 +1,6 @@ +Index: django-rest-framework-client-0.10.0/tests/api.py +=================================================================== +--- django-rest-framework-client-0.10.0.orig/tests/api.py ++++ django-rest-framework-client-0.10.0/tests/api.py +@@ -2,7 +2,7 @@ import json import sys - import json --import mock -+from unittest import mock - import requests - import requests_mock import unittest -diff -upr django-rest-framework-client-0.1.1.orig/tests/resources.py django-rest-framework-client-0.1.1/tests/resources.py ---- django-rest-framework-client-0.1.1.orig/tests/resources.py 2022-05-12 09:32:27.295052321 +0200 -+++ django-rest-framework-client-0.1.1/tests/resources.py 2022-05-12 09:32:27.299052347 +0200 -@@ -1,6 +1,6 @@ - import sys - import json + -import mock +from unittest import mock import requests import requests_mock - import unittest +