You've already forked akaunting-py
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f10b39a3ba | |||
| bbd2083109 | |||
| 2c6c1f6f34 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -6,6 +6,10 @@ __pycache__
|
||||
.coverage
|
||||
coverage.xml
|
||||
|
||||
# distribution files
|
||||
dist/
|
||||
*egg-info/
|
||||
|
||||
# Ignore Scratch files
|
||||
scratch/*
|
||||
!/scratch/README.md
|
||||
|
||||
1
.idea/akaunting-py.iml
generated
1
.idea/akaunting-py.iml
generated
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_
|
||||
"""
|
||||
@@ -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 @@
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
56
data/v3/GetAccountsSearch.json
Normal file
56
data/v3/GetAccountsSearch.json
Normal 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
|
||||
}
|
||||
}
|
||||
13
data/v3/GetAccountsSearchNotFound.json
Normal file
13
data/v3/GetAccountsSearchNotFound.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"data":[],
|
||||
"meta":{
|
||||
"total":0,
|
||||
"count":0,
|
||||
"per_page":100,
|
||||
"current_page":0,
|
||||
"total_pages":0,
|
||||
"links":{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
3
setup.py
3
setup.py
@@ -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=[
|
||||
|
||||
@@ -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¶ms=page¶ms=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¶ms=page¶ms=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¶ms=page¶ms=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
|
||||
|
||||
Reference in New Issue
Block a user