Hello community,

here is the log from the commit of package python-python-redmine for 
openSUSE:Factory checked in at 2020-08-06 10:42:07
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-redmine (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-redmine.new.3399 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-redmine"

Thu Aug  6 10:42:07 2020 rev:3 rq:824578 version:2.3.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-redmine/python-python-redmine.changes  
    2020-04-22 20:51:37.523231665 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-python-redmine.new.3399/python-python-redmine.changes
    2020-08-06 10:42:16.102124492 +0200
@@ -1,0 +2,21 @@
+Wed Aug  5 09:33:59 UTC 2020 - Marketa Calabkova <mcalabk...@suse.com>
+
+- Update to version 2.3.0
+  * Support custom filename in ``redmine.upload()``
+  * Support for ``get()`` and ``update()`` operations for ``/my/account`` 
endpoint which doesn't require admin
+    privileges by using ``me`` as an id, i.e. ``redmine.user.get('me')`` or 
``redmine.user.update('me',firstname='John')``
+    (requires Redmine >= 4.1.0)
+  * News ``create()``, ``update()``, ``delete()`` operations support (requires 
Redmine >= 4.1.0)
+  * ResourceSet's ``export()`` method now supports ``columns`` keyword 
argument which can be either an iterable
+    of column names, an "all" string which tells Python*Redmine to export all 
available columns, "all_gui" string
+    for GUI like behaviour or iterable of elements with "all_gui" string and 
additional columns to export
+  * Added support for special characters in WikiPage titles (`Issue #222 
<https://github.com/maxtepkeev/python-redmine/
+    issues/222>`__) (thanks to `Radek Czajka <https://github.com/rczajka>`__)
+  * Added ``return_response`` and ``ignore_response`` parameters to engine 
which allow to skip response processing
+    and speed up the create/update/delete operation in case response body 
isn't needed (see
+    `docs 
<https://python*redmine.com/advanced/request_engines.html#session>`__ for 
details)
+  * *Backwards Incompatible:* Requests version required >= 2.23.0
+  * *Backwards Incompatible:* Removed Python 3.4 support as it's not supported 
by Requests anymore
+- Drop apparently obsolete python-python-redmine-use-system-requests.patch
+
+-------------------------------------------------------------------

Old:
----
  python-python-redmine-use-system-requests.patch
  python-redmine-2.2.1.tar.gz

New:
----
  python-redmine-2.3.0.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-python-redmine.spec ++++++
--- /var/tmp/diff_new_pack.EmGb2m/_old  2020-08-06 10:42:16.766124824 +0200
+++ /var/tmp/diff_new_pack.EmGb2m/_new  2020-08-06 10:42:16.766124824 +0200
@@ -19,21 +19,20 @@
 %{?!python_module:%define python_module() python-%{**} python3-%{**}}
 %bcond_without python2
 Name:           python-python-redmine
-Version:        2.2.1
+Version:        2.3.0
 Release:        0
 Summary:        Python library for the Redmine RESTful API
 License:        Apache-2.0
 Group:          Development/Languages/Python
 URL:            https://python-redmine.com
 Source:         
https://files.pythonhosted.org/packages/source/p/python-redmine/python-redmine-%{version}.tar.gz
-Patch0:         python-python-redmine-use-system-requests.patch
 BuildRequires:  %{python_module coverage}
 BuildRequires:  %{python_module nose}
-BuildRequires:  %{python_module requests >= 2.20.0}
+BuildRequires:  %{python_module requests >= 2.23.0}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-requests >= 2.20.0
+Requires:       python-requests >= 2.23.0
 BuildArch:      noarch
 %if %{with python2}
 BuildRequires:  python-mock
@@ -48,7 +47,6 @@
 
 %prep
 %setup -q -n python-redmine-%{version}
-%patch0 -p1
 
 %build
 %python_build

++++++ python-redmine-2.2.1.tar.gz -> python-redmine-2.3.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/CHANGELOG.rst 
new/python-redmine-2.3.0/CHANGELOG.rst
--- old/python-redmine-2.2.1/CHANGELOG.rst      2019-02-28 12:40:05.000000000 
+0100
+++ new/python-redmine-2.3.0/CHANGELOG.rst      2020-05-21 13:45:19.000000000 
+0200
@@ -1,6 +1,45 @@
 Changelog
 ---------
 
+2.3.0 (2020-05-21)
+++++++++++++++++++
+
+**Improvements**:
+
+- Support custom filename in ``redmine.upload()``
+- Support for ``get()`` and ``update()`` operations for ``/my/account`` 
endpoint which doesn't require admin
+  privileges by using ``me`` as an id, i.e. ``redmine.user.get('me')`` or 
``redmine.user.update('me',firstname='John')``
+  (requires Redmine >= 4.1.0)
+- News ``create()``, ``update()``, ``delete()`` operations support (requires 
Redmine >= 4.1.0)
+- ResourceSet's ``export()`` method now supports ``columns`` keyword argument 
which can be either an iterable
+  of column names, an "all" string which tells Python-Redmine to export all 
available columns, "all_gui" string
+  for GUI like behaviour or iterable of elements with "all_gui" string and 
additional columns to export
+- Added support for special characters in WikiPage titles (`Issue #222 
<https://github.com/maxtepkeev/python-redmine/
+  issues/222>`__) (thanks to `Radek Czajka <https://github.com/rczajka>`__)
+- Added ``return_response`` and ``ignore_response`` parameters to engine which 
allow to skip response processing
+  and speed up the create/update/delete operation in case response body isn't 
needed (see
+  `docs <https://python-redmine.com/advanced/request_engines.html#session>`__ 
for details)
+
+**Changes**:
+
+- *Backwards Incompatible:* Requests version required >= 2.23.0
+- *Backwards Incompatible:* Removed Python 3.4 support as it's not supported 
by Requests anymore
+
+**Bugfixes**:
+
+- User's ``send_information`` field wasn't sent correctly to Redmine so 
account information emails were
+  never sent (`Issue #227 
<https://github.com/maxtepkeev/python-redmine/pull/227>`__) (thanks to
+  `wodny <https://github.com/wodny>`__)
+- Project resource ``default_version`` attribute was returned as a dict 
instead of being converted to
+  Resource object
+- Resource object was leaking memory during initialization (`Issue #257 
<https://github.com/maxtepkeev/python-redmine/
+  issues/257>`__) (thanks to `yihli <https://github.com/qianyi3210>`__)
+
+**Documentation**:
+
+- Introduced detailed parameter list for ``redmine.session``
+- Mentioned support for ``admin`` in User's resource create/update
+
 2.2.1 (2019-02-28)
 ++++++++++++++++++
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/LICENSE 
new/python-redmine-2.3.0/LICENSE
--- old/python-redmine-2.2.1/LICENSE    2019-01-13 11:14:36.000000000 +0100
+++ new/python-redmine-2.3.0/LICENSE    2020-05-20 14:31:32.000000000 +0200
@@ -1,4 +1,4 @@
-Copyright 2019 Maxim Tepkeev
+Copyright 2020 Maxim Tepkeev
 
 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/NOTICE 
new/python-redmine-2.3.0/NOTICE
--- old/python-redmine-2.2.1/NOTICE     2019-01-12 11:45:54.000000000 +0100
+++ new/python-redmine-2.3.0/NOTICE     2020-05-07 11:14:49.000000000 +0200
@@ -3,7 +3,7 @@
 Six License
 ===========
 
