Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An optional 'id' argument for Manager.save method. #215

Merged
merged 9 commits into from
Feb 11, 2023
2 changes: 1 addition & 1 deletion src/xero/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
from .api import Xero # NOQA: F401

__version__ = "0.9.3"
__version__ = "0.9.4.dev0"
9 changes: 8 additions & 1 deletion src/xero/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from .filesmanager import FilesManager
from .manager import Manager
from .paymentmanager import PaymentManager
from .payrollmanager import PayrollManager
from .projectmanager import ProjectManager

Expand Down Expand Up @@ -47,10 +48,16 @@ def __init__(self, credentials, unit_price_4dps=False, user_agent=None):
# the lowercase name of the object and attach it to an
# instance of a Manager object to operate on it
for name in self.OBJECT_LIST:

manager_class = Manager

if name == 'Payments':
manager_class = PaymentManager

setattr(
self,
name.lower(),
Manager(name, credentials, unit_price_4dps, user_agent),
manager_class(name, credentials, unit_price_4dps, user_agent),
)

setattr(self, "filesAPI", Files(credentials))
Expand Down
29 changes: 29 additions & 0 deletions src/xero/paymentmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
from __future__ import unicode_literals

from .basemanager import BaseManager
from .constants import XERO_API_URL
from .utils import resolve_user_agent, singular


class PaymentManager(BaseManager):

def __init__(self, name, credentials, unit_price_4dps=False, user_agent=None):

self.credentials = credentials
self.name = name
self.base_url = credentials.base_url + XERO_API_URL
self.extra_params = {"unitdp": 4} if unit_price_4dps else {}
self.singular = singular(name)
self.user_agent = resolve_user_agent(
user_agent, getattr(credentials, "user_agent", None)
)

for method_name in self.DECORATED_METHODS:
method = getattr(self, "_%s" % method_name)
setattr(self, method_name, self._get_data(method))

def _delete(self, id):
uri = "/".join([self.base_url, self.name, id])
data = {'Status': 'DELETED'}
body = {"xml": self._prepare_data_for_save(data)}
return uri, {}, "post", body, None, False
28 changes: 28 additions & 0 deletions tests/helpers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@

import re
import six
from collections import defaultdict
from xml.dom.minidom import parseString


def assertXMLEqual(test_case, xml1, xml2, message=""):
def to_str(s):
return s.decode("utf-8") if six.PY3 and isinstance(s, bytes) else str(s)

def clean_xml(xml):
xml = "<root>%s</root>" % to_str(xml)
return str(re.sub(">\n *<", "><", parseString(xml).toxml()))

def xml_to_dict(xml):
nodes = re.findall("(<([^>]*)>(.*?)</\\2>)", xml)
if len(nodes) == 0:
return xml
d = defaultdict(list)
for node in nodes:
d[node[1]].append(xml_to_dict(node[2]))
return d

cleaned = map(clean_xml, (xml1, xml2))
d1, d2 = tuple(map(xml_to_dict, cleaned))

test_case.assertEqual(d1, d2, message)
42 changes: 9 additions & 33 deletions tests/test_manager.py
Original file line number Diff line number Diff line change
@@ -1,39 +1,16 @@
from __future__ import unicode_literals

import datetime
import re
import six
import unittest
from collections import defaultdict
from mock import Mock, patch
from xml.dom.minidom import parseString

from xero.exceptions import XeroExceptionUnknown
from xero.manager import Manager

from .helpers import assertXMLEqual

class ManagerTest(unittest.TestCase):
def assertXMLEqual(self, xml1, xml2, message=""):
def to_str(s):
return s.decode("utf-8") if six.PY3 and isinstance(s, bytes) else str(s)

def clean_xml(xml):
xml = "<root>%s</root>" % to_str(xml)
return str(re.sub(">\n *<", "><", parseString(xml).toxml()))

def xml_to_dict(xml):
nodes = re.findall("(<([^>]*)>(.*?)</\\2>)", xml)
if len(nodes) == 0:
return xml
d = defaultdict(list)
for node in nodes:
d[node[1]].append(xml_to_dict(node[2]))
return d

cleaned = map(clean_xml, (xml1, xml2))
d1, d2 = tuple(map(xml_to_dict, cleaned))

self.assertEqual(d1, d2, message)
class ManagerTest(unittest.TestCase):

def test_serializer(self):
credentials = Mock(base_url="")
Expand Down Expand Up @@ -101,9 +78,7 @@ def test_serializer(self):
</Invoice>
"""

self.assertXMLEqual(
resultant_xml, expected_xml,
)
assertXMLEqual(self, resultant_xml, expected_xml)

def test_serializer_phones_addresses(self):
credentials = Mock(base_url="")
Expand Down Expand Up @@ -161,8 +136,11 @@ def test_serializer_phones_addresses(self):
</Contact>
"""

self.assertXMLEqual(
resultant_xml, expected_xml, "Resultant XML does not match expected."
assertXMLEqual(
self,
resultant_xml,
expected_xml,
"Resultant XML does not match expected."
)

def test_serializer_nested_singular(self):
Expand Down Expand Up @@ -196,9 +174,7 @@ def test_serializer_nested_singular(self):
<DueDate>2015-07-06T16:25:02</DueDate>
"""

self.assertXMLEqual(
resultant_xml, expected_xml,
)
assertXMLEqual(self, resultant_xml, expected_xml)

def test_filter(self):
"""The filter function should correctly handle various arguments"""
Expand Down
31 changes: 31 additions & 0 deletions tests/test_paymentmanager.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from __future__ import unicode_literals

import unittest
from mock import Mock

from xero.paymentmanager import PaymentManager

from .helpers import assertXMLEqual


class ManagerTest(unittest.TestCase):

def test_delete(self):

credentials = Mock(base_url="")
manager = PaymentManager("payments", credentials)

uri, params, method, body, headers, singleobject = manager._delete(
"768e44ef-c1e3-4d7f-8e06-f6e8bc4eefa4")

self.assertEqual(
uri, '/api.xro/2.0/payments/768e44ef-c1e3-4d7f-8e06-f6e8bc4eefa4'
)

self.assertEqual(params, {})
self.assertEqual(method, 'post')
self.assertIn('xml', body)

assertXMLEqual(self, body['xml'], "<Status>DELETED</Status>")

self.assertIsNone(headers)