Initial commit

This commit is contained in:
Andrew Bryant 2023-12-17 10:05:51 -05:00
commit c56243589b
12 changed files with 315 additions and 0 deletions

0
CHANGELOG.md Normal file
View File

5
README.md Normal file
View File

@ -0,0 +1,5 @@
# Python Invoice Ninja SDK
Inspired by the [official PHP SDK](https://github.com/invoiceninja/sdk-php), a
Python wrapper for Invoice Ninja's REST API.

74
invoice_ninja/__init__.py Normal file
View File

@ -0,0 +1,74 @@
import requests
class InvoiceNinja(object):
API_V1 = 'api/v1'
ENDPOINT_URLS = {
'clients': 'clients',
'products': 'products',
'invoices': 'invoices',
'recurring invoices': 'recurring_invoices',
'payments': 'payments',
'quotes': 'quotes',
'credits': 'credits',
'reports': 'reports',
'activities': 'activities',
'charts': 'charts',
'companies': 'companies',
'documents': 'documents',
'emails': 'emails',
'expense': 'expenses',
'export': 'export',
'import': 'import_json',
'ping': 'ping',
'health check': 'health_check',
'users': 'users'
}
def __init__(self,
endpoint_url: str = 'https://invoicing.co',
api_token: str = str()):
self.endpoint_url = '{}/{}'.format(endpoint_url, self.API_V1)
self.api_token = api_token
self.headers = dict()
def _get_url_for(self, endpoint: str = 'ping'):
'''
Get complete URL for an endpoint.
Endpoint URLs are appended to the Invoice Ninja base URL
and returned.
'''
if endpoint in self.ENDPOINT_URLS:
return '{}/{}'.format(self.endpoint_url,
self.ENDPOINT_URLS[endpoint])
else:
raise KeyError('Endpoint URL not found')
#def _get_headers(self, headers: dict = dict()):
def build_headers(self):
'''
Build Invoice Ninja API headers for request.
A header dictionary with the API token is returned by default.
'''
headers = {
'X-API-TOKEN': self.api_token,
'X-Requested-With': 'XMLHttpRequest'}
return self.headers.update(headers)
def ping(self):
'''
Ping Invoice Ninja instance.
'''
server_response = requests.get(url=self._get_url_for(),
headers=self.build_headers())
if server_response.ok:
return True
else:
return False

View File

View File

@ -0,0 +1,30 @@
from invoice_ninja import InvoiceNinja
class BaseEndpoint(InvoiceNinja):
def bulk(self, action: str):
pass
def archive(self):
pass
def delete(self):
pass
def restore(self):
pass
def all(self):
pass
def get(self):
pass
def update(self):
pass
def create(self):
pass
def download(self):
pass

View File

@ -0,0 +1,92 @@
from invoice_ninja.endpoints.base_endpoint import BaseEndpoint
from invoice_ninja.types.client import Client
import requests
class Clients(BaseEndpoint):
uri = '/api/v1/clients'
def __init__(self, base_url: str = str(), api_token: str = str()):
super().__init__(base_url, api_token)
self.url = super()._get_url_for('clients')
def __build_sort_params(self, sort: dict):
sort_params = {'sort': str()}
is_first_entry = True
for option in sort.keys():
if is_first_entry:
sort_params['sort'] += '{}|{}'.format(option, sort[option])
is_first_entry = False
else:
sort_params['sort'] += ' {}|{}'.format(option, sort[option])
return sort_params
def __client_from_dict(self, client: dict):
return Client(client_id=client['id'],
name=client['name'],
address=client['address1'],
city=client['city'],
state=client['state'],
postal_code=client['postal_code'],
phone=client['phone'],
email=client['contacts'][0]['email'],
pets=client['custom_value1'])
def __clients_from_response(self, response: requests.Response):
clients = list()
for client_dict in response.json()['data']:
clients.append(self.__client_from_dict(client_dict))
return clients
def show_client(self, client_id: str = None):
"""
Get client based on client id.
"""
if client_id:
response = requests.get(url=self.url,
headers=super()._get_headers())
if response.ok:
return self.__client_from_dict(response.json()['data'])
return None
def list_clients(self, include: str = 'activities',
sort: dict = dict(), status: str = 'active',
name: str = None):
"""
Get list of clients.
"""
request_params = dict()
# Add sort parameters to request
if len(sort) > 0:
request_params.update(self.__build_sort_params(sort))
# Add include parameters to request
request_params.update({'include': include})
# Add status parameters to request
request_params.update({'status': status})
# Add name parameters to request
if name:
request_params.update({'name': name})
# Check is request should be sent with parameters
if len(request_params) > 0:
response = requests.get(url=self.url,
params=request_params,
headers=super()._get_headers())
else:
response = requests.get(url=self.url,
headers=super()._get_headers())
if response.ok:
return self.__clients_from_response(response)

View File

View File

View File

@ -0,0 +1,27 @@
class Client(object):
def __init__(self, client_id: int = None, name: str = None,
address: str = None, city: str = None, state: str = None,
postal_code: str = None, phone: str = None,
email: str = None, pets: str = None):
self.id = client_id
self.name = name
self.address = address
self.city = city
self.state = state
self.postal_code = postal_code
self.phone = phone
self.email = email
self.pets = pets
def __str__(self):
return 'Client({}, {}, {}, {}, {}, {}, {}, {}, {})'.format(
self.id,
self.name,
self.address,
self.city,
self.state,
self.postal_code,
self.phone,
self.email,
self.pets)

View File

@ -0,0 +1,36 @@
class ClientContact(object):
def __init__(self, first_name: str = '',
last_name: str = '',
email: str = '',
phone: str = '',
send_email: bool = True,
custom_value1: str = '',
custom_value2: str = '',
custom_value3: str = '',
custom_value4: str = ''):
self.first_name = first_name
self.last_name = last_name
self.email = email
self.phone = phone
# Flag for whether the contact will receive emails.
self.send_email = send_email
# Custom values
self.custom_value1 = custom_value1
self.custom_value2 = custom_value2
self.custom_value3 = custom_value3
self.custom_value4 = custom_value4
def __str__(self):
return 'ClientContact({}, {}, {}, {}, {}, {}, {}, {}, {})'.format(
self.first_name,
self.last_name,
self.email,
self.phone,
self.send_email,
self.custom_value1,
self.custom_value2,
self.custom_value3,
self.custom_value4)

View File

@ -0,0 +1,38 @@
class ClientSettings(object):
def __init__(self, language_id: str = None,
currency_id: str = None,
payment_terms: str = None,
valid_until: str = None,
default_task_rate: float = 0,
send_reminders: bool = None):
# The language ID for the client, for the full list of languages,
# please see this resource - optional
#
# https://invoiceninja.github.io/docs/statics/#languages
self.language_id = language_id
# The currency ID - optional
# See this resource for full list:
#
# https://invoiceninja.github.io/docs/statics/#currencies
self.currency_id = currency_id
# The payment terms - in days - optional
self.payment_terms = payment_terms
# The quote terms - optional
#
# How many days the quote will be valid for.
self.valid_until = valid_until
# The task rate for this client - optional
#
# A value of 0 equates to disabled.
self.default_task_rate = default_task_rate
# Whether the client will receive reminders - optional
#
# When left unset, this setting will rely on the company
# settings as the override/default
self.send_reminders = send_reminders

13
shell.nix Normal file
View File

@ -0,0 +1,13 @@
{ pkgs ? import <nixpkgs> {} }:
with pkgs;
pkgs.mkShell {
nativeBuildInputs = [
# Python development environment
(python3.withPackages(ps: with ps; [
requests
]))
];
}