-Copyright (c) 2010-2019 Benjamin Peterson
+Copyright (c) 2010-2020 Benjamin Peterson
 
 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/PKG-INFO 
new/python-redmine-2.3.0/PKG-INFO
--- old/python-redmine-2.2.1/PKG-INFO   2019-02-28 12:42:23.000000000 +0100
+++ new/python-redmine-2.3.0/PKG-INFO   2020-05-21 13:48:11.953657900 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: python-redmine
-Version: 2.2.1
+Version: 2.3.0
 Summary: Library for communicating with a Redmine project management 
application
 Home-page: https://github.com/maxtepkeev/python-redmine
 Author: Maxim Tepkeev
@@ -63,7 +63,7 @@
         
         * Supports 100% of Redmine API
         * Supports external Redmine plugins API
-        * Supports Python 2.7, 3.4 - 3.7, PyPy and PyPy3
+        * Supports Python 2.7, 3.5 - 3.8, PyPy and PyPy3
         * Supports different request engines
         * Extendable via custom resources and custom request engines
         * Extensively documented
@@ -112,6 +112,45 @@
         Changelog
         ---------
         
+        2.3.0 (2020-05-21)
+        ++++++++++++++++++
+        
+        **Improvements**:
+        
+        - Support custom filename in ``redmine.upload()``
+        - Support for ``get()`` and ``update()`` operations for 
``/my/account`` endpoint which doesn't require admin
+          privileges by using ``me`` as an id, i.e. ``redmine.user.get('me')`` 
or ``redmine.user.update('me',firstname='John')``
+          (requires Redmine >= 4.1.0)
+        - News ``create()``, ``update()``, ``delete()`` operations support 
(requires Redmine >= 4.1.0)
+        - ResourceSet's ``export()`` method now supports ``columns`` keyword 
argument which can be either an iterable
+          of column names, an "all" string which tells Python-Redmine to 
export all available columns, "all_gui" string
+          for GUI like behaviour or iterable of elements with "all_gui" string 
and additional columns to export
+        - Added support for special characters in WikiPage titles (`Issue #222 
<https://github.com/maxtepkeev/python-redmine/
+          issues/222>`__) (thanks to `Radek Czajka 
<https://github.com/rczajka>`__)
+        - Added ``return_response`` and ``ignore_response`` parameters to 
engine which allow to skip response processing
+          and speed up the create/update/delete operation in case response 
body isn't needed (see
+          `docs 
<https://python-redmine.com/advanced/request_engines.html#session>`__ for 
details)
+        
+        **Changes**:
+        
+        - *Backwards Incompatible:* Requests version required >= 2.23.0
+        - *Backwards Incompatible:* Removed Python 3.4 support as it's not 
supported by Requests anymore
+        
+        **Bugfixes**:
+        
+        - User's ``send_information`` field wasn't sent correctly to Redmine 
so account information emails were
+          never sent (`Issue #227 
<https://github.com/maxtepkeev/python-redmine/pull/227>`__) (thanks to
+          `wodny <https://github.com/wodny>`__)
+        - Project resource ``default_version`` attribute was returned as a 
dict instead of being converted to
+          Resource object
+        - Resource object was leaking memory during initialization (`Issue 
#257 <https://github.com/maxtepkeev/python-redmine/
+          issues/257>`__) (thanks to `yihli <https://github.com/qianyi3210>`__)
+        
+        **Documentation**:
+        
+        - Introduced detailed parameter list for ``redmine.session``
+        - Mentioned support for ``admin`` in User's resource create/update
+        
         2.2.1 (2019-02-28)
         ++++++++++++++++++
         
@@ -772,10 +811,10 @@
 Classifier: Environment :: Web Environment
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/README.rst 
new/python-redmine-2.3.0/README.rst
--- old/python-redmine-2.2.1/README.rst 2019-01-04 11:44:32.000000000 +0100
+++ new/python-redmine-2.3.0/README.rst 2020-05-07 12:26:23.000000000 +0200
@@ -54,7 +54,7 @@
 
 * Supports 100% of Redmine API
 * Supports external Redmine plugins API
-* Supports Python 2.7, 3.4 - 3.7, PyPy and PyPy3
+* Supports Python 2.7, 3.5 - 3.8, PyPy and PyPy3
 * Supports different request engines
 * Extendable via custom resources and custom request engines
 * Extensively documented
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.2.1/python_redmine.egg-info/PKG-INFO 
new/python-redmine-2.3.0/python_redmine.egg-info/PKG-INFO
--- old/python-redmine-2.2.1/python_redmine.egg-info/PKG-INFO   2019-02-28 
12:42:23.000000000 +0100
+++ new/python-redmine-2.3.0/python_redmine.egg-info/PKG-INFO   2020-05-21 
13:48:11.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 1.2
 Name: python-redmine
-Version: 2.2.1
+Version: 2.3.0
 Summary: Library for communicating with a Redmine project management 
application
 Home-page: https://github.com/maxtepkeev/python-redmine
 Author: Maxim Tepkeev
@@ -63,7 +63,7 @@
         
         * Supports 100% of Redmine API
         * Supports external Redmine plugins API
-        * Supports Python 2.7, 3.4 - 3.7, PyPy and PyPy3
+        * Supports Python 2.7, 3.5 - 3.8, PyPy and PyPy3
         * Supports different request engines
         * Extendable via custom resources and custom request engines
         * Extensively documented
@@ -112,6 +112,45 @@
         Changelog
         ---------
         
+        2.3.0 (2020-05-21)
+        ++++++++++++++++++
+        
+        **Improvements**:
+        
+        - Support custom filename in ``redmine.upload()``
+        - Support for ``get()`` and ``update()`` operations for 
``/my/account`` endpoint which doesn't require admin
+          privileges by using ``me`` as an id, i.e. ``redmine.user.get('me')`` 
or ``redmine.user.update('me',firstname='John')``
+          (requires Redmine >= 4.1.0)
+        - News ``create()``, ``update()``, ``delete()`` operations support 
(requires Redmine >= 4.1.0)
+        - ResourceSet's ``export()`` method now supports ``columns`` keyword 
argument which can be either an iterable
+          of column names, an "all" string which tells Python-Redmine to 
export all available columns, "all_gui" string
+          for GUI like behaviour or iterable of elements with "all_gui" string 
and additional columns to export
+        - Added support for special characters in WikiPage titles (`Issue #222 
<https://github.com/maxtepkeev/python-redmine/
+          issues/222>`__) (thanks to `Radek Czajka 
<https://github.com/rczajka>`__)
+        - Added ``return_response`` and ``ignore_response`` parameters to 
engine which allow to skip response processing
+          and speed up the create/update/delete operation in case response 
body isn't needed (see
+          `docs 
<https://python-redmine.com/advanced/request_engines.html#session>`__ for 
details)
+        
+        **Changes**:
+        
+        - *Backwards Incompatible:* Requests version required >= 2.23.0
+        - *Backwards Incompatible:* Removed Python 3.4 support as it's not 
supported by Requests anymore
+        
+        **Bugfixes**:
+        
+        - User's ``send_information`` field wasn't sent correctly to Redmine 
so account information emails were
+          never sent (`Issue #227 
<https://github.com/maxtepkeev/python-redmine/pull/227>`__) (thanks to
+          `wodny <https://github.com/wodny>`__)
+        - Project resource ``default_version`` attribute was returned as a 
dict instead of being converted to
+          Resource object
+        - Resource object was leaking memory during initialization (`Issue 
#257 <https://github.com/maxtepkeev/python-redmine/
+          issues/257>`__) (thanks to `yihli <https://github.com/qianyi3210>`__)
+        
+        **Documentation**:
+        
+        - Introduced detailed parameter list for ``redmine.session``
+        - Mentioned support for ``admin`` in User's resource create/update
+        
         2.2.1 (2019-02-28)
         ++++++++++++++++++
         
