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
 
-[![Build 
Status](https://travis-ci.org/dkarchmer/django-rest-framework-client.svg?branch=master)](https://travis-ci.org/dkarchmer/django-rest-framework-client)
-[![PyPI 
version](https://img.shields.io/pypi/v/django-rest-framework-client.svg)](https://pypi.python.org/pypi/django-rest-framework-client)
 
+[![PyPI 
version](https://img.shields.io/pypi/v/django-rest-framework-client.svg)](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
+ 
 

Reply via email to