3 Commits
1.0.3 ... main

Author SHA1 Message Date
f10b39a3ba Update test data 2022-11-25 23:03:43 +13:00
bbd2083109 Added exception to catch rate-limiting. 2022-09-13 14:47:48 +12:00
2c6c1f6f34 v1.0.3 2022-06-26 07:57:43 +12:00
10 changed files with 188 additions and 38 deletions

4
.gitignore vendored
View File

@@ -6,6 +6,10 @@ __pycache__
.coverage
coverage.xml
# distribution files
dist/
*egg-info/
# Ignore Scratch files
scratch/*
!/scratch/README.md

View File

@@ -4,6 +4,7 @@
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/venv" />
<excludeFolder url="file://$MODULE_DIR$/.pytest_cache" />
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />

View File

@@ -1,10 +1,13 @@
import requests
gimport logging
from requests.auth import HTTPBasicAuth
from akauntingpy import exceptions
from akauntingpy.helpers import *
__version__ = "1.0.2"
__version__ = "1.0.5"
logger = logging.getLogger(__name__)
class Client(object):
@@ -18,16 +21,21 @@ class Client(object):
username,
password,
company_id,
ssl_verify=True,
currency_code="NZD",
currency_rate="1.0"):
"""
Create a new instance.
Args:
url (str): The URL to the Akaunting api.
username (str): The username of the Akaunting credentials.
password (str): The password of the Akaunting credentials.
url (str): The URL to the Akaunting api. ** required **
username (str): The username of the Akaunting credentials. ** required **
password (str): The password of the Akaunting credentials. ** required **
company_id (str): The company ID from Akaunting
currency_code (str): The currency code. default is NZD
currency_rate (str): The currency rate. default is "1.0"
"""
self.url = url
self.ssl_verify = ssl_verify
self.authentication = HTTPBasicAuth(username, password)
self.headers = {
'User-Agent': 'AkauntingPy v' + __version__,
@@ -44,7 +52,8 @@ class Client(object):
url=self.url + "/" + endpoint,
headers=self.headers,
auth=self.authentication,
params=MergeDict(self.default_params, params)
params=MergeDict(self.default_params, params),
verify=self.ssl_verify
)
response_ = response.json()
@@ -62,6 +71,9 @@ class Client(object):
"\n".join(errors))
# elif response.status_code != 200 and response.status_code != 201:
# raise exceptions.Error(response_['message'])
elif response.status_code == 429:
# We hit the maximum requests
raise exceptions.TooManyAttempts(response_['message'])
return response_
def ping(self):
@@ -73,17 +85,45 @@ class Client(object):
if params.get('search', False):
# Check if there is an account returned
if data['meta']['pagination'].get('count') == 0:
# No account found
raise exceptions.AccountNotFound("Sorry, account not found matching search parameters: %s".format(
params.get('search')
))
try:
if data['meta']['pagination'].get('count') == 0:
# No account found
raise exceptions.AccountNotFound("Sorry, account not found matching search parameters: %s".format(
params.get('search')
))
except KeyError as e:
# New API 3.0
if data['meta']['total'] == 0:
raise exceptions.AccountNotFound("Sorry, account not found matching search parameters: %s".format(
params.get('search')
))
return data['data']
def get_contact(self, **params):
data = self.call(endpoint="contacts", **params)
print(data)
if params.get('search', False):
try:
# Check if there is an account returned
if data['meta']['pagination'].get('count') == 0:
# No account found
raise exceptions.AccountNotFound("Sorry, contact not found matching search parameters: %s".format(
params.get('search')
))
except KeyError as e:
# New API 3.0
if data['meta']['total'] == 0:
raise exceptions.AccountNotFound("Sorry, contact not found matching search parameters: %s".format(
params.get('search')
))
return data['data']
def create_transaction(self,
transaction_type='income', # Payment method type
account_id=None, # Account ID to assign
number="NULL", # Transaction number
category_id=None, # Category ID to assign
contact_id=None, # Contact ID/Client to assign
description=None, # Description
@@ -106,6 +146,7 @@ class Client(object):
data = self.call(endpoint="transactions",
method="POST",
search="type:" + transaction_type,
number=number,
type=transaction_type,
account_id=account_id,
category_id=category_id,
@@ -121,18 +162,23 @@ class Client(object):
)
return data
def create_transfer(self,
from_account_id=None, # Account ID to create transfer from
to_account_id=None, # Account ID to create transfer to
transferred_at=None, # Date of expense/transfer or income
payment_method="Bank Transfer", # Payment method
amount=None, # Amount received/paid
**params # Any additional parameters
):
data = self.call(endpoint="transfers",
method="POST",
from_account_id=None, # Account ID to create transfer from
to_account_id=None, # Account ID to create transfer to
transferred_at=None, # Date of expense/transfer or income
payment_method="Bank Transfer", # Payment method
amount=None, # Amount received/paid
**params # Any additional parameters
):
logger.info("Transfer called with parameters")
logger.info("from_account_id: %s", from_account_id)
logger.info("to_account_id: %s", to_account_id)
logger.info("transferred_at: %s", transferred_at)
logger.info("payment_method: %s", payment_method)
logger.info("amount: %s", amount)
data = self.call(endpoint="transfers",
method="POST",
from_account_id=from_account_id,
to_account_id=to_account_id,
transferred_at=transferred_at,
@@ -140,4 +186,4 @@ class Client(object):
amount=EnsurePositiveInteger(amount),
**params
)
return data
return data

View File

@@ -23,6 +23,22 @@ class AccountNotFound(Error):
"""
Account not found
Args:
Error (_type_): _description_
"""
class ContactNotFound(Error):
"""
Account not found
Args:
Error (_type_): _description_
"""
class TooManyAttempts(Error):
"""
Too Many attempts to API
Args:
Error (_type_): _description_
"""

View File

@@ -4,7 +4,7 @@
"id":2,
"company_id":1,
"name":"Some Account",
"number":"**-9011-*******-03",
"number":"00-0000-0000000-00",
"currency_code":"NZD",
"opening_balance":0,
"opening_balance_formatted":"$0.00",
@@ -31,4 +31,4 @@
}
}
}
}
}

View File

@@ -0,0 +1,56 @@
{
"data":[
{
"id":2,
"company_id":1,
"type":"bank",
"name":"Some Account",
"number":"00-0000-0000000-00",
"currency_code":"NZD",
"opening_balance":351.17,
"opening_balance_formatted":"$351.17",
"current_balance":306.3600000000006,
"current_balance_formatted":"$306.36",
"bank_name":"None",
"bank_phone":"None",
"bank_address":"None",
"enabled":true,
"created_from":"core::ui",
"created_by":1,
"created_at":"2022-05-30T11:06:21+12:00",
"updated_at":"2022-05-30T11:10:47+12:00"
}
],
"links":{
"first":"https://someakaunting-url/api/accounts?page=1",
"last":"https://someakaunting-url/api/accounts?page=1",
"prev":"None",
"next":"None"
},
"meta":{
"current_page":1,
"from":1,
"last_page":1,
"links":[
{
"url":"None",
"label":"Previous",
"active":false
},
{
"url":"https://someakaunting-url/api/accounts?page=1",
"label":"1",
"active":true
},
{
"url":"None",
"label":"Next",
"active":false
}
],
"path":"https://someakaunting-url/api/accounts",
"per_page":100,
"to":1,
"total":1
}
}

View File

@@ -0,0 +1,13 @@
{
"data":[],
"meta":{
"total":0,
"count":0,
"per_page":100,
"current_page":0,
"total_pages":0,
"links":{
}
}
}

View File

@@ -8,7 +8,7 @@ with open('README.md', 'r') as readme:
setup(
name='akauntingpy',
name='akaunting-py',
use_scm_version=True,
author='CyberCinch',
description='Python interface to the Akaunting API.',
@@ -16,6 +16,7 @@ setup(
long_description=long_description,
long_description_content_type='text/markdown',
license='MIT',
version='1.0.5',
keywords='akaunting api library',
packages=find_packages(),
install_requires=[

View File

@@ -11,24 +11,24 @@ from requests.auth import HTTPBasicAuth
class TestAPI:
@pytest.fixture()
def setUp(self):
c = akauntingpy.Client("https://akaunting.guise.net.nz/api",
"aaron@guise.net.nz",
"L3Tm31N0w",
c = akauntingpy.Client("https://someakaunting-url/api",
"some-emailaddress@somewhere.com",
"aPassWord",
1)
yield c
@pytest.fixture()
def setUpFailed(self):
c = akauntingpy.Client("https://akaunting.guise.net.nz/api",
"aaron@guise.net.nz",
"L3Tm31N0w1",
c = akauntingpy.Client("https://someakaunting-url/api",
"some-emailaddress@somewhere.com",
"aWrongPassWord",
1)
yield c
def test_init(self, setUp):
c = setUp
assert isinstance(c, akauntingpy.Client)
assert c.url == "https://akaunting.guise.net.nz/api"
assert c.url == "https://someakaunting-url/api"
assert isinstance(c.authentication, HTTPBasicAuth)
def test_ping_success(self, setUp, requests_mock):
@@ -55,19 +55,32 @@ class TestAPI:
json=RetrieveJSONFromFile("data/GetAccountsList.json"))
data = c.get_accounts(params={'page': 1, 'limit': 200})
def test_get_account_search(self, setUp, requests_mock):
def test_get_account_search_v2(self, setUp, requests_mock):
c = setUp
requests_mock.get(c.url + "/accounts?search=number%3A38-9011-0510023-03&params=page&params=limit&company_id=1",
json=RetrieveJSONFromFile("data/GetAccountsSearch.json"))
data = c.get_accounts(search="number:38-9011-0510023-03", params={'page': 1, 'limit': 200})
requests_mock.get(c.url + "/accounts?search=number%3A00-0000-0000000-00&params=page&params=limit&company_id=1",
json=RetrieveJSONFromFile("data/v2/GetAccountsSearch.json"))
data = c.get_accounts(search="number:00-0000-0000000-00", params={'page': 1, 'limit': 200})
def test_get_account_search_not_found(self, setUp, requests_mock):
def test_get_account_search_v3(self, setUp, requests_mock):
c = setUp
requests_mock.get(c.url + "/accounts?search=number%3A00-0000-0000000-00&params=page&params=limit&company_id=1",
json=RetrieveJSONFromFile("data/v3/GetAccountsSearch.json"))
data = c.get_accounts(search="number:00-0000-0000000-00", params={'page': 1, 'limit': 200})
def test_get_account_search_not_found_v2(self, setUp, requests_mock):
c = setUp
requests_mock.get(c.url + "/accounts?search=number%3Aarandomvalue&company_id=1",
json=RetrieveJSONFromFile("data/GetAccountsSearchNotFound.json"))
json=RetrieveJSONFromFile("data/v2/GetAccountsSearchNotFound.json"))
with pytest.raises(AccountNotFound):
data = c.get_accounts(search="number:arandomvalue")
def test_get_account_search_not_found_v3(self, setUp, requests_mock):
c = setUp
requests_mock.get(c.url + "/accounts?search=number%3Aarandomvalue&company_id=1",
json=RetrieveJSONFromFile("data/v3/GetAccountsSearchNotFound.json"))
with pytest.raises(AccountNotFound):
data = c.get_accounts(search="number:arandomvalue")
def test_create_transaction_income_success(self, setUp, requests_mock,
transaction_type='income', # Payment method type
account_id=2, # Account ID to assign