@@ -772,10 +811,10 @@
 Classifier: Environment :: Web Environment
 Classifier: Operating System :: OS Independent
 Classifier: Programming Language :: Python :: 2.7
-Classifier: Programming Language :: Python :: 3.4
 Classifier: Programming Language :: Python :: 3.5
 Classifier: Programming Language :: Python :: 3.6
 Classifier: Programming Language :: Python :: 3.7
+Classifier: Programming Language :: Python :: 3.8
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
-Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*
+Requires-Python: >=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.2.1/python_redmine.egg-info/requires.txt 
new/python-redmine-2.3.0/python_redmine.egg-info/requires.txt
--- old/python-redmine-2.2.1/python_redmine.egg-info/requires.txt       
2019-02-28 12:42:23.000000000 +0100
+++ new/python-redmine-2.3.0/python_redmine.egg-info/requires.txt       
2020-05-21 13:48:11.000000000 +0200
@@ -1 +1 @@
-requests>=2.20.0
+requests>=2.23.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/__init__.py 
new/python-redmine-2.3.0/redminelib/__init__.py
--- old/python-redmine-2.2.1/redminelib/__init__.py     2019-01-03 
08:27:49.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/__init__.py     2020-05-21 
13:25:05.000000000 +0200
@@ -33,15 +33,14 @@
         :param raise_attr_exception: (optional). Control over resource 
attribute access exception raising.
         :type raise_attr_exception: bool or tuple
         :param cls engine: (optional). Engine that will be used to make 
requests to Redmine.
-        :param bool return_raw_response (optional). Whether engine should 
return raw or json encoded responses.
         """
         self.url = url.rstrip('/')
-        self.ver = kwargs.get('version', None)
-        self.date_format = kwargs.get('date_format', '%Y-%m-%d')
-        self.datetime_format = kwargs.get('datetime_format', 
'%Y-%m-%dT%H:%M:%SZ')
-        self.raise_attr_exception = kwargs.get('raise_attr_exception', True)
+        self.ver = kwargs.pop('version', None)
+        self.date_format = kwargs.pop('date_format', '%Y-%m-%d')
+        self.datetime_format = kwargs.pop('datetime_format', 
'%Y-%m-%dT%H:%M:%SZ')
+        self.raise_attr_exception = kwargs.pop('raise_attr_exception', True)
 
-        engine = kwargs.get('engine', engines.DefaultEngine)
+        engine = kwargs.pop('engine', engines.DefaultEngine)
 
         if not inspect.isclass(engine) or not issubclass(engine, 
engines.BaseEngine):
             raise exceptions.EngineClassError
@@ -87,18 +86,20 @@
         finally:
             self.engine = engine
 
-    def upload(self, f):
+    def upload(self, f, filename=None):
         """
         Uploads file from file path / file stream to Redmine and returns an 
assigned token.
 
         :param f: (required). File path / stream that will be uploaded.
         :type f: string or file-like object
+        :param filename: (optional). Filename for the file that will be 
uploaded.
         """
         if self.ver is not None and LooseVersion(str(self.ver)) < 
LooseVersion('1.4.0'):
             raise exceptions.VersionMismatchError('File uploading')
 
         url = '{0}/uploads.json'.format(self.url)
         headers = {'Content-Type': 'application/octet-stream'}
+        params = {'filename': filename or ''}
 
         # There're myriads of file-like object implementations here and there 
and some of them don't have
         # a "read" method, which is wrong, but that's what we have, on the 
other hand it looks like all of
@@ -128,7 +129,7 @@
             stream = open(f, 'rb')
             close = True
 
-        response = self.engine.request('post', url, data=stream, 
headers=headers)
+        response = self.engine.request('post', url, params=params, 
data=stream, headers=headers)
 
         if close:
             stream.close()
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/engines/base.py 
new/python-redmine-2.3.0/redminelib/engines/base.py
--- old/python-redmine-2.2.1/redminelib/engines/base.py 2019-01-01 
16:01:53.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/engines/base.py 2020-05-19 
16:12:07.000000000 +0200
@@ -17,11 +17,18 @@
         :param string password: (optional). Password used for authentication.
         :param dict requests: (optional). Connection options.
         :param string impersonate: (optional). Username to impersonate.
+        :param bool ignore_response (optional). If True no response processing 
will be done at all.
+        :param bool return_response (optional). Whether to return response or 
None.
         :param bool return_raw_response (optional). Whether to return raw or 
json encoded responses.
         """
+        self.ignore_response = options.pop('ignore_response', False)
+        self.return_response = options.pop('return_response', True)
         self.return_raw_response = options.pop('return_raw_response', False)
         self.requests = dict(dict(headers={}, params={}, data={}), 
**options.get('requests', {}))
 
+        if self.ignore_response:
+            self.requests['stream'] = True
+
         if options.get('impersonate') is not None:
             self.requests['headers']['X-Redmine-Switch-User'] = 
options['impersonate']
 
@@ -132,6 +139,9 @@
 
         :param obj response: (required). Response object with response details.
         """
+        if self.ignore_response:
+            return None
+
         if response.history:
             r = response.history[0]
             if r.is_redirect and r.request.url.startswith('http://') and 
response.request.url.startswith('https://'):
@@ -140,7 +150,9 @@
         status_code = response.status_code
 
         if status_code in (200, 201, 204):
-            if self.return_raw_response:
+            if not self.return_response:
+                return None
+            elif self.return_raw_response:
                 return response
             elif not response.content.strip():
                 return True
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/managers/__init__.py 
new/python-redmine-2.3.0/redminelib/managers/__init__.py
--- old/python-redmine-2.2.1/redminelib/managers/__init__.py    2018-05-02 
13:33:02.000000000 +0200
+++ new/python-redmine-2.3.0/redminelib/managers/__init__.py    2020-05-20 
15:05:32.000000000 +0200
@@ -3,4 +3,4 @@
 """
 
 from .base import ResourceManager
-from .standard import WikiPageManager, FileManager
+from .standard import FileManager, WikiPageManager, UserManager, NewsManager
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/managers/base.py 
new/python-redmine-2.3.0/redminelib/managers/base.py
--- old/python-redmine-2.2.1/redminelib/managers/base.py        2019-01-04 
10:08:54.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/managers/base.py        2020-05-21 
12:35:24.000000000 +0200
@@ -2,7 +2,7 @@
 Defines base Redmine resource manager class and it's infrastructure.
 """
 
-from .. import utilities, resultsets, exceptions
+from .. import resultsets, exceptions
 
 
 class ResourceManager(object):
@@ -54,6 +54,22 @@
         manager.params = params
         return manager
 
+    def _construct_get_url(self, path):
+        """
+        Constructs URL for get method.
+
+        :param string path: absolute URL path.
+        """
+        return self.redmine.url + path
+
+    def _prepare_get_request(self, request):
+        """
+        Makes the necessary preparations for get request data.
+
+        :param dict request: Request data.
+        """
+        return self.resource_class.bulk_decode(request, self)
+
     def get(self, resource_id, **params):
         """
         Returns a Resource object from Redmine by resource id.
