Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-python-redmine for 
openSUSE:Factory checked in at 2024-04-08 17:39:54
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-python-redmine (Old)
 and      /work/SRC/openSUSE:Factory/.python-python-redmine.new.1905 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-python-redmine"

Mon Apr  8 17:39:54 2024 rev:9 rq:1166107 version:2.5.0

Changes:
--------
--- 
/work/SRC/openSUSE:Factory/python-python-redmine/python-python-redmine.changes  
    2024-03-04 21:25:13.306582259 +0100
+++ 
/work/SRC/openSUSE:Factory/.python-python-redmine.new.1905/python-python-redmine.changes
    2024-04-08 17:52:09.761443408 +0200
@@ -1,0 +2,54 @@
+Sun Apr  7 19:14:48 UTC 2024 - Martin Hauke <mar...@gmx.de>
+
+- Update to version 2.5.0
+  Deprecations:
+  * Requests version required >= 2.31.0
+  New Features:
+  * Pro Edition: RedmineUP Products plugin support
+  * Issue copying (see docs for details)
+  Improvements:
+  * dir(resource) and list(resource) now also show properties of
+    an object.
+  * Support for issues_assigned and issues_authored relations in
+    User object
+  * Original filename will be used as a filename for all uploaded
+    files if a path was provided and filename wasn't set.
+  * Pro Edition: Added support for RedmineUP Contact avatar
+    add/update operations (see docs for details).
+  * Pro Edition: Added support for RedmineUP DealCategory create(),
+    update(), delete() operations (see docs for details).
+  * Pro Edition: RedmineUP CrmQuery resource now supports
+    invoices and expenses relation attributes.
+  * PerformanceWarning will be issued when Python-Redmine does some
+    unnecessary redirects before the actual request is made.
+  Changes:
+  * Backwards Incompatible: API key is now being sent in the
+    X-Redmine-API-Key header instead of the key GET parameter which
+    makes things more secure in case of a failed connection, but
+    it might created issues for servers that don't do custom
+    request header forwarding by default, so be sure to check your
+    web server before upgrading (Issue #328 and Issue #330).
+  * Backwards Incompatible: User all operation now really returns
+    all users, i.e. not only active, but locked, registered and
+    anonymous as well instead of only returning just active users
+    in previous versions due to the respect to Redmine's standard
+    behaviour.
+  Bugfixes:
+  * Tests were failing on Python 3.12 (Issue #332).
+  * Some closed Issues weren't converted to Resource objects
+    using redmine.search().
+  * Pro Edition: RedmineUP Invoice resource order attribute was
+    returned as a dict instead of being converted to Resource
+    object.
+  * Pro Edition: RedmineUP CrmQuery resource deals and contacts
+    relation attributes didn't work.
+  * Pro Edition: RedmineUP DealStatus resource deals relation
+    attribute didn't work.
+  Documentation:
+  * Mentioned support for author_id in Issue's resource filter
+    operation.
+- Drop not longer needed patches
+  * 328.patch
+  * support-python-312.patch
+
+-------------------------------------------------------------------

Old:
----
  328.patch
  python-redmine-2.4.0.tar.gz
  support-python-312.patch

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

BETA DEBUG BEGIN:
  Old:- Drop not longer needed patches
  * 328.patch
  * support-python-312.patch
  Old:  * 328.patch
  * support-python-312.patch
BETA DEBUG END:

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

Other differences:
------------------
++++++ python-python-redmine.spec ++++++
--- /var/tmp/diff_new_pack.6BO90v/_old  2024-04-08 17:52:11.449505712 +0200
+++ /var/tmp/diff_new_pack.6BO90v/_new  2024-04-08 17:52:11.473506597 +0200
@@ -17,24 +17,21 @@
 
 
 Name:           python-python-redmine
-Version:        2.4.0
+Version:        2.5.0
 Release:        0
 Summary:        Python library for the Redmine RESTful API
 License:        Apache-2.0
 URL:            https://python-redmine.com
 Source:         
https://files.pythonhosted.org/packages/source/p/python-redmine/python-redmine-%{version}.tar.gz
-Patch0:         https://github.com/maxtepkeev/python-redmine/pull/328.patch
-# PATCH-FIX-UPSTREAM gh#maxtepkeev/python-redmine#332
-Patch1:         support-python-312.patch
 BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module pytest-cov}
 BuildRequires:  %{python_module pytest}
-BuildRequires:  %{python_module requests >= 2.28.2}
+BuildRequires:  %{python_module requests >= 2.31.0}
 BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
-Requires:       python-requests >= 2.28.2
+Requires:       python-requests >= 2.31.0
 BuildArch:      noarch
 %python_subpackages
 

++++++ python-redmine-2.4.0.tar.gz -> python-redmine-2.5.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/CHANGELOG.rst 
new/python-redmine-2.5.0/CHANGELOG.rst
--- old/python-redmine-2.4.0/CHANGELOG.rst      2023-01-17 19:13:58.000000000 
+0100
+++ new/python-redmine-2.5.0/CHANGELOG.rst      2024-03-31 16:46:44.000000000 
+0200
@@ -1,6 +1,61 @@
 Changelog
 ---------
 
+2.5.0 (2024-03-31)
+++++++++++++++++++
+
+**Deprecations**:
+
+- Requests version required >= 2.31.0
+
+**New Features**:
+
+- *Pro Edition:* RedmineUP `Products plugin 
<https://www.redmineup.com/pages/plugins/products>`__ support
+- Issue copying (see `docs 
<https://python-redmine.com/resources/issue.html#copying>`__ for details)
+  (`Issue #203 <https://github.com/maxtepkeev/python-redmine/issues/203>`__)
+
+**Improvements**:
+
+- Migrated CI to GitHub Actions, also we now test not only on Linux, but on 
macOS and Windows as well
+- ``dir(resource)`` and ``list(resource)`` now also show properties of an 
object
+- Support for ``issues_assigned`` and ``issues_authored`` relations in User 
object
+  (`Issue #317 <https://github.com/maxtepkeev/python-redmine/issues/317>`__)
+- Original filename will be used as a filename for all uploaded files if a 
path was provided and filename wasn't set
+- *Pro Edition:* Added support for RedmineUP Contact avatar add/update 
operations
+  (see `docs 
<https://python-redmine.com/resources/contact.html#create-methods>`__ for 
details)
+- *Pro Edition:* Added support for RedmineUP DealCategory ``create()``, 
``update()``, ``delete()`` operations
+  (see `docs 
<https://python-redmine.com/resources/deal_category.html#create-methods>`__ for 
details)
+- *Pro Edition:* RedmineUP CrmQuery resource now supports ``invoices`` and 
``expenses`` relation attributes
+- ``PerformanceWarning`` will be issued when Python-Redmine does some 
unnecessary redirects before the actual
+  request is made
+
+**Changes**:
+
+- *Backwards Incompatible:* API key is now being sent in the X-Redmine-API-Key 
header instead of the key GET
+  parameter which makes things more secure in case of a failed connection, but 
it might created issues for servers
+  that don't do custom request header forwarding by default, so be sure to 
check your web server before upgrading
+  (`Issue #328 <https://github.com/maxtepkeev/python-redmine/issues/328>`__ and
+  `Issue #330 <https://github.com/maxtepkeev/python-redmine/issues/330>`__) 
(thanks to `Tom Misilo <https://github.com/misilot>`__
+  and `Ricardo Branco <https://github.com/ricardobranco777>`__)
+- *Backwards Incompatible:* User ``all`` operation now really returns all 
users, i.e. not only active, but locked,
+  registered and anonymous as well instead of only returning just active users 
in previous versions due to the
+  respect to Redmine's standard behaviour (`Issue #327 
<https://github.com/maxtepkeev/python-redmine/issues/327>`__)
+
+**Bugfixes**:
+
+- Tests were failing on Windows OS
+- Tests were failing on Python 3.12 (`Issue #332 
<https://github.com/maxtepkeev/python-redmine/pull/332>`__)
+  (thanks to `Michał Górny <https://github.com/mgorny>`__)
+- Some closed Issues weren't converted to Resource objects using 
``redmine.search()``
+- *Pro Edition:* RedmineUP Invoice resource ``order`` attribute was returned 
as a dict instead of being converted to
+  Resource object
+- *Pro Edition:* RedmineUP CrmQuery resource ``deals`` and ``contacts`` 
relation attributes didn't work
+- *Pro Edition:* RedmineUP DealStatus resource ``deals`` relation attribute 
didn't work
+
+**Documentation**:
+
+- Mentioned support for ``author_id`` in Issue's resource filter operation
+
 2.4.0 (2023-01-18)
 ++++++++++++++++++
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/LICENSE 
new/python-redmine-2.5.0/LICENSE
--- old/python-redmine-2.4.0/LICENSE    2023-01-17 19:56:47.000000000 +0100
+++ new/python-redmine-2.5.0/LICENSE    2024-03-31 15:31:59.000000000 +0200
@@ -1,4 +1,4 @@
-Copyright 2023 Maxim Tepkeev
+Copyright 2024 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.4.0/PKG-INFO 
new/python-redmine-2.5.0/PKG-INFO
--- old/python-redmine-2.4.0/PKG-INFO   2023-01-17 20:07:22.000000000 +0100
+++ new/python-redmine-2.5.0/PKG-INFO   2024-03-31 17:15:21.813374500 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-redmine
-Version: 2.4.0
+Version: 2.5.0
 Summary: Library for communicating with a Redmine project management 
application
 Home-page: https://github.com/maxtepkeev/python-redmine
 Author: Maxim Tepkeev
@@ -22,11 +22,13 @@
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Requires-Python: >=3.7, <4
 Description-Content-Type: text/x-rst
+Requires-Dist: requests>=2.31.0
 
 Python-Redmine
 ==============
@@ -34,8 +36,8 @@
 .. image:: https://badge.fury.io/py/python-redmine.svg
    :target: https://badge.fury.io/py/python-redmine
 
-.. image:: https://img.shields.io/travis/com/maxtepkeev/python-redmine/master
-   :target: https://app.travis-ci.com/maxtepkeev/python-redmine
+.. image:: 
https://img.shields.io/github/actions/workflow/status/maxtepkeev/python-redmine/tests.yml
+   :target: 
https://github.com/maxtepkeev/python-redmine/actions/workflows/tests.yml
 
 .. image:: 
https://img.shields.io/coverallsCoverage/github/maxtepkeev/python-redmine?branch=master
    :target: https://coveralls.io/github/maxtepkeev/python-redmine?branch=master
@@ -84,7 +86,7 @@
 
 * Supports 100% of Redmine API
 * Supports external Redmine plugins API
-* Supports Python 3.7 - 3.11 and PyPy3
+* Supports Python 3.7 - 3.12 and PyPy3
 * Supports different request engines
 * Extendable via custom resources and custom request engines
 * Extensively documented
@@ -133,6 +135,61 @@
 Changelog
 ---------
 
+2.5.0 (2024-03-31)
+++++++++++++++++++
+
+**Deprecations**:
+
+- Requests version required >= 2.31.0
+
+**New Features**:
+
+- *Pro Edition:* RedmineUP `Products plugin 
<https://www.redmineup.com/pages/plugins/products>`__ support
+- Issue copying (see `docs 
<https://python-redmine.com/resources/issue.html#copying>`__ for details)
+  (`Issue #203 <https://github.com/maxtepkeev/python-redmine/issues/203>`__)
+
+**Improvements**:
+
+- Migrated CI to GitHub Actions, also we now test not only on Linux, but on 
macOS and Windows as well
+- ``dir(resource)`` and ``list(resource)`` now also show properties of an 
object
+- Support for ``issues_assigned`` and ``issues_authored`` relations in User 
object
+  (`Issue #317 <https://github.com/maxtepkeev/python-redmine/issues/317>`__)
+- Original filename will be used as a filename for all uploaded files if a 
path was provided and filename wasn't set
+- *Pro Edition:* Added support for RedmineUP Contact avatar add/update 
operations
+  (see `docs 
<https://python-redmine.com/resources/contact.html#create-methods>`__ for 
details)
+- *Pro Edition:* Added support for RedmineUP DealCategory ``create()``, 
``update()``, ``delete()`` operations
+  (see `docs 
<https://python-redmine.com/resources/deal_category.html#create-methods>`__ for 
details)
+- *Pro Edition:* RedmineUP CrmQuery resource now supports ``invoices`` and 
``expenses`` relation attributes
+- ``PerformanceWarning`` will be issued when Python-Redmine does some 
unnecessary redirects before the actual
+  request is made
+
+**Changes**:
+
+- *Backwards Incompatible:* API key is now being sent in the X-Redmine-API-Key 
header instead of the key GET
+  parameter which makes things more secure in case of a failed connection, but 
it might created issues for servers
+  that don't do custom request header forwarding by default, so be sure to 
check your web server before upgrading
+  (`Issue #328 <https://github.com/maxtepkeev/python-redmine/issues/328>`__ and
+  `Issue #330 <https://github.com/maxtepkeev/python-redmine/issues/330>`__) 
(thanks to `Tom Misilo <https://github.com/misilot>`__
+  and `Ricardo Branco <https://github.com/ricardobranco777>`__)
+- *Backwards Incompatible:* User ``all`` operation now really returns all 
users, i.e. not only active, but locked,
+  registered and anonymous as well instead of only returning just active users 
in previous versions due to the
+  respect to Redmine's standard behaviour (`Issue #327 
<https://github.com/maxtepkeev/python-redmine/issues/327>`__)
+
+**Bugfixes**:
+
+- Tests were failing on Windows OS
+- Tests were failing on Python 3.12 (`Issue #332 
<https://github.com/maxtepkeev/python-redmine/pull/332>`__)
+  (thanks to `Michał Górny <https://github.com/mgorny>`__)
+- Some closed Issues weren't converted to Resource objects using 
``redmine.search()``
+- *Pro Edition:* RedmineUP Invoice resource ``order`` attribute was returned 
as a dict instead of being converted to
+  Resource object
+- *Pro Edition:* RedmineUP CrmQuery resource ``deals`` and ``contacts`` 
relation attributes didn't work
+- *Pro Edition:* RedmineUP DealStatus resource ``deals`` relation attribute 
didn't work
+
+**Documentation**:
+
+- Mentioned support for ``author_id`` in Issue's resource filter operation
+
 2.4.0 (2023-01-18)
 ++++++++++++++++++
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/README.rst 
new/python-redmine-2.5.0/README.rst
--- old/python-redmine-2.4.0/README.rst 2023-01-13 19:48:24.000000000 +0100
+++ new/python-redmine-2.5.0/README.rst 2024-03-02 16:55:16.000000000 +0100
@@ -4,8 +4,8 @@
 .. image:: https://badge.fury.io/py/python-redmine.svg
    :target: https://badge.fury.io/py/python-redmine
 
-.. image:: https://img.shields.io/travis/com/maxtepkeev/python-redmine/master
-   :target: https://app.travis-ci.com/maxtepkeev/python-redmine
+.. image:: 
https://img.shields.io/github/actions/workflow/status/maxtepkeev/python-redmine/tests.yml
+   :target: 
https://github.com/maxtepkeev/python-redmine/actions/workflows/tests.yml
 
 .. image:: 
https://img.shields.io/coverallsCoverage/github/maxtepkeev/python-redmine?branch=master
    :target: https://coveralls.io/github/maxtepkeev/python-redmine?branch=master
@@ -54,7 +54,7 @@
 
 * Supports 100% of Redmine API
 * Supports external Redmine plugins API
-* Supports Python 3.7 - 3.11 and PyPy3
+* Supports Python 3.7 - 3.12 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.4.0/python_redmine.egg-info/PKG-INFO 
new/python-redmine-2.5.0/python_redmine.egg-info/PKG-INFO
--- old/python-redmine-2.4.0/python_redmine.egg-info/PKG-INFO   2023-01-17 
20:07:22.000000000 +0100
+++ new/python-redmine-2.5.0/python_redmine.egg-info/PKG-INFO   2024-03-31 
17:15:21.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.1
 Name: python-redmine
-Version: 2.4.0
+Version: 2.5.0
 Summary: Library for communicating with a Redmine project management 
application
 Home-page: https://github.com/maxtepkeev/python-redmine
 Author: Maxim Tepkeev
@@ -22,11 +22,13 @@
 Classifier: Programming Language :: Python :: 3.9
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
+Classifier: Programming Language :: Python :: 3.12
 Classifier: Programming Language :: Python :: 3 :: Only
 Classifier: Programming Language :: Python :: Implementation :: CPython
 Classifier: Programming Language :: Python :: Implementation :: PyPy
 Requires-Python: >=3.7, <4
 Description-Content-Type: text/x-rst
+Requires-Dist: requests>=2.31.0
 
 Python-Redmine
 ==============
@@ -34,8 +36,8 @@
 .. image:: https://badge.fury.io/py/python-redmine.svg
    :target: https://badge.fury.io/py/python-redmine
 
-.. image:: https://img.shields.io/travis/com/maxtepkeev/python-redmine/master
-   :target: https://app.travis-ci.com/maxtepkeev/python-redmine
+.. image:: 
https://img.shields.io/github/actions/workflow/status/maxtepkeev/python-redmine/tests.yml
+   :target: 
https://github.com/maxtepkeev/python-redmine/actions/workflows/tests.yml
 
 .. image:: 
https://img.shields.io/coverallsCoverage/github/maxtepkeev/python-redmine?branch=master
    :target: https://coveralls.io/github/maxtepkeev/python-redmine?branch=master
@@ -84,7 +86,7 @@
 
 * Supports 100% of Redmine API
 * Supports external Redmine plugins API
-* Supports Python 3.7 - 3.11 and PyPy3
+* Supports Python 3.7 - 3.12 and PyPy3
 * Supports different request engines
 * Extendable via custom resources and custom request engines
 * Extensively documented
@@ -133,6 +135,61 @@
 Changelog
 ---------
 
+2.5.0 (2024-03-31)
+++++++++++++++++++
+
+**Deprecations**:
+
+- Requests version required >= 2.31.0
+
+**New Features**:
+
+- *Pro Edition:* RedmineUP `Products plugin 
<https://www.redmineup.com/pages/plugins/products>`__ support
+- Issue copying (see `docs 
<https://python-redmine.com/resources/issue.html#copying>`__ for details)
+  (`Issue #203 <https://github.com/maxtepkeev/python-redmine/issues/203>`__)
+
+**Improvements**:
+
+- Migrated CI to GitHub Actions, also we now test not only on Linux, but on 
macOS and Windows as well
+- ``dir(resource)`` and ``list(resource)`` now also show properties of an 
object
+- Support for ``issues_assigned`` and ``issues_authored`` relations in User 
object
+  (`Issue #317 <https://github.com/maxtepkeev/python-redmine/issues/317>`__)
+- Original filename will be used as a filename for all uploaded files if a 
path was provided and filename wasn't set
+- *Pro Edition:* Added support for RedmineUP Contact avatar add/update 
operations
+  (see `docs 
<https://python-redmine.com/resources/contact.html#create-methods>`__ for 
details)
+- *Pro Edition:* Added support for RedmineUP DealCategory ``create()``, 
``update()``, ``delete()`` operations
+  (see `docs 
<https://python-redmine.com/resources/deal_category.html#create-methods>`__ for 
details)
+- *Pro Edition:* RedmineUP CrmQuery resource now supports ``invoices`` and 
``expenses`` relation attributes
+- ``PerformanceWarning`` will be issued when Python-Redmine does some 
unnecessary redirects before the actual
+  request is made
+
+**Changes**:
+
+- *Backwards Incompatible:* API key is now being sent in the X-Redmine-API-Key 
header instead of the key GET
+  parameter which makes things more secure in case of a failed connection, but 
it might created issues for servers
+  that don't do custom request header forwarding by default, so be sure to 
check your web server before upgrading
+  (`Issue #328 <https://github.com/maxtepkeev/python-redmine/issues/328>`__ and
+  `Issue #330 <https://github.com/maxtepkeev/python-redmine/issues/330>`__) 
(thanks to `Tom Misilo <https://github.com/misilot>`__
+  and `Ricardo Branco <https://github.com/ricardobranco777>`__)
+- *Backwards Incompatible:* User ``all`` operation now really returns all 
users, i.e. not only active, but locked,
+  registered and anonymous as well instead of only returning just active users 
in previous versions due to the
+  respect to Redmine's standard behaviour (`Issue #327 
<https://github.com/maxtepkeev/python-redmine/issues/327>`__)
+
+**Bugfixes**:
+
+- Tests were failing on Windows OS
+- Tests were failing on Python 3.12 (`Issue #332 
<https://github.com/maxtepkeev/python-redmine/pull/332>`__)
+  (thanks to `Michał Górny <https://github.com/mgorny>`__)
+- Some closed Issues weren't converted to Resource objects using 
``redmine.search()``
+- *Pro Edition:* RedmineUP Invoice resource ``order`` attribute was returned 
as a dict instead of being converted to
+  Resource object
+- *Pro Edition:* RedmineUP CrmQuery resource ``deals`` and ``contacts`` 
relation attributes didn't work
+- *Pro Edition:* RedmineUP DealStatus resource ``deals`` relation attribute 
didn't work
+
+**Documentation**:
+
+- Mentioned support for ``author_id`` in Issue's resource filter operation
+
 2.4.0 (2023-01-18)
 ++++++++++++++++++
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.4.0/python_redmine.egg-info/requires.txt 
new/python-redmine-2.5.0/python_redmine.egg-info/requires.txt
--- old/python-redmine-2.4.0/python_redmine.egg-info/requires.txt       
2023-01-17 20:07:22.000000000 +0100
+++ new/python-redmine-2.5.0/python_redmine.egg-info/requires.txt       
2024-03-31 17:15:21.000000000 +0200
@@ -1 +1 @@
-requests>=2.28.2
+requests>=2.31.0
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/__init__.py 
new/python-redmine-2.5.0/redminelib/__init__.py
--- old/python-redmine-2.4.0/redminelib/__init__.py     2023-01-13 
19:48:24.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/__init__.py     2024-03-24 
13:47:47.000000000 +0100
@@ -109,10 +109,6 @@
         if self.ver is not None and self.ver < (1, 4, 0):
             raise exceptions.VersionMismatchError('File uploading')
 
-        url = f'{self.url}/uploads.json'
-        headers = {'Content-Type': 'application/octet-stream'}
-        params = {'filename': filename or ''}
-
         # There are 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
         # them implement a "close" method, that's why we check for it here. 
Also, we don't want to close the
@@ -127,8 +123,8 @@
             # We need to send bytes over the socket, so in case a file-like 
object contains a unicode
             # object underneath, we need to convert it to bytes, otherwise 
we'll get an exception
             if isinstance(c, str):
-                warnings.warn("File-like object contains unicode, hence an 
additional step is performed to convert "
-                              "its content to bytes, please consider switching 
to bytes to eliminate this warning",
+                warnings.warn('File-like object contains unicode, hence an 
additional step is performed to convert '
+                              'its content to bytes, please consider switching 
to bytes to eliminate this warning',
                               exceptions.PerformanceWarning)
                 f = io.BytesIO(f.read().encode('utf-8'))
 
@@ -138,9 +134,16 @@
             if not os.path.isfile(f) or os.path.getsize(f) == 0:
                 raise exceptions.NoFileError
 
+            if not filename:
+                filename = os.path.basename(f)
+
             stream = open(f, 'rb')
             close = True
 
+        url = f'{self.url}/uploads.json'
+        headers = {'Content-Type': 'application/octet-stream'}
+        params = {'filename': filename or ''}
+
         response = self.engine.request('post', url, params=params, 
data=stream, headers=headers)
 
         if close:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/engines/base.py 
new/python-redmine-2.5.0/redminelib/engines/base.py
--- old/python-redmine-2.4.0/redminelib/engines/base.py 2023-01-11 
16:52:11.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/engines/base.py 2024-03-03 
11:14:49.000000000 +0100
@@ -3,6 +3,7 @@
 """
 
 import json
+import warnings
 
 from .. import exceptions
 
@@ -24,7 +25,7 @@
         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', {}))
+        self.requests = dict(dict(headers={}, params={}), 
**options.get('requests', {}))
 
         if self.ignore_response:
             self.requests['stream'] = True
@@ -34,7 +35,7 @@
 
         # We would like to be authenticated by API key by default
         if options.get('key') is not None:
-            self.requests['params']['key'] = options['key']
+            self.requests['headers']['X-Redmine-API-Key'] = options['key']
         elif options.get('username') is not None and options.get('password') 
is not None:
             self.requests['auth'] = (options['username'], options['password'])
 
@@ -144,8 +145,17 @@
 
         if response.history:
             r = response.history[0]
-            if r.is_redirect and r.request.url.startswith('http://') and 
response.request.url.startswith('https://'):
-                raise exceptions.HTTPProtocolError
+
+            if 300 <= r.status_code <= 399:
+                url1, url2 = str(r.request.url), str(response.request.url)
+
+                if (url1[:5] == 'http:' and url2[:6] == 'https:') or (url1[:6] 
== 'https:' and url2[:5] == 'http:'):
+                    raise exceptions.HTTPProtocolError
+                else:
+                    warnings.warn('Redirect detected during request-response, 
normally there should be no redirects, '
+                                  'so please check your Redmine URL for things 
like prepending www which redirects to '
+                                  'a no www domain and vice versa or using an 
old domain which redirects to a new one',
+                                  exceptions.PerformanceWarning)
 
         status_code = response.status_code
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/exceptions.py 
new/python-redmine-2.5.0/redminelib/exceptions.py
--- old/python-redmine-2.4.0/redminelib/exceptions.py   2023-01-08 
19:20:13.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/exceptions.py   2024-03-02 
15:34:23.000000000 +0100
@@ -282,7 +282,7 @@
     Wrong HTTP protocol usage.
     """
     def __init__(self):
-        super().__init__('Redmine url should start with HTTPS and not with 
HTTP')
+        super().__init__('Protocol redirect detected, Redmine URL expects 
HTTPS, but code uses HTTP or vice versa')
 
 
 class TimezoneError(BaseRedmineError):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/managers/__init__.py 
new/python-redmine-2.5.0/redminelib/managers/__init__.py
--- old/python-redmine-2.4.0/redminelib/managers/__init__.py    2023-01-17 
19:56:47.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/managers/__init__.py    2024-03-31 
15:31:59.000000000 +0200
@@ -3,4 +3,4 @@
 """
 
 from .base import ResourceManager
-from .standard import ProjectManager, FileManager, WikiPageManager, 
UserManager, NewsManager
+from .standard import ProjectManager, IssueManager, FileManager, 
WikiPageManager, UserManager, NewsManager
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/managers/base.py 
new/python-redmine-2.5.0/redminelib/managers/base.py
--- old/python-redmine-2.4.0/redminelib/managers/base.py        2023-01-15 
13:44:13.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/managers/base.py        2024-03-31 
15:33:21.000000000 +0200
@@ -219,7 +219,7 @@
 
         :param dict request: Request data.
         """
-        return {self.resource_class.container_update: 
self.resource_class.bulk_decode(request, self)}
+        return {self.container: self.resource_class.bulk_decode(request, self)}
 
     def update(self, resource_id, **fields):
         """
@@ -246,6 +246,8 @@
             else:
                 raise exceptions.ValidationError(f'{e} argument is required')
 
+        
self.params.update(self.resource_class.query_update.formatter.used_kwargs)
+        self.container = self.resource_class.container_update
         url = self._construct_update_url(query_update)
         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)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/managers/standard.py 
new/python-redmine-2.5.0/redminelib/managers/standard.py
--- old/python-redmine-2.4.0/redminelib/managers/standard.py    2023-01-03 
14:35:51.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/managers/standard.py    2024-03-09 
11:50:16.000000000 +0100
@@ -18,6 +18,25 @@
         raise AttributeError(f"'{self.__class__.__name__}' object has no 
attribute '{attr}'")
 
 
+class IssueManager(ResourceManager):
+    def copy(self, issue_id, link_original=True, include=(), **fields):
+        fields['_copy'] = {'copy_from': issue_id}
+
+        if link_original:
+            fields['_copy']['link_copy'] = '1'
+
+        if include is not None:
+            for i in include or ('subtasks', 'attachments'):
+                fields['_copy'][f'copy_{i}'] = '1'
+
+        return self.create(**fields)
+
+    def _prepare_create_request(self, request):
+        request = super()._prepare_create_request(request)
+        request.update(request[self.container].pop('_copy', {}))
+        return request
+
+
 class FileManager(ResourceManager):
     def _process_create_response(self, request, response):
         if response is True:
@@ -45,6 +64,18 @@
     def _construct_get_url(self, path):
         return super()._construct_get_url(self._check_custom_url(path))
 
+    def all(self, **params):
+        resourceset = super().all(**params)
+
+        if self.redmine.ver is not None:  # 
https://www.redmine.org/issues/32090#note-6
+            if self.redmine.ver >= (5, 1, 2):
+                resourceset.manager.url = f'{resourceset.manager.url}*'
+            elif self.redmine.ver in ((5, 1, 0), (5, 1, 1)):
+                resourceset.manager.url = 
(f'{resourceset.manager.url[:-7]}f[]=status_id&'
+                                           
f'op[status_id]==&v[status_id][]=1&v[status_id][]=2&v[status_id][]=3')
+
+        return resourceset
+
     def _prepare_create_request(self, request):
         request = super()._prepare_create_request(request)
         request['send_information'] = 
request[self.container].pop('send_information', False)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/resources/base.py 
new/python-redmine-2.5.0/redminelib/resources/base.py
--- old/python-redmine-2.4.0/redminelib/resources/base.py       2023-01-17 
17:17:14.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/resources/base.py       2024-03-02 
15:39:12.000000000 +0100
@@ -16,9 +16,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().__new__(mcs, name, bases, attrs)
+        cls = super().__new__(mcs, name, bases, mcs.bulk_update_attrs(attrs))
+        mcs.bulk_update_cls_attrs(cls, attrs)
 
         if name.startswith('Base'):  # base classes shouldn't be added to the 
registry
             return cls
@@ -59,16 +58,36 @@
         return registry[name].setdefault('class', cls)
 
     @staticmethod
-    def update_query_strings(attrs):
+    def bulk_update_attrs(attrs):
         """
-        Updates all `query_*` string attributes to use ResourceQueryFormatter 
by default.
+        Updates attrs with specific features and/or actualizes their content 
before a class is created.
+
+        :param dict attrs: (required). Attributes to work with.
         """
-        for k, v in attrs.items():
-            if k.startswith('query_') and v is not None:
-                attrs[k] = utilities.ResourceQueryStr(v)
+        for attr, value in attrs.items():
+            # `query_*` class attributes should use ResourceQueryFormatter by 
default
+            if attr.startswith('query_') and value is not None:
+                attrs[attr] = utilities.ResourceQueryStr(value)
 
         return attrs
 
+    @classmethod
+    def bulk_update_cls_attrs(mcs, cls, attrs):
+        """
+        Updates attrs with specific features and/or actualizes their content 
after a class is created.
+
+        :param any cls: (required). Resource class.
+        :param dict attrs: (required). Attributes to work with.
+        """
+        properties = []
+
+        for attr, value in attrs.items():
+            # `_members` class attribute should also contain all public 
properties
+            if not attr.startswith('_') and isinstance(value, property):
+                properties.append(attr)
+
+        mcs.update_cls_attr(cls, '_members', properties)
+
     @staticmethod
     def update_cls_attr(cls, name, value):
         """
@@ -77,12 +96,12 @@
 
         :param any cls: (required). Resource class.
         :param string name: (required). Attribute name.
-        :param any value: (optional). Attribute value.
+        :param any value: (required). Attribute value.
         """
         attr = getattr(cls, name, None)
 
         if isinstance(attr, list):
-            value = list(attr) + list(value)
+            value = list(set().union(attr, value))
         elif isinstance(attr, dict):
             value = dict(attr, **value)
         else:
@@ -211,7 +230,9 @@
         """
         Sets the requested attribute.
         """
-        if attr in self._members or attr.startswith('_'):
+        custom_settable = [*self._single_attr_id_map, 
*self._multiple_attr_id_map]
+
+        if attr.startswith('_') or attr in self._members and attr not in 
custom_settable:
             return super().__setattr__(attr, value)
         elif attr in self._create_readonly and self.is_new():
             raise exceptions.ReadonlyAttrError
@@ -396,7 +417,8 @@
         if not self.is_new():
             self.pre_update()
             self.manager.update(self.internal_id, **self._changes)
-            self._decoded_attrs['updated_on'] = 
datetime.utcnow().strftime(self.manager.redmine.datetime_format)
+            self._decoded_attrs['updated_on'] = 
datetime.now(timezone.utc).strftime(
+                self.manager.redmine.datetime_format)
             self.post_update()
         else:
             self.pre_create()
@@ -481,13 +503,13 @@
         """
         Allows dir() to be called on a Resource object and shows Resource 
attributes.
         """
-        return list(self._decoded_attrs.keys())
+        return [*self._decoded_attrs, *self._members]
 
     def __iter__(self):
         """
         Provides a way to iterate through Resource attributes and its values.
         """
-        return iter(self._decoded_attrs.items())
+        return iter(dict(self._decoded_attrs, **{m: getattr(self, m, None) for 
m in self._members}).items())
 
     def __int__(self):
         """
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.4.0/redminelib/resources/standard.py 
new/python-redmine-2.5.0/redminelib/resources/standard.py
--- old/python-redmine-2.4.0/redminelib/resources/standard.py   2023-01-16 
11:12:59.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/resources/standard.py   2024-03-09 
12:38:06.000000000 +0100
@@ -78,8 +78,9 @@
     query_create = '/projects/{project_id}/issues.json'
     query_update = '/issues/{}.json'
     query_delete = '/issues/{}.json'
-    search_hints = ['issue', 'issue closed']
+    search_hints = ['issue', 'issue closed', 'issue-closed']
     extra_export_columns = ['description', 'last_notes']
+    manager_class = managers.IssueManager
 
     _repr = [['id', 'subject'], ['title'], ['id']]
     _includes = ['children', 'attachments', 'relations', 'changesets', 
'journals', 'watchers', 'allowed_statuses']
@@ -174,6 +175,12 @@
 
         return super().decode(attr, value, manager)
 
+    def copy(self, link_original=True, include=(), **fields):
+        if 'project_id' not in fields and not self.is_new():
+            fields['project_id'] = self._decoded_attrs['project']['id']
+
+        return self.manager.copy(self.internal_id, 
link_original=link_original, include=include, **fields)
+
 
 class TimeEntry(BaseResource):
     redmine_version = (1, 1, 0)
@@ -402,7 +409,7 @@
     container_create = 'user'
     container_update = 'user'
     query_all_export = '/users.{format}'
-    query_all = '/users.json'
+    query_all = '/users.json?status='
     query_one = '/users/{}.json'
     query_filter = '/users.json'
     query_create = '/users.json'
@@ -412,7 +419,7 @@
 
     _repr = [['id', 'firstname', 'lastname'], ['id', 'name']]
     _includes = ['memberships', 'groups']
-    _relations = ['issues', 'time_entries']
+    _relations = ['issues', 'issues_assigned', 'issues_authored', 
'time_entries']
     _relations_name = 'assigned_to'
     _unconvertible = ['status']
     _create_readonly = BaseResource._create_readonly + ['api_key', 
'last_login_on']
@@ -422,12 +429,18 @@
         'groups': 'Group',
         'memberships': 'ProjectMembership',
         'issues': 'Issue',
+        'issues_assigned': 'Issue',
+        'issues_authored': 'Issue',
         'time_entries': 'TimeEntry',
     }
 
     def __getattr__(self, attr):
-        if attr == 'time_entries' and attr not in self._encoded_attrs:
-            self._relations_name = 'user'
+        if attr in self._relations and attr not in self._encoded_attrs:
+            if attr == 'issues_authored':
+                self._relations_name = 'author'
+            elif attr == 'time_entries':
+                self._relations_name = 'user'
+
             value = super().__getattr__(attr)
             self._relations_name = 'assigned_to'
             return value
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/redminelib/version.py 
new/python-redmine-2.5.0/redminelib/version.py
--- old/python-redmine-2.4.0/redminelib/version.py      2023-01-17 
19:13:58.000000000 +0100
+++ new/python-redmine-2.5.0/redminelib/version.py      2024-03-31 
16:46:44.000000000 +0200
@@ -1 +1 @@
-__version__ = '2.4.0'
+__version__ = '2.5.0'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/setup.py 
new/python-redmine-2.5.0/setup.py
--- old/python-redmine-2.4.0/setup.py   2023-01-17 19:24:30.000000000 +0100
+++ new/python-redmine-2.5.0/setup.py   2024-03-02 21:10:57.000000000 +0100
@@ -18,7 +18,7 @@
     long_description=open('README.rst').read() + '\n\n' + 
open('CHANGELOG.rst').read(),
     keywords='redmine redmineup redminecrm redminelib easyredmine',
     python_requires='>=3.7, <4',
-    install_requires=['requests>=2.28.2'],
+    install_requires=['requests>=2.31.0'],
     zip_safe=False,
     classifiers=[
         'Development Status :: 5 - Production/Stable',
@@ -35,6 +35,7 @@
         'Programming Language :: Python :: 3.9',
         'Programming Language :: Python :: 3.10',
         'Programming Language :: Python :: 3.11',
+        'Programming Language :: Python :: 3.12',
         'Programming Language :: Python :: 3 :: Only',
         'Programming Language :: Python :: Implementation :: CPython',
         'Programming Language :: Python :: Implementation :: PyPy'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/tests/__init__.py 
new/python-redmine-2.5.0/tests/__init__.py
--- old/python-redmine-2.4.0/tests/__init__.py  2022-12-25 20:47:30.000000000 
+0100
+++ new/python-redmine-2.5.0/tests/__init__.py  2024-03-02 15:34:23.000000000 
+0100
@@ -4,13 +4,13 @@
 
 
 class BaseRedmineTestCase(TestCase):
-    url = 'http://foo.bar'
+    url = 'https://foo.bar'
     patch_prefix = 'patch'
     patch_targets = {'requests': 
'redminelib.engines.sync.requests.Session.request'}
 
     def setUp(self):
         self.redmine = Redmine(self.url)
-        self.response = mock.Mock(status_code=200, history=[])
+        self.response = mock.Mock(**{'status_code': 200, 'history': [], 
'request.url': self.url})
 
         for target, path in self.patch_targets.items():
             setattr(self, f'{self.patch_prefix}_{target}', mock.patch(path, 
return_value=self.response).start())
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/tests/test_engines.py 
new/python-redmine-2.5.0/tests/test_engines.py
--- old/python-redmine-2.4.0/tests/test_engines.py      2020-05-19 
16:12:07.000000000 +0200
+++ new/python-redmine-2.5.0/tests/test_engines.py      2024-03-03 
11:18:27.000000000 +0100
@@ -1,3 +1,5 @@
+import warnings
+
 from . import mock, BaseRedmineTestCase, Redmine
 
 from redminelib import engines, exceptions
@@ -6,7 +8,7 @@
 class BaseEngineTestCase(BaseRedmineTestCase):
     def test_engine_init(self):
         redmine = Redmine(self.url, key='123', impersonate='jsmith', 
requests={'foo': 'bar'})
-        self.assertEqual(redmine.engine.requests['params']['key'], '123')
+        
self.assertEqual(redmine.engine.requests['headers']['X-Redmine-API-Key'], '123')
         
self.assertEqual(redmine.engine.requests['headers']['X-Redmine-Switch-User'], 
'jsmith')
         self.assertEqual(redmine.engine.requests['foo'], 'bar')
         redmine = Redmine(self.url, username='john', password='qwerty')
@@ -91,10 +93,17 @@
         self.assertRaises(exceptions.UnknownError, lambda: 
self.redmine.engine.request('get', self.url))
 
     def test_http_protocol_exception(self):
-        self.response.history = [mock.Mock()]
-        self.redmine.url = 'http://foo.bar'
+        self.response.history = [mock.Mock(**{'status_code': 301, 
'request.url': 'http://foo.bar'})]
         self.assertRaises(exceptions.HTTPProtocolError, lambda: 
self.redmine.engine.request('get', self.url))
 
+    def test_redirect_warning(self):
+        self.response.history = [mock.Mock(**{'status_code': 301, 
'request.url': 'https://www.foo.bar'})]
+        with warnings.catch_warnings(record=True) as w:
+            warnings.simplefilter('always')
+            self.redmine.engine.request('get', self.url)
+            self.assertEqual(len(w), 1)
+            self.assertIs(w[0].category, exceptions.PerformanceWarning)
+
     def test_engine_is_picklable(self):
         import pickle
         self.redmine.engine.requests['params']['key'] = '123'
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/tests/test_managers.py 
new/python-redmine-2.5.0/tests/test_managers.py
--- old/python-redmine-2.4.0/tests/test_managers.py     2023-01-16 
16:50:28.000000000 +0100
+++ new/python-redmine-2.5.0/tests/test_managers.py     2024-03-02 
15:34:23.000000000 +0100
@@ -12,7 +12,7 @@
 
 class ResourceManagerTestCase(BaseRedmineTestCase):
     def test_has_custom_repr(self):
-        self.assertEqual(repr(self.redmine.issue), 
'<redminelib.managers.ResourceManager object for Issue resource>')
+        self.assertEqual(repr(self.redmine.query), 
'<redminelib.managers.ResourceManager object for Query resource>')
 
     def test_supports_additional_resources(self):
         self.assertIsInstance(self.redmine.foo_resource, 
managers.ResourceManager)
@@ -125,7 +125,7 @@
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter('always')
             issue = self.redmine.issue.create(project_id=1, subject='Foo', 
uploads=[{'path': stream}])
-            self.assertEquals(len(w), 1)
+            self.assertEqual(len(w), 1)
             self.assertIs(w[0].category, exceptions.PerformanceWarning)
         self.assertEqual(issue.project_id, 1)
         self.assertEqual(issue.subject, 'Foo')
@@ -166,7 +166,7 @@
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter('always')
             self.assertEqual(self.redmine.issue.update(1, subject='Bar', 
uploads=[{'path': stream}]), True)
-            self.assertEquals(len(w), 1)
+            self.assertEqual(len(w), 1)
             self.assertIs(w[0].category, exceptions.PerformanceWarning)
 
     def test_update_resource_returns_none(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/tests/test_redmine.py 
new/python-redmine-2.5.0/tests/test_redmine.py
--- old/python-redmine-2.4.0/tests/test_redmine.py      2023-01-05 
19:00:33.000000000 +0100
+++ new/python-redmine-2.5.0/tests/test_redmine.py      2024-03-03 
11:20:32.000000000 +0100
@@ -41,8 +41,8 @@
 
     def test_session_key(self):
         with self.redmine.session(key='opa'):
-            self.assertEqual(self.redmine.engine.requests['params']['key'], 
'opa')
-        self.assertRaises(KeyError, lambda: 
self.redmine.engine.requests['params']['key'])
+            
self.assertEqual(self.redmine.engine.requests['headers']['X-Redmine-API-Key'], 
'opa')
+        self.assertRaises(KeyError, lambda: 
self.redmine.engine.requests['headers']['X-Redmine-API-Key'])
 
     def test_session_username_password(self):
         with self.redmine.session(username='john', password='smith'):
@@ -53,7 +53,8 @@
         self.redmine.engine.requests['cert'] = ('bar', 'baz')
         requests = {'verify': False, 'timeout': 2, 'cert': ('foo', 'bar'), 
'params': {'foo': 'bar'}}
         with self.redmine.session(key='secret', requests=requests):
-            self.assertEqual(self.redmine.engine.requests['params'], 
dict(key='secret', **requests['params']))
+            self.assertEqual(self.redmine.engine.requests['headers'], 
{'X-Redmine-API-Key': 'secret'})
+            self.assertEqual(self.redmine.engine.requests['params'], 
requests['params'])
             self.assertEqual(self.redmine.engine.requests['verify'], 
requests['verify'])
             self.assertEqual(self.redmine.engine.requests['timeout'], 
requests['timeout'])
             self.assertEqual(self.redmine.engine.requests['cert'], 
requests['cert'])
@@ -77,23 +78,23 @@
         with warnings.catch_warnings(record=True) as w:
             warnings.simplefilter('always')
             
self.assertEqual(self.redmine.upload(StringIO(b'\xcf\x86oo'.decode('utf-8')))['token'],
 '456789')
-            self.assertEquals(len(w), 1)
+            self.assertEqual(len(w), 1)
             self.assertIs(w[0].category, exceptions.PerformanceWarning)
 
     @mock.patch('redminelib.open', mock.mock_open(), create=True)
     def test_successful_file_download(self):
         self.response.status_code = 200
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.download('http://foo/bar.txt', 
'/some/path'), '/some/path/bar.txt')
+        self.assertEqual(self.redmine.download(f'{self.url}/bar.txt', 
'/some/path/'), '/some/path/bar.txt')
 
     def test_successful_in_memory_file_download(self):
         self.response.status_code = 200
         self.response.iter_content = lambda: (str(num) for num in range(0, 5))
-        
self.assertEqual(''.join(self.redmine.download('http://foo/bar.txt').iter_content()),
 '01234')
+        
self.assertEqual(''.join(self.redmine.download(f'{self.url}/bar.txt').iter_content()),
 '01234')
 
     def test_file_url_exception(self):
         self.response.status_code = 200
-        self.assertRaises(exceptions.FileUrlError, lambda: 
self.redmine.download('http://bad_url', '/some/path'))
+        self.assertRaises(exceptions.FileUrlError, lambda: 
self.redmine.download('https://bad_url', '/some/path'))
 
     def test_file_upload_no_file_exception(self):
         self.assertRaises(exceptions.NoFileError, lambda: 
self.redmine.upload('foo',))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' 
old/python-redmine-2.4.0/tests/test_resources_standard.py 
new/python-redmine-2.5.0/tests/test_resources_standard.py
--- old/python-redmine-2.4.0/tests/test_resources_standard.py   2023-01-17 
18:35:01.000000000 +0100
+++ new/python-redmine-2.5.0/tests/test_resources_standard.py   2024-03-09 
12:07:39.000000000 +0100
@@ -1,7 +1,7 @@
 from . import mock, BaseRedmineTestCase
 from .responses import responses
 
-from redminelib import resources, resultsets, exceptions
+from redminelib import resources, managers, resultsets, exceptions
 
 
 class StandardResourcesTestCase(BaseRedmineTestCase):
@@ -25,7 +25,7 @@
     def test_export(self):
         self.response.json.return_value = responses['issue']['get']
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.issue.get(1).export('txt', '/foo/bar'), 
'/foo/bar/1.txt')
+        self.assertEqual(self.redmine.issue.get(1).export('txt', '/foo/bar/'), 
'/foo/bar/1.txt')
 
     def test_export_not_supported_exception(self):
         self.response.json.return_value = responses['attachment']['get']
@@ -183,12 +183,21 @@
         self.assertIn('subject', attributes)
         self.assertIn('relations', attributes)
         self.assertIn('time_entries', attributes)
+        self.assertIn('children', attributes)
+        self.assertIn('attachments', attributes)
+        self.assertIn('manager', attributes)
+        self.assertIn('url', attributes)
+        self.assertIn('internal_id', attributes)
 
     def test_supports_iteration(self):
         self.response.json.return_value = responses['project']['get']
-        project = list(self.redmine.project.get(1))
+        p = self.redmine.project.get(1)
+        project = list(p)
         self.assertIn(('name', 'Foo'), project)
         self.assertIn(('id', 1), project)
+        self.assertIn(('manager', p.manager), project)
+        self.assertIn(('url', f'{self.url}/projects/foo'), project)
+        self.assertIn(('internal_id', 1), project)
 
     def test_setting_custom_field_raises_exception_if_not_list_of_dicts(self):
         self.response.json.return_value = {'project': {'name': 'Foo', 'id': 1, 
'custom_fields': [{'id': 1}]}}
@@ -288,7 +297,7 @@
         self.response.json.return_value = {
             'project': {'name': 'Foo', 'id': 1, 'custom_fields': [{'id': 1, 
'value': 'foo'}]}}
         project = self.redmine.project.get(1)
-        project.homepage = 'http://foo.bar'
+        project.homepage = self.url
         project.parent_id = 3
         project.custom_fields = [{'id': 1, 'value': 'bar'}]
         self.assertIsInstance(project.save(), resources.Project)
@@ -345,7 +354,7 @@
     def test_project_export(self):
         self.response.json.return_value = responses['project']['all']
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.project.all().export('txt', '/foo/bar'), 
'/foo/bar/projects.txt')
+        self.assertEqual(self.redmine.project.all().export('txt', 
'/foo/bar/'), '/foo/bar/projects.txt')
 
     def test_project_parent_converts_to_resource(self):
         self.response.json.return_value = {'project': {'name': 'Foo', 'id': 1, 
'parent': {'id': 2}}}
@@ -363,6 +372,18 @@
         self.assertIsInstance(project.default_assignee, resources.User)
         self.assertEqual(project.default_assignee.id, 4)
 
+    def test_project_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['project']['get']
+        project = self.redmine.project.get(1)
+        project.parent_id = 1
+        self.assertEqual(project._decoded_attrs['parent'], {'id': 1})
+
+    def test_project_sets_attrs_from_multiple_attr_id_map(self):
+        self.response.json.return_value = responses['project']['get']
+        project = self.redmine.project.get(1)
+        project.tracker_ids = [1, 2]
+        self.assertEqual(project._decoded_attrs['trackers'], [{'id': 1}, 
{'id': 2}])
+
     def test_project_supports_close_reopen_archive_unarchive(self):
         self.response.json.return_value = responses['project']['get']
         project = self.redmine.project.get(1)
@@ -428,6 +449,32 @@
         self.assertIsInstance(issue.save(), resources.Issue)
         self.assertEqual(issue.custom_fields[0].value, 'bar')
 
+    def test_issue_copy(self):
+        import json
+        self.response.status_code = 201
+        self.response.json.return_value = {'issue': {'subject': 'Foo', 'id': 
1, 'project': {'id': 1}}}
+        self.redmine.issue.get(1).copy()
+        request = json.loads(self.patch_requests.call_args[1]['data'])
+        self.assertEqual(request['copy_from'], 1)
+        self.assertEqual(request['link_copy'], '1')
+        self.assertEqual(request['copy_subtasks'], '1')
+        self.assertEqual(request['copy_attachments'], '1')
+
+    def test_issue_copy_via_manager(self):
+        import json
+        self.response.status_code = 201
+        self.response.json.return_value = responses['issue']['get']
+        self.redmine.issue.copy(1, project_id=1, link_original=False, 
include=None)
+        request = json.loads(self.patch_requests.call_args[1]['data'])
+        self.assertEqual(request['copy_from'], 1)
+        self.assertNotIn('link_copy', request)
+        self.assertNotIn('copy_subtasks', request)
+        self.assertNotIn('copy_attachments', request)
+
+    def test_issue_custom_manager(self):
+        self.assertEqual(repr(self.redmine.issue), 
'<redminelib.managers.IssueManager object for Issue resource>')
+        self.assertIsInstance(self.redmine.issue, managers.IssueManager)
+
     def test_issue_relations(self):
         self.response.json.return_value = responses['issue']['get']
         issue = self.redmine.issue.get(1)
@@ -561,9 +608,9 @@
     def test_issue_export(self):
         self.response.json.return_value = responses['issue']['all']
         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')
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar/'), 
'/foo/bar/issues.txt')
         self.response.json.return_value = responses['issue']['get']
-        self.assertEqual(self.redmine.issue.get(1).export('txt', '/foo/bar'), 
'/foo/bar/1.txt')
+        self.assertEqual(self.redmine.issue.get(1).export('txt', '/foo/bar/'), 
'/foo/bar/1.txt')
 
     def test_issue_parent_converts_to_resource(self):
         self.response.json.return_value = {'issue': {'subject': 'Foo', 'id': 
1, 'parent': {'id': 2}}}
@@ -599,6 +646,32 @@
         self.assertIsInstance(issue.fixed_version, resources.Version)
         self.assertEqual(issue.fixed_version.id, 1)
 
+    def test_issue_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['issue']['get']
+        issue = self.redmine.issue.get(1)
+        issue.project_id = 1
+        issue.tracker_id = 1
+        issue.status_id = 1
+        issue.priority_id = 1
+        issue.category_id = 1
+        issue.fixed_version_id = 1
+        issue.assigned_to_id = 1
+        issue.parent_issue_id = 1
+        self.assertEqual(issue._decoded_attrs['project'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['tracker'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['status'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['priority'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['category'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['fixed_version'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['assigned_to'], {'id': 1})
+        self.assertEqual(issue._decoded_attrs['parent'], {'id': 1})
+
+    def test_issue_sets_attrs_from_multiple_attr_id_map(self):
+        self.response.json.return_value = responses['issue']['get']
+        issue = self.redmine.issue.get(1)
+        issue.watcher_user_ids = [1, 2]
+        self.assertEqual(issue._decoded_attrs['watchers'], [{'id': 1}, {'id': 
2}])
+
     def test_time_entry_version(self):
         
self.assertEqual(self.redmine.time_entry.resource_class.redmine_version, (1, 1, 
0))
 
@@ -675,7 +748,7 @@
     def test_time_entry_export(self):
         self.response.json.return_value = responses['time_entry']['all']
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.time_entry.all().export('txt', 
'/foo/bar'), '/foo/bar/time_entries.txt')
+        self.assertEqual(self.redmine.time_entry.all().export('txt', 
'/foo/bar/'), '/foo/bar/time_entries.txt')
 
     def test_time_entry_resource_map_converts_to_resource(self):
         self.response.json.return_value = responses['time_entry']['get']
@@ -693,6 +766,16 @@
         self.assertIsInstance(time_entry.activity, resources.Enumeration)
         self.assertEqual(time_entry.activity.id, 1)
 
+    def test_time_entry_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['time_entry']['get']
+        time_entry = self.redmine.time_entry.get(1)
+        time_entry.project_id = 1
+        time_entry.issue_id = 1
+        time_entry.activity_id = 1
+        self.assertEqual(time_entry._decoded_attrs['project'], {'id': 1})
+        self.assertEqual(time_entry._decoded_attrs['issue'], {'id': 1})
+        self.assertEqual(time_entry._decoded_attrs['activity'], {'id': 1})
+
     def test_enumeration_version(self):
         
self.assertEqual(self.redmine.enumeration.resource_class.redmine_version, (2, 
2, 0))
 
@@ -766,10 +849,10 @@
     @mock.patch('redminelib.open', mock.mock_open(), create=True)
     def test_attachment_download(self):
         response = responses['attachment']['get']
-        response['attachment']['content_url'] = 'http://foo/bar.txt'
+        response['attachment']['content_url'] = f'{self.url}/bar.txt'
         self.response.json.return_value = response
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        
self.assertEqual(self.redmine.attachment.get(1).download('/some/path'), 
'/some/path/bar.txt')
+        
self.assertEqual(self.redmine.attachment.get(1).download('/some/path/'), 
'/some/path/bar.txt')
 
     def test_attachment_resource_map_converts_to_resource(self):
         self.response.json.return_value = responses['attachment']['get']
@@ -847,10 +930,10 @@
     @mock.patch('redminelib.open', mock.mock_open(), create=True)
     def test_file_download(self):
         response = responses['attachment']['get']
-        response['attachment']['content_url'] = 'http://foo/bar.txt'
+        response['attachment']['content_url'] = f'{self.url}/bar.txt'
         self.response.json.return_value = response
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.file.get(1).download('/some/path'), 
'/some/path/bar.txt')
+        self.assertEqual(self.redmine.file.get(1).download('/some/path/'), 
'/some/path/bar.txt')
 
     def test_file_resource_map_converts_to_resource(self):
         self.response.json.return_value = responses['attachment']['get']
@@ -876,7 +959,7 @@
         wiki_page = self.redmine.wiki_page.get('Foo%Bar', project_id=1)
         self.assertEqual(self.patch_requests.call_args[0][1], 
f'{self.url}/projects/1/wiki/Foo%25Bar.json')
         self.assertEqual(wiki_page.title, 'Foo%Bar')
-        self.assertEqual(wiki_page.url, 
'http://foo.bar/projects/1/wiki/Foo%25Bar')
+        self.assertEqual(wiki_page.url, 
f'{self.url}/projects/1/wiki/Foo%25Bar')
 
     def test_wiki_page_filter(self):
         self.response.json.return_value = responses['wiki_page']['filter']
@@ -965,7 +1048,7 @@
     def test_wiki_page_export(self):
         self.response.json.return_value = responses['wiki_page']['get']
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.wiki_page.get('Foo', 
project_id='Foo').export('txt', '/foo'), '/foo/Foo.txt')
+        self.assertEqual(self.redmine.wiki_page.get('Foo', 
project_id='Foo').export('txt', '/foo/'), '/foo/Foo.txt')
 
     def test_wiki_page_parent_converts_to_resource(self):
         self.response.json.return_value = {'wiki_page': {'title': 'Foo', 
'project_id': 1, 'parent': {'title': 'Bar'}}}
@@ -980,6 +1063,12 @@
         self.assertIsInstance(wiki_page.author, resources.User)
         self.assertEqual(wiki_page.author.firstname, 'John')
 
+    def test_wiki_page_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['wiki_page']['get']
+        wiki_page = self.redmine.wiki_page.get(1, project_id=1)
+        wiki_page.project_id = 1
+        self.assertEqual(wiki_page._decoded_attrs['project'], {'id': 1})
+
     def test_project_membership_version(self):
         
self.assertEqual(self.redmine.project_membership.resource_class.redmine_version,
 (1, 4, 0))
 
@@ -1046,6 +1135,20 @@
         self.assertIsInstance(membership.group, resources.Group)
         self.assertEqual(membership.group.id, 1)
 
+    def test_project_membership_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = 
responses['project_membership']['get']
+        membership = self.redmine.project_membership.get(1)
+        membership.project_id = 1
+        membership.user_id = 1
+        self.assertEqual(membership._decoded_attrs['project'], {'id': 1})
+        self.assertEqual(membership._decoded_attrs['user'], {'id': 1})
+
+    def test_project_membership_sets_attrs_from_multiple_attr_id_map(self):
+        self.response.json.return_value = 
responses['project_membership']['get']
+        membership = self.redmine.project_membership.get(1)
+        membership.role_ids = [1, 2]
+        self.assertEqual(membership._decoded_attrs['roles'], [{'id': 1}, 
{'id': 2}])
+
     def test_issue_category_version(self):
         
self.assertEqual(self.redmine.issue_category.resource_class.redmine_version, 
(1, 3, 0))
 
@@ -1102,6 +1205,14 @@
         self.assertIsInstance(category.assigned_to, resources.User)
         self.assertEqual(category.assigned_to.firstname, 'John')
 
+    def test_issue_category_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['issue_category']['get']
+        category = self.redmine.issue_category.get(1)
+        category.project_id = 1
+        category.assigned_to_id = 1
+        self.assertEqual(category._decoded_attrs['project'], {'id': 1})
+        self.assertEqual(category._decoded_attrs['assigned_to'], {'id': 1})
+
     def test_issue_relation_version(self):
         
self.assertEqual(self.redmine.issue_relation.resource_class.redmine_version, 
(1, 3, 0))
 
@@ -1147,6 +1258,12 @@
         self.response.json.return_value = responses['issue_relation']['get']
         self.assertEqual(self.redmine.issue_relation.get(1).url, 
f'{self.url}/relations/1')
 
+    def test_issue_relation_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['issue_relation']['get']
+        relation = self.redmine.issue_relation.get(1)
+        relation.issue_id = 1
+        self.assertEqual(relation._decoded_attrs['issue'], {'id': 1})
+
     def test_version_version(self):
         self.assertEqual(self.redmine.version.resource_class.redmine_version, 
(1, 3, 0))
 
@@ -1205,6 +1322,12 @@
         self.assertIsInstance(version.project, resources.Project)
         self.assertEqual(version.project.identifier, 'foo')
 
+    def test_version_relation_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['version']['get']
+        version = self.redmine.version.get(1)
+        version.project_id = 1
+        self.assertEqual(version._decoded_attrs['project'], {'id': 1})
+
     def test_user_version(self):
         self.assertEqual(self.redmine.user.resource_class.redmine_version, (1, 
1, 0))
 
@@ -1228,6 +1351,15 @@
         self.assertEqual(users[1].id, 2)
         self.assertEqual(users[1].firstname, 'Jack')
 
+    def test_user_all_url_variations(self):
+        self.redmine.ver = (5, 0, 0)
+        self.assertEqual(self.redmine.user.all().manager.url, 
f'{self.url}/users.json?status=')
+        self.redmine.ver = (5, 1, 0)
+        self.assertEqual(self.redmine.user.all().manager.url, 
f'{self.url}/users.json?f[]=status_id&'
+                                            
f'op[status_id]==&v[status_id][]=1&v[status_id][]=2&v[status_id][]=3')
+        self.redmine.ver = (6, 0, 0)
+        self.assertEqual(self.redmine.user.all().manager.url, 
f'{self.url}/users.json?status=*')
+
     def test_user_filter(self):
         self.response.json.return_value = responses['user']['filter']
         users = self.redmine.user.filter(status_id=2)
@@ -1286,6 +1418,8 @@
         self.response.json.return_value = responses['user']['get']
         user = self.redmine.user.get(1)
         self.assertIsInstance(user.issues, resultsets.ResourceSet)
+        self.assertIsInstance(user.issues_assigned, resultsets.ResourceSet)
+        self.assertIsInstance(user.issues_authored, resultsets.ResourceSet)
         self.assertIsInstance(user.time_entries, resultsets.ResourceSet)
 
     def test_user_includes(self):
@@ -1314,6 +1448,12 @@
         self.response.json.return_value = responses['user']['get']
         self.assertEqual(self.redmine.user.get(1).url, f'{self.url}/users/1')
 
+    @mock.patch('redminelib.open', mock.mock_open(), create=True)
+    def test_user_export(self):
+        self.response.json.return_value = responses['user']['all']
+        self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
+        self.assertEqual(self.redmine.user.all().export('txt', '/foo/bar/'), 
'/foo/bar/users.txt')
+
     def test_group_version(self):
         self.assertEqual(self.redmine.group.resource_class.redmine_version, 
(2, 1, 0))
 
@@ -1383,6 +1523,12 @@
         self.response.json.return_value = responses['group']['get']
         self.assertEqual(self.redmine.group.get(1).url, f'{self.url}/groups/1')
 
+    def test_group_sets_attrs_from_multiple_attr_id_map(self):
+        self.response.json.return_value = responses['group']['get']
+        group = self.redmine.group.get(1)
+        group.user_ids = [1, 2]
+        self.assertEqual(group._decoded_attrs['users'], [{'id': 1}, {'id': 2}])
+
     def test_role_version(self):
         self.assertEqual(self.redmine.role.resource_class.redmine_version, (1, 
4, 0))
 
@@ -1464,7 +1610,7 @@
     def test_news_export(self):
         self.response.json.return_value = responses['news']['all']
         self.response.iter_content = lambda chunk_size: (str(num) for num in 
range(0, 5))
-        self.assertEqual(self.redmine.news.all().export('txt', '/foo/bar'), 
'/foo/bar/news.txt')
+        self.assertEqual(self.redmine.news.all().export('txt', '/foo/bar/'), 
'/foo/bar/news.txt')
 
     def test_news_str(self):
         self.response.json.return_value = responses['news']['filter']
@@ -1495,6 +1641,12 @@
         self.response.json.return_value = response_includes
         self.assertIsInstance(news.comments, list)
 
+    def test_news_sets_attrs_from_single_attr_id_map(self):
+        self.response.json.return_value = responses['news']['get']
+        news = self.redmine.news.get(1)
+        news.project_id = 1
+        self.assertEqual(news._decoded_attrs['project'], {'id': 1})
+
     def test_issue_status_version(self):
         
self.assertEqual(self.redmine.issue_status.resource_class.redmine_version, (1, 
3, 0))
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/python-redmine-2.4.0/tests/test_resultsets.py 
new/python-redmine-2.5.0/tests/test_resultsets.py
--- old/python-redmine-2.4.0/tests/test_resultsets.py   2022-12-27 
14:21:04.000000000 +0100
+++ new/python-redmine-2.5.0/tests/test_resultsets.py   2023-03-03 
15:47:52.000000000 +0100
@@ -176,33 +176,34 @@
     @mock.patch('redminelib.open', mock.mock_open(), create=True)
     def test_export(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'), 
'/foo/bar/issues.txt')
+        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.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')
+        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.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')
+        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):
+        columns = ['all_gui']
         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.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar/', 
columns=columns), '/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')
+        self.assertEqual(self.redmine.issue.all().export('txt', '/foo/bar/', 
columns=columns), '/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')
+        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