@@ -72,20 +88,31 @@
             return resource
 
         try:
-            self.url = self.redmine.url + 
self.resource_class.query_one.format(resource_id, **params)
-        except KeyError as exception:
-            raise exceptions.ValidationError('{0} argument is 
required'.format(exception))
+            self.url = 
self._construct_get_url(self.resource_class.query_one.format(resource_id, 
**params))
+        except KeyError as e:
+            raise exceptions.ValidationError('{0} argument is 
required'.format(e))
 
-        self.params = self.resource_class.bulk_decode(params, self)
+        self.params = self._prepare_get_request(params)
         self.container = self.resource_class.container_one
 
         try:
-            return self.to_resource(self.redmine.engine.request('get', 
self.url, params=self.params)[self.container])
+            response = self.redmine.engine.request('get', self.url, 
params=self.params)
         except exceptions.ResourceNotFoundError as e:
             if self.resource_class.requirements:
                 raise 
exceptions.ResourceRequirementsError(self.resource_class.requirements)
             raise e
 
+        return self._process_get_response(self.params, response)
+
+    def _process_get_response(self, request, response):
+        """
+        Processes get response and constructs resource object.
+
+        :param dict request: Original request data.
+        :param any response: Response received from Redmine for this request 
data.
+        """
+        return self.to_resource(response[self.container])
+
     def all(self, **params):
         """
         Returns a ResourceSet object with all Resource objects.
@@ -149,17 +176,19 @@
         if not fields:
             raise exceptions.ResourceNoFieldsProvidedError
 
-        formatter = utilities.MemorizeFormatter()
-
         try:
-            url = 
self._construct_create_url(formatter.format(self.resource_class.query_create, 
**fields))
+            url = 
self._construct_create_url(self.resource_class.query_create.format(**fields))
         except KeyError as e:
             raise exceptions.ValidationError('{0} field is required'.format(e))
 
-        self.params = formatter.used_kwargs
+        self.params = self.resource_class.query_create.formatter.used_kwargs
         self.container = self.resource_class.container_create
-        request = self._prepare_create_request(formatter.unused_kwargs)
+        request = 
self._prepare_create_request(self.resource_class.query_create.formatter.unused_kwargs)
         response = 
self.redmine.engine.request(self.resource_class.http_method_create, url, 
data=request)
+
+        if response is None:
+            return None
+
         resource = self._process_create_response(request, response)
         self.url = self.redmine.url + 
self.resource_class.query_one.format(resource.internal_id, **fields)
         return resource
@@ -203,22 +232,24 @@
         if not fields:
             raise exceptions.ResourceNoFieldsProvidedError
 
-        formatter = utilities.MemorizeFormatter()
-
         try:
-            query_update = formatter.format(self.resource_class.query_update, 
resource_id, **fields)
+            query_update = 
self.resource_class.query_update.format(resource_id, **fields)
         except KeyError as e:
             param = e.args[0]
 
             if param in self.params:
                 fields[param] = self.params[param]
-                query_update = 
formatter.format(self.resource_class.query_update, resource_id, **fields)
+                query_update = 
self.resource_class.query_update.format(resource_id, **fields)
             else:
                 raise exceptions.ValidationError('{0} argument is 
required'.format(e))
 
         url = self._construct_update_url(query_update)
-        request = self._prepare_update_request(formatter.unused_kwargs)
+        request = 
self._prepare_update_request(self.resource_class.query_update.formatter.unused_kwargs)
         response = 
self.redmine.engine.request(self.resource_class.http_method_update, url, 
data=request)
+
+        if response is None:
+            return None
+
         return self._process_update_response(request, response)
 
     def _process_update_response(self, request, response):
@@ -264,6 +295,10 @@
 
         request = self._prepare_delete_request(params)
         response = 
self.redmine.engine.request(self.resource_class.http_method_delete, url, 
params=request)
+
+        if response is None:
+            return None
+
         return self._process_delete_response(request, response)
 
     def _process_delete_response(self, request, response):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/managers/standard.py 
new/python-redmine-2.3.0/redminelib/managers/standard.py
--- old/python-redmine-2.2.1/redminelib/managers/standard.py    2018-05-01 
19:29:11.000000000 +0200
+++ new/python-redmine-2.3.0/redminelib/managers/standard.py    2020-05-21 
12:35:18.000000000 +0200
@@ -6,6 +6,14 @@
 from .. import exceptions
 
 
+class FileManager(ResourceManager):
+    def _process_create_response(self, request, response):
+        if response is True:
+            response = {self.container: {'id': 
int(request[self.container]['token'].split('.')[0])}}
+
+        return super(FileManager, self)._process_create_response(request, 
response)
+
+
 class WikiPageManager(ResourceManager):
     def _process_create_response(self, request, response):
         if response is True:
@@ -14,9 +22,34 @@
         return super(WikiPageManager, self)._process_create_response(request, 
response)
 
 
-class FileManager(ResourceManager):
+class UserManager(ResourceManager):
+    @staticmethod
+    def _check_custom_url(path):
+        if path.endswith('/me.json'):
+            path = '/my/account.json'
+
+        return path
+
+    def _construct_get_url(self, path):
+        return super(UserManager, 
self)._construct_get_url(self._check_custom_url(path))
+
+    def _prepare_create_request(self, request):
+        request = super(UserManager, self)._prepare_create_request(request)
+        request['send_information'] = 
request[self.container].pop('send_information', False)
+        return request
+
+    def _construct_update_url(self, path):
+        return super(UserManager, 
self)._construct_update_url(self._check_custom_url(path))
+
+    def _prepare_update_request(self, request):
+        request = super(UserManager, self)._prepare_update_request(request)
+        request['send_information'] = 
request[self.resource_class.container_update].pop('send_information', False)
+        return request
+
+
+class NewsManager(ResourceManager):
     def _process_create_response(self, request, response):
         if response is True:
-            response = {self.container: {'id': 
int(request[self.container]['token'].split('.')[0])}}
+            response = {self.container: 
self.redmine.news.filter(**self.params)[0].raw()}
 
-        return super(FileManager, self)._process_create_response(request, 
response)
+        return super(NewsManager, self)._process_create_response(request, 
response)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/resources/base.py 
new/python-redmine-2.3.0/redminelib/resources/base.py
--- old/python-redmine-2.2.1/redminelib/resources/base.py       2019-01-04 
10:57:02.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/resources/base.py       2020-05-21 
13:27:59.000000000 +0200
@@ -19,6 +19,8 @@
     which name starts with Base are considered base classes and not added to 
the registry.
     """
     def __new__(mcs, name, bases, attrs):
+        mcs.update_query_strings(attrs)
+
         cls = super(Registrar, mcs).__new__(mcs, name, bases, attrs)
 
         if name.startswith('Base'):  # base classes shouldn't be added to the 
registry
@@ -54,6 +56,17 @@
         return registry[name].setdefault('class', cls)
 
     @staticmethod
+    def update_query_strings(attrs):
+        """
+        Updates all `query_*` string attributes to use ResourceQueryFormatter 
by default.
+        """
+        for k, v in attrs.items():
+            if k.startswith('query_') and v is not None:
+                attrs[k] = utilities.ResourceQueryStr(v)
+
+        return attrs
+
+    @staticmethod
     def update_cls_attr(cls, name, value):
         """
         Updates class attribute's value by first copying the current value and 
then updating it with
@@ -97,6 +110,7 @@
     query_update = None
     query_delete = None
     search_hints = None
+    extra_export_columns = []
     http_method_create = 'post'
     http_method_update = 'put'
     http_method_delete = 'delete'
@@ -125,8 +139,8 @@
         relations_includes = self._relations + self._includes
 
         self.manager = manager
-        self._create_readonly += relations_includes
-        self._update_readonly += relations_includes
+        self._create_readonly = self._create_readonly[:] + relations_includes
+        self._update_readonly = self._update_readonly[:] + relations_includes
         self._decoded_attrs = dict(dict.fromkeys(relations_includes), 
**attributes)
         self._encoded_attrs = {}
         self._changes = {}
@@ -243,7 +257,8 @@
         if attr == 'uploads':
             for index, attachment in enumerate(value):
                 if 'token' not in attachment:
-                    value[index]['token'] = 
manager.redmine.upload(attachment.pop('path', ''))['token']
+                    value[index]['token'] = manager.redmine.upload(
+                        attachment.pop('path', ''), 
filename=attachment.get('filename'))['token']
 
             return attr, value
         elif attr == 'include' and isinstance(value, (list, tuple)):
@@ -370,7 +385,11 @@
             self.post_update()
         else:
             self.pre_create()
-            self._decoded_attrs = self.manager.create(**self._changes).raw()
+            resource = self.manager.create(**self._changes)
+
+            if resource is not None:
+                self._decoded_attrs = resource.raw()
+
             self.post_create()
 
         self._changes = {}
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.2.1/redminelib/resources/standard.py 
new/python-redmine-2.3.0/redminelib/resources/standard.py
--- old/python-redmine-2.2.1/redminelib/resources/standard.py   2019-02-22 
13:00:08.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/resources/standard.py   2020-05-21 
12:39:02.000000000 +0200
@@ -30,6 +30,7 @@
                   'news', 'issues', 'files']
     _unconvertible = BaseResource._unconvertible + ['identifier', 'status']
     _update_readonly = BaseResource._update_readonly + ['identifier']
+    _resource_map = {'default_version': 'Version'}
     _resource_set_map = {
         'custom_fields': 'CustomField',
         'trackers': 'Tracker',
@@ -73,6 +74,7 @@
     query_update = '/issues/{0}.json'
     query_delete = '/issues/{0}.json'
     search_hints = ['issue', 'issue closed']
+    extra_export_columns = ['description', 'last_notes']
 
     _repr = [['id', 'subject'], ['title'], ['id']]
     _includes = ['children', 'attachments', 'relations', 'changesets', 
'journals', 'watchers']
@@ -201,12 +203,13 @@
     redmine_version = '2.2'
     container_filter = '{resource}'
     query_filter = '/enumerations/{resource}.json'
+    query_url = '/enumerations/{0}/edit'
 
     _resource_set_map = {'custom_fields': 'CustomField'}
 
     @property
     def url(self):
-        return '{0}/enumerations/{1}/edit'.format(self.manager.redmine.url, 
self.internal_id)
+        return self.manager.redmine.url + 
self.query_url.format(self.internal_id)
 
 
 class Attachment(BaseResource):
@@ -393,12 +396,14 @@
     container_filter = 'users'
     container_create = 'user'
     container_update = 'user'
+    query_all_export = '/users.{format}'
     query_all = '/users.json'
     query_one = '/users/{0}.json'
     query_filter = '/users.json'
     query_create = '/users.json'
     query_update = '/users/{0}.json'
     query_delete = '/users/{0}.json'
+    manager_class = managers.UserManager
 
     _repr = [['id', 'firstname', 'lastname'], ['id', 'name']]
     _includes = ['memberships', 'groups']
@@ -485,24 +490,34 @@
 class News(BaseResource):
     redmine_version = '1.1'
     container_all = 'news'
+    container_one = 'news'
     container_filter = 'news'
+    container_create = 'news'
+    container_update = 'news'
     query_all_export = '/news.{format}'
     query_all = '/news.json'
+    query_one = '/news/{0}.json'
     query_filter = '/news.json'
+    query_create = '/projects/{project_id}/news.json'
+    query_update = '/news/{0}.json'
+    query_delete = '/news/{0}.json'
+    query_url = '/news/{0}'
     search_hints = ['news']
+    manager_class = managers.NewsManager
 
     _repr = [['id', 'title']]
     _resource_map = {'project': 'Project', 'author': 'User'}
 
     @property
     def url(self):
-        return '{0}/news/{1}'.format(self.manager.redmine.url, 
self.internal_id)
+        return self.manager.redmine.url + 
self.query_url.format(self.internal_id)
 
 
 class IssueStatus(BaseResource):
     redmine_version = '1.3'
     container_all = 'issue_statuses'
     query_all = '/issue_statuses.json'
+    query_url = '/issue_statuses/{0}/edit'
 
     _relations = ['issues']
     _relations_name = 'status'
@@ -510,37 +525,40 @@
 
     @property
     def url(self):
-        return '{0}/issue_statuses/{1}/edit'.format(self.manager.redmine.url, 
self.internal_id)
+        return self.manager.redmine.url + 
self.query_url.format(self.internal_id)
 
 
 class Tracker(BaseResource):
     redmine_version = '1.3'
     container_all = 'trackers'
     query_all = '/trackers.json'
+    query_url = '/trackers/{0}/edit'
 
     _relations = ['issues']
     _resource_set_map = {'issues': 'Issue'}
 
     @property
     def url(self):
-        return '{0}/trackers/{1}/edit'.format(self.manager.redmine.url, 
self.internal_id)
+        return self.manager.redmine.url + 
self.query_url.format(self.internal_id)
 
 
 class Query(BaseResource):
     redmine_version = '1.3'
     container_all = 'queries'
     query_all = '/queries.json'
+    query_url = '/projects/{0}/issues?query_id={1}'
 
     @property
     def url(self):
-        return '{0}/projects/{1}/issues?query_id={2}'.format(
-            self.manager.redmine.url, self._decoded_attrs.get('project_id', 
0), self.internal_id)
+        return self.manager.redmine.url + self.query_url.format(
+            self._decoded_attrs.get('project_id', 0), self.internal_id)
 
 
 class CustomField(BaseResource):
     redmine_version = '2.4'
     container_all = 'custom_fields'
     query_all = '/custom_fields.json'
+    query_url = '/custom_fields/{0}/edit'
 
     _resource_set_map = {'trackers': 'Tracker', 'roles': 'Role'}
 
@@ -566,4 +584,4 @@
 
     @property
     def url(self):
-        return '{0}/custom_fields/{1}/edit'.format(self.manager.redmine.url, 
self.internal_id)
+        return self.manager.redmine.url + 
self.query_url.format(self.internal_id)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/resultsets.py 
new/python-redmine-2.3.0/redminelib/resultsets.py
--- old/python-redmine-2.2.1/redminelib/resultsets.py   2019-01-04 
10:42:57.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/resultsets.py   2020-05-06 
19:11:49.000000000 +0200
@@ -6,7 +6,9 @@
 import functools
 import itertools
 
-from . import lookups, utilities, exceptions
+from distutils.version import LooseVersion
+
+from . import lookups, exceptions
 
 
 class BaseResourceSet(object):
@@ -42,24 +44,49 @@
 
         return self._total_count
 
-    def export(self, fmt, savepath=None, filename=None):
+    def export(self, fmt, savepath=None, filename=None, columns=None, 
encoding='UTF-8'):
         """
         Exports all resources from resource set to requested format if 
Resource supports that.
 
         :param string fmt: (required). Format to use for export, e.g. atom, 
csv, txt, pdf, html etc.
         :param string savepath: (optional). Path where to save the file.
         :param string filename: (optional). Name that will be used for the 
file.
+        :param columns: (optional). Iterable of column names, "all_gui" for 
GUI behaviour or "all" for all columns.
+        :param encoding: (optional). Encoding that will be used by Redmine for 
the result file.
+        :type columns: iterable or string
         """
         if self.manager.resource_class.query_all_export is None:
             raise exceptions.ExportNotSupported
 
-        formatter = utilities.MemorizeFormatter()
+        url = self.manager.redmine.url + 
self.manager.resource_class.query_all_export.format(
+                format=fmt, **self.manager.params)
+
+        params = 
dict(self.manager.resource_class.query_all_export.formatter.unused_kwargs, 
encoding=encoding)
 
-        url = self.manager.redmine.url + formatter.format(
-            self.manager.resource_class.query_all_export, format=fmt, 
**self.manager.params)
+        if columns is not None:
+            if columns == 'all':
+                columns = ['all', 'all_inline'] + 
self.manager.resource_class.extra_export_columns
+
+                if self.manager.redmine.ver is not None and 
LooseVersion(str(self.manager.redmine.ver)) < '3.4.0':
+                    
params.update(dict.fromkeys(self.manager.resource_class.extra_export_columns, 
1), columns='all')
+            elif 'all_gui' in columns:
+                if columns == 'all_gui':
+                    columns = ['all', 'all_inline']
+
+                    if self.manager.redmine.ver is not None and 
LooseVersion(str(self.manager.redmine.ver)) < '3.4.0':
+                        params['columns'] = 'all'
+                else:
+                    if self.manager.redmine.ver is not None and 
LooseVersion(str(self.manager.redmine.ver)) < '3.4.0':
+                        params.update(dict.fromkeys(columns, 1), columns='all')
+
+                    columns = list(columns) + ['all', 'all_inline']
+
+            # Redmine >= 3.4.0 happily accepts c[] array with column names
+            # for older versions the above hack with params is being used
+            params['c[]'] = columns
 
         try:
-            return self.manager.redmine.download(url, savepath, filename, 
params=formatter.unused_kwargs)
+            return self.manager.redmine.download(url, savepath, filename, 
params=params)
         except exceptions.UnknownError as e:
             if e.status_code == 406:
                 raise exceptions.ExportFormatNotSupportedError
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/utilities.py 
new/python-redmine-2.3.0/redminelib/utilities.py
--- old/python-redmine-2.2.1/redminelib/utilities.py    2019-01-01 
19:47:22.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/utilities.py    2019-08-18 
15:53:53.000000000 +0200
@@ -7,6 +7,11 @@
 import string
 import functools
 
+try:
+    from urllib.parse import quote
+except ImportError:
+    from urllib import quote
+
 
 def fix_unicode(cls):
     """
@@ -65,9 +70,9 @@
     return result
 
 
-class MemorizeFormatter(string.Formatter):
+class ResourceQueryFormatter(string.Formatter):
     """
-    Memorizes all arguments, used during string formatting.
+    Quotes query and memorizes all arguments, used during string formatting.
     """
     def __init__(self):
         self.used_kwargs = {}
@@ -79,3 +84,16 @@
                 self.used_kwargs[item] = kwargs.pop(item)
 
         self.unused_kwargs = kwargs
+
+    def format_field(self, value, format_spec):
+        return quote(super(ResourceQueryFormatter, self).format_field(value, 
format_spec).encode('utf-8'))
+
+
+class ResourceQueryStr(str):
+    """
+    Extends default string with additional formatting capabilities.
+    """
+    formatter = ResourceQueryFormatter()
+
+    def format(self, *args, **kwargs):
+        return self.formatter.format(self, *args, **kwargs)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/redminelib/version.py 
new/python-redmine-2.3.0/redminelib/version.py
--- old/python-redmine-2.2.1/redminelib/version.py      2019-02-28 
12:40:05.000000000 +0100
+++ new/python-redmine-2.3.0/redminelib/version.py      2020-05-21 
13:45:19.000000000 +0200
@@ -1 +1 @@
-__version__ = '2.2.1'
+__version__ = '2.3.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/setup.py 
new/python-redmine-2.3.0/setup.py
--- old/python-redmine-2.2.1/setup.py   2019-01-12 12:44:05.000000000 +0100
+++ new/python-redmine-2.3.0/setup.py   2020-05-07 12:35:55.000000000 +0200
@@ -22,7 +22,7 @@
 
 tests_require = ['nose', 'coverage']
 
-if sys.version_info[:2] < (3, 3):
+if sys.version_info[:2] == (2, 7):
     tests_require.append('mock')
 
 exec(open('redminelib/version.py').read())
@@ -41,8 +41,8 @@
     description='Library for communicating with a Redmine project management 
application',
     long_description=open('README.rst').read() + '\n\n' + 
open('CHANGELOG.rst').read(),
     keywords='redmine redmineup redminecrm redminelib easyredmine',
-    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*',
-    install_requires=['requests>=2.20.0'],
+    python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*',
+    install_requires=['requests>=2.23.0'],
     tests_require=tests_require,
     cmdclass={'test': NoseTests},
     zip_safe=False,
@@ -57,10 +57,10 @@
         'Environment :: Web Environment',
         'Operating System :: OS Independent',
         'Programming Language :: Python :: 2.7',
-        'Programming Language :: Python :: 3.4',
         'Programming Language :: Python :: 3.5',
         'Programming Language :: Python :: 3.6',
         'Programming Language :: Python :: 3.7',
+        'Programming Language :: Python :: 3.8',
         'Programming Language :: Python :: Implementation :: CPython',
         'Programming Language :: Python :: Implementation :: PyPy'
     ],
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/tests/responses/standard.py 
new/python-redmine-2.3.0/tests/responses/standard.py
--- old/python-redmine-2.2.1/tests/responses/standard.py        2018-05-01 
16:26:13.000000000 +0200
+++ new/python-redmine-2.3.0/tests/responses/standard.py        2020-05-08 
15:41:47.000000000 +0200
@@ -24,6 +24,7 @@
     },
     'wiki_page': {
         'get': {'wiki_page': {'title': 'Foo', 'version': 1}},
+        'get_special': {'wiki_page': {'title': 'Foo%Bar', 'version': 1}},
         'filter': {'wiki_pages': [{'title': 'Foo', 'version': 1}, {'title': 
'Bar', 'version': 2}]},
     },
     'project_membership': {
@@ -56,8 +57,9 @@
         'all': {'roles': [{'name': 'Foo', 'id': 1}, {'name': 'Bar', 'id': 2}]},
     },
     'news': {
-        'all': {'news': [{'title': 'Foo', 'id': 1}, {'title': 'Bar', 'id': 
2}]},
-        'filter': {'news': [{'title': 'Foo', 'id': 1}, {'title': 'Bar', 'id': 
2}]},
+        'get': {'news': {'title': 'Foo', 'id': 1}},
+        'all': {'news': [{'title': 'Foo', 'id': 2}, {'title': 'Bar', 'id': 
1}]},
+        'filter': {'news': [{'title': 'Foo', 'id': 2}, {'title': 'Bar', 'id': 
1}]},
     },
     'issue_status': {
         'all': {'issue_statuses': [{'name': 'Foo', 'id': 1}, {'name': 'Bar', 
'id': 2}]},
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/tests/test_engines.py 
new/python-redmine-2.3.0/tests/test_engines.py
--- old/python-redmine-2.2.1/tests/test_engines.py      2017-04-10 
20:53:44.000000000 +0200
+++ new/python-redmine-2.3.0/tests/test_engines.py      2020-05-19 
16:12:07.000000000 +0200
@@ -29,6 +29,17 @@
         self.response.content = ''
         self.assertEqual(self.redmine.engine.request('put', self.url), True)
 
+    def test_returns_none_with_ignore_response_true(self):
+        with self.redmine.session(ignore_response=True):
+            self.assertEqual(self.redmine.engine.ignore_response, True)
+            self.assertEqual(self.redmine.engine.requests['stream'], True)
+            self.assertEqual(self.redmine.engine.request('post', self.url), 
None)
+
+    def test_returns_none_with_return_response_false(self):
+        with self.redmine.session(return_response=False):
+            self.assertEqual(self.redmine.engine.return_response, False)
+            self.assertEqual(self.redmine.engine.request('post', self.url), 
None)
+
     def test_session_not_implemented_exception(self):
         self.assertRaises(NotImplementedError, lambda: engines.BaseEngine())
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/tests/test_managers.py 
new/python-redmine-2.3.0/tests/test_managers.py
--- old/python-redmine-2.2.1/tests/test_managers.py     2019-01-04 
11:18:43.000000000 +0100
+++ new/python-redmine-2.3.0/tests/test_managers.py     2020-05-20 
16:20:12.000000000 +0200
@@ -12,7 +12,7 @@
 
 class ResourceManagerTestCase(BaseRedmineTestCase):
     def test_has_custom_repr(self):
-        self.assertEqual(repr(self.redmine.user), 
'<redminelib.managers.ResourceManager object for User resource>')
+        self.assertEqual(repr(self.redmine.issue), 
'<redminelib.managers.ResourceManager object for Issue resource>')
 
     def test_supports_additional_resources(self):
         self.assertIsInstance(self.redmine.foo_resource, 
managers.ResourceManager)
@@ -136,6 +136,10 @@
         self.assertEqual(project._decoded_attrs, defaults)
         self.assertEqual(repr(project), '<redminelib.resources.Project #0 "">')
 
+    def test_create_resource_returns_none(self):
+        with self.redmine.session(return_response=False):
+            self.assertEqual(self.redmine.user.create(firstname='John', 
lastname='Smith'), None)
+
     def test_update_resource(self):
         self.response.content = ''
         manager = self.redmine.wiki_page
@@ -165,6 +169,10 @@
             self.assertEquals(len(w), 1)
             self.assertIs(w[0].category, exceptions.PerformanceWarning)
 
+    def test_update_resource_returns_none(self):
+        with self.redmine.session(return_response=False):
+            self.assertEqual(self.redmine.issue.update(1, subject='Bar'), None)
+
     def test_delete_resource(self):
         self.response.content = ''
         
self.assertEqual(self.redmine.wiki_page.delete(b'\xcf\x86oo'.decode('utf-8'), 
project_id=1), True)
@@ -174,6 +182,10 @@
         self.response.content = ''
         
self.assertEqual(self.redmine.wiki_page.delete(b'\xcf\x86oo'.decode('utf-8'), 
project_id=1), True)
 
+    def test_delete_resource_returns_none(self):
+        with self.redmine.session(return_response=False):
+            self.assertEqual(self.redmine.user.delete(1), None)
+
     def test_resource_get_method_unsupported_exception(self):
         self.assertRaises(exceptions.ResourceBadMethodError, lambda: 
self.redmine.issue_journal.get(1))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/tests/test_redmine.py 
new/python-redmine-2.3.0/tests/test_redmine.py
--- old/python-redmine-2.2.1/tests/test_redmine.py      2019-01-04 
11:18:43.000000000 +0100
+++ new/python-redmine-2.3.0/tests/test_redmine.py      2020-05-21 
13:33:28.000000000 +0200
@@ -62,7 +62,7 @@
     def test_successful_file_upload(self):
         self.response.status_code = 201
         self.response.json.return_value = {'upload': {'id': 1, 'token': 
'123456'}}
-        self.assertEqual(self.redmine.upload('foo')['token'], '123456')
+        self.assertEqual(self.redmine.upload('foo', 
filename='foo.jpg')['token'], '123456')
 
     def test_successful_filestream_upload(self):
         from io import StringIO
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.2.1/tests/test_resources_standard.py 
new/python-redmine-2.3.0/tests/test_resources_standard.py
--- old/python-redmine-2.2.1/tests/test_resources_standard.py   2018-05-01 
19:29:11.000000000 +0200
+++ new/python-redmine-2.3.0/tests/test_resources_standard.py   2020-05-20 
19:33:58.000000000 +0200
@@ -702,6 +702,17 @@
         wiki_page = self.redmine.wiki_page.get('Foo', project_id=1)
         self.assertEqual(wiki_page.title, 'Foo')
 
+    def test_wiki_page_get_special(self):
+        """Test getting a wiki page with special char in title."""
+        self.response.json.return_value = responses['wiki_page']['get_special']
+        wiki_page = self.redmine.wiki_page.get('Foo%Bar', project_id=1)
+        self.assertEqual(
+            self.patch_requests.call_args[0][1],
+            '{0}/projects/1/wiki/Foo%25Bar.json'.format(self.url)
+        )
+        self.assertEqual(wiki_page.title, 'Foo%Bar')
+        self.assertEqual(wiki_page.url, 
'http://foo.bar/projects/1/wiki/Foo%25Bar')
+
     def test_wiki_page_filter(self):
         self.response.json.return_value = responses['wiki_page']['filter']
         wiki_pages = self.redmine.wiki_page.filter(project_id=1)
@@ -714,6 +725,17 @@
         wiki_page = self.redmine.wiki_page.create(project_id='foo', 
title='Foo')
         self.assertEqual(wiki_page.title, 'Foo')
 
+    def test_wiki_page_create_special(self):
+        """Test creating a wiki page with special char in title."""
+        self.response.status_code = 201
+        self.response.json.return_value = responses['wiki_page']['get_special']
+        wiki_page = self.redmine.wiki_page.create(project_id='foo', 
title='Foo%Bar')
+        self.assertEqual(
+            self.patch_requests.call_args[0][1],
+            '{0}/projects/foo/wiki/Foo%25Bar.json'.format(self.url)
+        )
+        self.assertEqual(wiki_page.title, 'Foo%Bar')
+
     def test_wiki_page_delete(self):
         self.response.json.return_value = responses['wiki_page']['get']
         wiki_page = self.redmine.wiki_page.get('Foo', project_id=1)
@@ -996,6 +1018,12 @@
         self.assertEqual(user.id, 1)
         self.assertEqual(user.firstname, 'John')
 
+    def test_user_get_account(self):
+        self.response.json.return_value = responses['user']['get']
+        user = self.redmine.user.get('me')
+        self.assertEqual(user.firstname, 'John')
+        
self.assertTrue(self.patch_requests.call_args[0][1].endswith('/my/account.json'))
+
     def test_user_all(self):
         self.response.json.return_value = responses['user']['all']
         users = self.redmine.user.all()
@@ -1019,6 +1047,13 @@
         self.assertEqual(user.firstname, 'John')
         self.assertEqual(user.lastname, 'Smith')
 
+    def test_user_create_with_send_information(self):
+        import json
+        self.response.status_code = 201
+        self.response.json.return_value = responses['user']['get']
+        self.redmine.user.create(firstname='John', lastname='Smith', 
send_information=True)
+        
self.assertEqual(json.loads(self.patch_requests.call_args[1]['data'])['send_information'],
 True)
+
     def test_user_delete(self):
         self.response.json.return_value = responses['user']['get']
         user = self.redmine.user.get(1)
@@ -1033,6 +1068,16 @@
         user.firstname = 'Bar'
         self.assertIsInstance(user.save(), resources.User)
 
+    def test_user_update_account(self):
+        self.redmine.user.update('me', lastname='Foo', firstname='Bar')
+        
self.assertTrue(self.patch_requests.call_args[0][1].endswith('/my/account.json'))
+
+    def test_user_update_with_send_information(self):
+        import json
+        self.response.json.return_value = responses['user']['get']
+        self.redmine.user.update(1, firstname='John', lastname='Smith', 
send_information=True)
+        
self.assertEqual(json.loads(self.patch_requests.call_args[1]['data'])['send_information'],
 True)
+
     def test_user_custom_str(self):
         self.response.json.return_value = responses['user']['get']
         self.assertEqual(str(self.redmine.user.get(1)), 'John Smith')
@@ -1167,7 +1212,7 @@
         self.assertEqual(self.redmine.news.resource_class.redmine_version, 
'1.1')
 
     def test_news_get(self):
-        self.response.json.return_value = responses['news']['all']
+        self.response.json.return_value = responses['news']['get']
         news = self.redmine.news.get(1)
         self.assertEqual(news.id, 1)
         self.assertEqual(news.title, 'Foo')
@@ -1175,22 +1220,49 @@
     def test_news_all(self):
         self.response.json.return_value = responses['news']['all']
         news = self.redmine.news.all()
-        self.assertEqual(news[0].id, 1)
+        self.assertEqual(news[0].id, 2)
         self.assertEqual(news[0].title, 'Foo')
-        self.assertEqual(news[1].id, 2)
+        self.assertEqual(news[1].id, 1)
         self.assertEqual(news[1].title, 'Bar')
 
     def test_news_filter(self):
         self.response.json.return_value = responses['news']['filter']
         news = self.redmine.news.filter(project_id=1)
-        self.assertEqual(news[0].id, 1)
+        self.assertEqual(news[0].id, 2)
         self.assertEqual(news[0].title, 'Foo')
-        self.assertEqual(news[1].id, 2)
+        self.assertEqual(news[1].id, 1)
         self.assertEqual(news[1].title, 'Bar')
 
+    def test_news_create(self):
+        self.response.status_code = 201
+        self.response.json.return_value = responses['news']['get']
+        news = self.redmine.news.create(project_id=1, title='Foo')
+        self.assertEqual(news.title, 'Foo')
+
+    def test_news_create_empty_response(self):
+        self.set_patch_side_effect([
+            mock.Mock(status_code=204, history=[], content=''),
+            mock.Mock(status_code=201, history=[], **{'json.return_value': 
responses['news']['filter']})
+        ])
+        news = self.redmine.news.create(project_id=1, title='Foo')
+        self.assertEqual(news.title, 'Foo')
+
+    def test_news_delete(self):
+        self.response.json.return_value = responses['news']['get']
+        news = self.redmine.news.get(1)
+        self.response.content = ''
+        self.assertEqual(news.delete(), True)
+        self.assertEqual(self.redmine.news.delete(1), True)
+
+    def test_news_update(self):
+        self.response.json.return_value = responses['news']['get']
+        news = self.redmine.news.get(1)
+        news.title = 'Bar'
+        self.assertIsInstance(news.save(), resources.News)
+
     def test_news_url(self):
         self.response.json.return_value = responses['news']['filter']
-        self.assertEqual(self.redmine.news.filter(project_id=1)[0].url, 
'{0}/news/1'.format(self.url))
+        self.assertEqual(self.redmine.news.filter(project_id=1)[0].url, 
'{0}/news/2'.format(self.url))
 
     @mock.patch('redminelib.open', mock.mock_open(), create=True)
     def test_news_export(self):
@@ -1204,7 +1276,7 @@
 
     def test_news_repr(self):
         self.response.json.return_value = responses['news']['filter']
-        self.assertEqual(repr(self.redmine.news.filter(project_id=1)[0]), 
'<redminelib.resources.News #1 "Foo">')
+        self.assertEqual(repr(self.redmine.news.filter(project_id=1)[0]), 
'<redminelib.resources.News #2 "Foo">')
 
     def test_issue_status_version(self):
         
self.assertEqual(self.redmine.issue_status.resource_class.redmine_version, 
'1.3')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.2.1/tests/test_resultsets.py 
new/python-redmine-2.3.0/tests/test_resultsets.py
--- old/python-redmine-2.2.1/tests/test_resultsets.py   2018-04-29 
19:17:19.000000000 +0200
+++ new/python-redmine-2.3.0/tests/test_resultsets.py   2020-05-06 
19:25:41.000000000 +0200
@@ -178,6 +178,32 @@
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
         self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar'), 
'/foo/bar/issues.txt')
 
+    @mock.patch('redminelib.open', mock.mock_open(), create=True)
+    def test_export_with_all_columns(self):
+        self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns='all'), '/foo/bar/issues.txt')
+        self.redmine.ver = '3.3.0'
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns='all'), '/foo/bar/issues.txt')
+
+    @mock.patch('redminelib.open', mock.mock_open(), create=True)
+    def test_export_with_all_gui_columns(self):
+        self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns='all_gui'), '/foo/bar/issues.txt')
+        self.redmine.ver = '3.3.0'
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns='all_gui'), '/foo/bar/issues.txt')
+
+    @mock.patch('redminelib.open', mock.mock_open(), create=True)
+    def test_export_with_all_gui_extra_columns(self):
+        self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns=['all_gui']), '/foo/bar/issues.txt')
+        self.redmine.ver = '3.3.0'
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns=['all_gui']), '/foo/bar/issues.txt')
+
+    @mock.patch('redminelib.open', mock.mock_open(), create=True)
+    def test_export_with_custom_columns(self):
+        self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar', 
columns=['status']), '/foo/bar/issues.txt')
+
     def test_export_not_supported_exception(self):
         self.assertRaises(exceptions.ExportNotSupported, lambda: 
self.redmine.custom_field.all().export('pdf'))
 


Reply via email to