Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 02cc6cdf0e | |||
| 560d3377e3 | |||
| e61396dd6a | |||
| e14328c68b |
@ -1,33 +0,0 @@
|
|||||||
# EditorConfig is awesome: https://EditorConfig.org
|
|
||||||
|
|
||||||
# top-most EditorConfig file
|
|
||||||
root = true
|
|
||||||
|
|
||||||
# Unix-style newlines with a newline ending every file
|
|
||||||
[*]
|
|
||||||
end_of_line = lf
|
|
||||||
insert_final_newline = true
|
|
||||||
|
|
||||||
# Matches multiple files with brace expansion notation
|
|
||||||
# Set default charset
|
|
||||||
[*.{js,py}]
|
|
||||||
charset = utf-8
|
|
||||||
|
|
||||||
# Python indentation
|
|
||||||
[*.py]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
# Nix indentation
|
|
||||||
[*.nix]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 2
|
|
||||||
|
|
||||||
# Markdown
|
|
||||||
[*.md]
|
|
||||||
indent_style = space
|
|
||||||
indent_size = 4
|
|
||||||
|
|
||||||
# Tab indentation (no size specified)
|
|
||||||
[Makefile]
|
|
||||||
indent_style = tab
|
|
||||||
@ -1,10 +0,0 @@
|
|||||||
name: test-clients
|
|
||||||
run-name: ${{ gitea.actor }} is testing pyinvoiceninja
|
|
||||||
on: push
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
pyinvoiceninja:
|
|
||||||
runs-on: invoiceninja
|
|
||||||
steps:
|
|
||||||
- run: echo "Hello world!"
|
|
||||||
|
|
||||||
12
CHANGELOG.md
12
CHANGELOG.md
@ -1,12 +0,0 @@
|
|||||||
# Changelog
|
|
||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
|
||||||
|
|
||||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
||||||
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
||||||
|
|
||||||
## [Unreleased]
|
|
||||||
|
|
||||||
### Added
|
|
||||||
|
|
||||||
- This CHANGELOG.md!
|
|
||||||
22
README.md
22
README.md
@ -1,25 +1,5 @@
|
|||||||
# pyinvoiceninja
|
# Python Invoice Ninja SDK
|
||||||
|
|
||||||
Inspired by the [official PHP SDK](https://github.com/invoiceninja/sdk-php), a
|
Inspired by the [official PHP SDK](https://github.com/invoiceninja/sdk-php), a
|
||||||
Python wrapper for Invoice Ninja's REST API.
|
Python wrapper for Invoice Ninja's REST API.
|
||||||
|
|
||||||
## Model Projects
|
|
||||||
|
|
||||||
Projects to look to for guidance:
|
|
||||||
|
|
||||||
* [official PHP SDK](https://github.com/invoiceninja/sdk-php)
|
|
||||||
- official Invoice Ninja SDK
|
|
||||||
|
|
||||||
* [python-vaultwarden](https://github.com/numberly/python-vaultwarden)
|
|
||||||
- simple python wrapper for the bitwarden/vaultwarden
|
|
||||||
|
|
||||||
## How to use
|
|
||||||
|
|
||||||
Let see some basic usage:
|
|
||||||
```
|
|
||||||
>>> # Import client from module
|
|
||||||
>>> from pyinvoiceninja import InvoiceNinjaClient
|
|
||||||
>>> #
|
|
||||||
>>> client = InvoiceNinjaClient(base_url='https://ninja.instance/api/v1')
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|||||||
24
invoice_ninja/__init__.py
Normal file
24
invoice_ninja/__init__.py
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
from invoice_ninja.endpoints.clients import Clients
|
||||||
|
|
||||||
|
class InvoiceNinja(object):
|
||||||
|
def __init__(self, endpoint_url: str = 'https://invoicing.co',
|
||||||
|
api_token: str = str()):
|
||||||
|
self._endpoint_url = endpoint_url
|
||||||
|
self._api_token = api_token
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint_url(self):
|
||||||
|
return self._endpoint_url
|
||||||
|
|
||||||
|
@endpoint_url.setter
|
||||||
|
def endpoint_url(self, endpoint_url: str):
|
||||||
|
self._endpoint_url = endpoint_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_token(self):
|
||||||
|
return self._api_token
|
||||||
|
|
||||||
|
@api_token.setter
|
||||||
|
def api_token(self, api_token: str):
|
||||||
|
self._api_token = api_token
|
||||||
|
|
||||||
28
invoice_ninja/endpoints/base_endpoint.py
Normal file
28
invoice_ninja/endpoints/base_endpoint.py
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
class BaseEndpoint(object):
|
||||||
|
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
|
||||||
|
|
||||||
88
invoice_ninja/endpoints/clients.py
Normal file
88
invoice_ninja/endpoints/clients.py
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
from invoice_ninja.endpoints.base_endpoint import BaseEndpoint
|
||||||
|
from invoice_ninja.models.client import Client
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class Clients(BaseEndpoint):
|
||||||
|
uri = '/api/v1/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)
|
||||||
|
|
||||||
10
invoice_ninja/endpoints/ping.py
Normal file
10
invoice_ninja/endpoints/ping.py
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
from invoice_ninja.endpoints.base_endpoint import BaseEndpoint
|
||||||
|
|
||||||
|
import requests
|
||||||
|
|
||||||
|
class Ping(BaseEndpoint):
|
||||||
|
uri = '/api/v1/ping'
|
||||||
|
|
||||||
|
def ping(self):
|
||||||
|
pass
|
||||||
|
|
||||||
57
invoice_ninja/http_client.py
Normal file
57
invoice_ninja/http_client.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
class HTTPClient(object):
|
||||||
|
"""HTTP client for Invoice Ninja REST API."""
|
||||||
|
|
||||||
|
def __init__(self, endpoint_url: str = 'https://invoicing.co',
|
||||||
|
api_token: str = str()):
|
||||||
|
self._endpoint_url = 'https://invoicing.co'
|
||||||
|
self._api_token = str()
|
||||||
|
self._headers = dict()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint_url(self):
|
||||||
|
return self._endpoint_url
|
||||||
|
|
||||||
|
@endpoint_url.setter
|
||||||
|
def endpoint_url(self, endpoint_url: str):
|
||||||
|
self._endpoint_url = endpoint_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_token(self):
|
||||||
|
return self._api_token
|
||||||
|
|
||||||
|
@api_token.setter
|
||||||
|
def api_token(self, api_token: str):
|
||||||
|
self._api_token = api_token
|
||||||
|
|
||||||
|
def add_headers(self, headers: dict):
|
||||||
|
"""Add HTTP headers to request."""
|
||||||
|
|
||||||
|
self._headers.update(headers)
|
||||||
|
|
||||||
|
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': _api_token,
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'}
|
||||||
|
|
||||||
|
return self._headers.update(headers)
|
||||||
|
|
||||||
|
def send(self, uri: str,
|
||||||
|
payload: dict,
|
||||||
|
method: str = 'get'):
|
||||||
|
"""Send request to Invoice Ninja REST API."""
|
||||||
|
|
||||||
|
url = '{}/{}'.format(endpoint_url, uri)
|
||||||
|
|
||||||
|
if method == 'get':
|
||||||
|
return requests.get(url, params=payload)
|
||||||
|
|
||||||
|
elif method == 'post':
|
||||||
|
return requests.post(url, params=payload)
|
||||||
|
|
||||||
57
invoice_ninja/http_client/http_client.py
Normal file
57
invoice_ninja/http_client/http_client.py
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
import requests
|
||||||
|
|
||||||
|
class HTTPClient(object):
|
||||||
|
"""HTTP client for Invoice Ninja REST API."""
|
||||||
|
|
||||||
|
def __init__(self, endpoint_url: str = 'https://invoicing.co',
|
||||||
|
api_token: str = str()):
|
||||||
|
self_endpoint_url = 'https://invoicing.co'
|
||||||
|
self._api_token = str()
|
||||||
|
self._headers = dict()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def endpoint_url(self):
|
||||||
|
return self._endpoint_url
|
||||||
|
|
||||||
|
@endpoint_url.setter
|
||||||
|
def endpoint_url(self, endpoint_url: str):
|
||||||
|
self._endpoint_url = endpoint_url
|
||||||
|
|
||||||
|
@property
|
||||||
|
def api_token(self):
|
||||||
|
return self._api_token
|
||||||
|
|
||||||
|
@api_token.setter
|
||||||
|
def api_token(self, api_token: str):
|
||||||
|
self._api_token = api_token
|
||||||
|
|
||||||
|
def add_headers(self, headers: dict):
|
||||||
|
"""Add HTTP headers to request."""
|
||||||
|
|
||||||
|
self._headers.update(headers)
|
||||||
|
|
||||||
|
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': _api_token,
|
||||||
|
'X-Requested-With': 'XMLHttpRequest'}
|
||||||
|
|
||||||
|
return self._headers.update(headers)
|
||||||
|
|
||||||
|
def send(self, uri: str,
|
||||||
|
payload: dict,
|
||||||
|
method: str = 'get'):
|
||||||
|
"""Send request to Invoice Ninja REST API."""
|
||||||
|
|
||||||
|
url = '{}/{}'.format(endpoint_url, uri)
|
||||||
|
|
||||||
|
if method == 'get':
|
||||||
|
return requests.get(url, params=payload)
|
||||||
|
|
||||||
|
elif method == 'post':
|
||||||
|
return requests.post(url, params=payload)
|
||||||
|
|
||||||
0
invoice_ninja/models/README.md
Normal file
0
invoice_ninja/models/README.md
Normal file
0
invoice_ninja/models/__init__.py
Normal file
0
invoice_ninja/models/__init__.py
Normal file
27
invoice_ninja/models/client.py
Normal file
27
invoice_ninja/models/client.py
Normal 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)
|
||||||
|
|
||||||
36
invoice_ninja/models/clientContact.py
Normal file
36
invoice_ninja/models/clientContact.py
Normal 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)
|
||||||
|
|
||||||
38
invoice_ninja/models/clientSettings.py
Normal file
38
invoice_ninja/models/clientSettings.py
Normal 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
|
||||||
|
|
||||||
@ -1,4 +0,0 @@
|
|||||||
from .client import InvoiceNinjaClient
|
|
||||||
|
|
||||||
__all__ = ['InvoiceNinjaClient']
|
|
||||||
|
|
||||||
@ -1,146 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
|
|
||||||
# HTTP client
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Logger
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
# Local library imports
|
|
||||||
from .endpoints.clients import ClientsAPI
|
|
||||||
from .endpoints.documents import DocumentsAPI
|
|
||||||
|
|
||||||
class InvoiceNinjaClient(object):
|
|
||||||
"""
|
|
||||||
A client for interacting with the Invoice Ninja API.
|
|
||||||
|
|
||||||
Attributes
|
|
||||||
----------
|
|
||||||
base_url : str
|
|
||||||
URL to Invoice Ninja instance.
|
|
||||||
api_token : str
|
|
||||||
Token for API request access.
|
|
||||||
clients : ClientsAPI
|
|
||||||
An instance of the ClientsAPI class for client-related operations.
|
|
||||||
documents : DocumentsAPI
|
|
||||||
An instance of the DocumentsAPI class for document-related operations.
|
|
||||||
debug : bool
|
|
||||||
Flag to enable or disable debug logging.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self,
|
|
||||||
base_url: str = 'https://invoicing.co/api/v1',
|
|
||||||
api_token: str = '',
|
|
||||||
debug: bool = False):
|
|
||||||
"""
|
|
||||||
Constructs all the necessary attributes for the InvoiceNinjaClient object
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
base_url : str, optional
|
|
||||||
The base URL of the Invoice Ninja API (default is 'https://invoicing.co/api/v1').
|
|
||||||
api_token : str, optional
|
|
||||||
The API token for authentication (default is an empty string).
|
|
||||||
debug : bool, optional
|
|
||||||
Flag to enable or disable debug logging (default is False).
|
|
||||||
"""
|
|
||||||
|
|
||||||
# Check parameters for correct types
|
|
||||||
if not isinstance(base_url, str):
|
|
||||||
raise ValueError('Base URL must be a string.')
|
|
||||||
if not isinstance(api_token, str):
|
|
||||||
raise ValueError('API token must be a string.')
|
|
||||||
if not isinstance(debug, bool):
|
|
||||||
raise ValueError('Debug flag must be a boolean.')
|
|
||||||
|
|
||||||
# Initialize paramater attributes
|
|
||||||
self._base_url = base_url
|
|
||||||
self._api_token = api_token
|
|
||||||
self._debug = debug
|
|
||||||
|
|
||||||
# Initialize API client attributes
|
|
||||||
self._documents = DocumentsAPI(self)
|
|
||||||
self._clients = ClientsAPI(self)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def base_url(self) -> str:
|
|
||||||
"""Gets the base URL of the Invoice Ninja API."""
|
|
||||||
return self._base_url
|
|
||||||
|
|
||||||
@base_url.setter
|
|
||||||
def base_url(self, base_url: str):
|
|
||||||
"""Sets the base URL of the Invoice Ninja API."""
|
|
||||||
if not isinstance(base_url, str):
|
|
||||||
raise ValueError('Base URL must be a string.')
|
|
||||||
self._base_url = base_url
|
|
||||||
|
|
||||||
@property
|
|
||||||
def api_token(self) -> str:
|
|
||||||
"""Gets the API token."""
|
|
||||||
return self._api_token
|
|
||||||
|
|
||||||
@api_token.setter
|
|
||||||
def api_token(self, api_token: str):
|
|
||||||
"""Sets the API token."""
|
|
||||||
if not isinstance(api_token, str):
|
|
||||||
raise ValueError('API token must be a string.')
|
|
||||||
self._api_token = api_token
|
|
||||||
|
|
||||||
@property
|
|
||||||
def clients(self) -> ClientsAPI:
|
|
||||||
return self._clients
|
|
||||||
|
|
||||||
@property
|
|
||||||
def documents(self) -> DocumentsAPI:
|
|
||||||
return self._documents
|
|
||||||
|
|
||||||
@property
|
|
||||||
def debug(self):
|
|
||||||
return self._debug
|
|
||||||
|
|
||||||
@debug.setter
|
|
||||||
def debug(self, debug: bool) -> None:
|
|
||||||
"""Sets the debug flag."""
|
|
||||||
if not isinstance(debug, bool):
|
|
||||||
raise ValueError('Debug flag must be a boolean.')
|
|
||||||
self._debug = debug
|
|
||||||
if self._debug:
|
|
||||||
logger.enable(__name__)
|
|
||||||
else:
|
|
||||||
logger.disable(__name__)
|
|
||||||
|
|
||||||
@property
|
|
||||||
def headers(self) -> Dict[str, Any]:
|
|
||||||
"""Gets the headers for API requests."""
|
|
||||||
return {'X-API-TOKEN': self._api_token,
|
|
||||||
'X-Requested-With': 'XMLHttpRequest'}
|
|
||||||
|
|
||||||
def _log_debug(self, message: str) -> None:
|
|
||||||
"""
|
|
||||||
Logs a debug message if debugging is enabled.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
message : str
|
|
||||||
The debug message to log.
|
|
||||||
"""
|
|
||||||
if self._debug:
|
|
||||||
print(f'DEBUG: {message}')
|
|
||||||
|
|
||||||
def ping(self):
|
|
||||||
"""
|
|
||||||
Ping the Invoice Ninja instance to check if it's reachable.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
bool
|
|
||||||
True if the server response is OK, False otherwise.
|
|
||||||
"""
|
|
||||||
self._log_debug(f'Pinging {self.base_url}/ping with headers {self.headers}')
|
|
||||||
server_response = requests.get(url=f'{self.base_url}/ping',
|
|
||||||
headers=self.headers)
|
|
||||||
self._log_debug(f'Server response: {server_response.status_code}, {server_response.text}')
|
|
||||||
|
|
||||||
return server_response.ok
|
|
||||||
|
|
||||||
@ -1,5 +0,0 @@
|
|||||||
from .clients import ClientsAPI
|
|
||||||
from .documents import DocumentsAPI
|
|
||||||
|
|
||||||
__all__ = ['ClientsAPI', 'DocumentsAPI']
|
|
||||||
|
|
||||||
@ -1,176 +0,0 @@
|
|||||||
# HTTP client
|
|
||||||
import requests
|
|
||||||
|
|
||||||
# Logger
|
|
||||||
from loguru import logger
|
|
||||||
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
from pyinvoiceninja.models import Client
|
|
||||||
|
|
||||||
class ClientsAPI:
|
|
||||||
"""
|
|
||||||
A client for interacting with the Invoice Ninja clients API.
|
|
||||||
|
|
||||||
Attributes
|
|
||||||
----------
|
|
||||||
client : InvoiceNinjaClient
|
|
||||||
An instance of the InvoiceNinjaClient class.
|
|
||||||
clients_url : str
|
|
||||||
The URL for accessing the clients endpoint.
|
|
||||||
"""
|
|
||||||
|
|
||||||
VALID_SORT_BY: List[str] = [
|
|
||||||
'id', 'name', 'balance', 'paid_to_date', 'payment_balance',
|
|
||||||
'credit_balance', 'archived_at', 'updated_at', 'created_at',
|
|
||||||
'display_name', 'address', 'city', 'state', 'postal_code',
|
|
||||||
'custom_value1', 'custom_value2', 'custom_value3', 'custom_value4'
|
|
||||||
]
|
|
||||||
VALID_SORT_ORDER: List[str] = ['asc', 'desc']
|
|
||||||
VALID_INCLUDES: List[str] = ['activities', 'ledger', 'system_logs']
|
|
||||||
VALID_CLIENT_STATUSES: List[str] = ['active', 'archived', 'deleted']
|
|
||||||
VALID_BALANCE_OPERATORS: List[str] = ['lt', 'lte', 'gt', 'gte', 'eq']
|
|
||||||
|
|
||||||
def __init__(self, client: 'InvoiceNinjaClient') -> None:
|
|
||||||
"""
|
|
||||||
Constructs all the necessary attributes for the ClientsAPI object.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
client : InvoiceNinjaClient
|
|
||||||
An instance of the InvoiceNinjaClient class.
|
|
||||||
"""
|
|
||||||
self.client = client
|
|
||||||
self.clients_url = f'{self.client.base_url}/clients'
|
|
||||||
|
|
||||||
def get_clients(self,
|
|
||||||
includes: Optional[List[str]] = None,
|
|
||||||
sort: Optional[Dict[str, str]] = None,
|
|
||||||
statuses: Optional[List[str]] = None,
|
|
||||||
name: Optional[str] = None,
|
|
||||||
email: Optional[str] = None,
|
|
||||||
balance: Optional[Dict[str, Union[str, int]]] = None,
|
|
||||||
is_deleted: Optional[bool] = None,
|
|
||||||
filter_deleted_clients: Optional[bool] = None) -> List[Client]:
|
|
||||||
"""
|
|
||||||
Retrieves a list of clients from the Invoice Ninja API.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
includes : Optional[List[str]]
|
|
||||||
The child relationships to include in the response.
|
|
||||||
sort : Optional[Dict[str, str]]
|
|
||||||
A dictionary containing the field by which to sort the clients and the order.
|
|
||||||
statuses : Optional[List[str]]
|
|
||||||
The statuses to filter the clients.
|
|
||||||
name : Optional[str]
|
|
||||||
The name to filter the clients.
|
|
||||||
email : Optional[str]
|
|
||||||
The email to filter the clients.
|
|
||||||
balance : Optional[Dict[str, Union[str, int]]]
|
|
||||||
The balance to filter the clients.
|
|
||||||
is_deleted : Optional[bool]
|
|
||||||
Whether to include deleted clients.
|
|
||||||
filter_deleted_clients : Optional[bool]
|
|
||||||
Whether to filter out deleted clients.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
List[Client]
|
|
||||||
A list of Client objects if the request is successful.
|
|
||||||
|
|
||||||
Raises
|
|
||||||
------
|
|
||||||
ValueError
|
|
||||||
If any of the parameters are invalid.
|
|
||||||
"""
|
|
||||||
params = {}
|
|
||||||
|
|
||||||
if includes:
|
|
||||||
if isinstance(includes, list):
|
|
||||||
for item in includes:
|
|
||||||
if item not in self.VALID_INCLUDES:
|
|
||||||
raise ValueError(f'Invalid include option: {item}. Valid options: {self.VALID_INCLUDES}')
|
|
||||||
params['include'] = ','.join(includes)
|
|
||||||
else:
|
|
||||||
raise ValueError('Includes must be a list of valid client child relationship strings.')
|
|
||||||
|
|
||||||
if sort:
|
|
||||||
if isinstance(sort, dict):
|
|
||||||
if 'sort_by' not in sort or sort['sort_by'] not in self.VALID_SORT_BY:
|
|
||||||
raise ValueError(f'Invalid sort by option: {sort["sort_by"]}. Valid options: {self.VALID_SORT_BY}')
|
|
||||||
if 'sort_order' not in sort or sort['sort_order'] not in self.VALID_SORT_ORDER:
|
|
||||||
raise ValueError(f'Invalid sort order option: {sort["sort_order"]}. Valid options: {self.VALID_SORT_ORDER}')
|
|
||||||
params['sort'] = f'{sort["sort_by"]}|{sort["sort_order"]}'
|
|
||||||
else:
|
|
||||||
raise ValueError('Sort must be a dictionary with "sort_by" and "sort_order" keys.')
|
|
||||||
|
|
||||||
if statuses:
|
|
||||||
if isinstance(statuses, list):
|
|
||||||
for item in statuses:
|
|
||||||
if item not in self.VALID_CLIENT_STATUSES:
|
|
||||||
raise ValueError(f'Invalid status: {item}. Valid options: {self.VALID_CLIENT_STATUSES}')
|
|
||||||
params['status'] = ','.join(statuses)
|
|
||||||
else:
|
|
||||||
raise ValueError('Statuses must be a list of valid client statuses.')
|
|
||||||
|
|
||||||
if name:
|
|
||||||
if isinstance(name, str):
|
|
||||||
params['name'] = name
|
|
||||||
else:
|
|
||||||
raise ValueError('Name must be a string.')
|
|
||||||
|
|
||||||
if email:
|
|
||||||
if isinstance(email, str):
|
|
||||||
params['email'] = email
|
|
||||||
else:
|
|
||||||
raise ValueError('Email parameter must be a string value.')
|
|
||||||
|
|
||||||
# FIXME: This doesn't seem to be working properly.
|
|
||||||
if balance:
|
|
||||||
if isinstance(balance, dict):
|
|
||||||
if 'operator' in balance and 'value' in balance:
|
|
||||||
if balance['operator'] not in self.VALID_BALANCE_OPERATORS:
|
|
||||||
raise ValueError(f'Invalid balance operator: {balance["operator"]}. Valid options: {self.VALID_BALANCE_OPERATORS}')
|
|
||||||
if not isinstance(balance['value'], int):
|
|
||||||
raise ValueError('Balance value must be an integer.')
|
|
||||||
params['balance'] = f'{balance["operator"]}:{balance["value"]}'
|
|
||||||
else:
|
|
||||||
raise ValueError('Balance must be a dictionary with "operator" and "value" keys.')
|
|
||||||
else:
|
|
||||||
raise ValueError('Balance must be a dictionary with "operator" and "value" keys.')
|
|
||||||
|
|
||||||
if is_deleted is not None:
|
|
||||||
if isinstance(is_deleted, bool):
|
|
||||||
params['is_deleted'] = is_deleted
|
|
||||||
else:
|
|
||||||
raise ValueError('Is deleted must be a boolean value.')
|
|
||||||
|
|
||||||
if filter_deleted_clients is not None:
|
|
||||||
if isinstance(filter_deleted_clients, bool):
|
|
||||||
params['filter_deleted_clients'] = filter_deleted_clients
|
|
||||||
else:
|
|
||||||
raise ValueError('Filter deleted clients must be a boolean value.')
|
|
||||||
|
|
||||||
request = requests.Request('GET', self.clients_url, headers=self.client.headers, params=params)
|
|
||||||
prepared_request = request.prepare()
|
|
||||||
logger.debug(f'Fetching clients from server: {prepared_request.url}')
|
|
||||||
session = requests.Session()
|
|
||||||
response = session.send(prepared_request)
|
|
||||||
logger.debug(f'Server response status: {response.status_code}')
|
|
||||||
|
|
||||||
if response.ok:
|
|
||||||
clients = [Client.from_json(client_dict) for client_dict in response.json().get('data', [])]
|
|
||||||
if clients:
|
|
||||||
logger.success('Fetched client list from server.')
|
|
||||||
return clients
|
|
||||||
else:
|
|
||||||
logger.error('Search yielded no clients.')
|
|
||||||
return []
|
|
||||||
else:
|
|
||||||
logger.error(f'Error fetching clients: {response.text}')
|
|
||||||
response.raise_for_status()
|
|
||||||
|
|
||||||
@ -1,64 +0,0 @@
|
|||||||
import requests
|
|
||||||
|
|
||||||
from pyinvoiceninja.models import Client
|
|
||||||
from pyinvoiceninja.models import Document
|
|
||||||
|
|
||||||
class DocumentsAPI(object):
|
|
||||||
"""
|
|
||||||
A client for interacting with the Invoice Ninja documents API.
|
|
||||||
|
|
||||||
Attributes
|
|
||||||
----------
|
|
||||||
client : InvoiceNinjaClient
|
|
||||||
An instance of the InvoiceNinjaClient class.
|
|
||||||
documents_url : str
|
|
||||||
The URL for accessing the clients endpoint.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, client):
|
|
||||||
"""
|
|
||||||
Constructs all the necessary attributes for the DocumentsAPI object.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
client : InvoiceNinjaClient
|
|
||||||
An instance of the InvoiceNinjaClient class.
|
|
||||||
"""
|
|
||||||
self.client = client
|
|
||||||
self.documents_url = f'{self.client.base_url}/documents'
|
|
||||||
|
|
||||||
def __get_documents_from_response(self, response: requests.Response):
|
|
||||||
"""
|
|
||||||
Parses the response from the API and converts it to a list of Client objects.
|
|
||||||
|
|
||||||
Parameters
|
|
||||||
----------
|
|
||||||
response : requests.Response
|
|
||||||
The response object from the API request.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
list of Client
|
|
||||||
A list of Client objects parsed from the response.
|
|
||||||
"""
|
|
||||||
documents = list()
|
|
||||||
for document_dict in response.json()['data']:
|
|
||||||
documents.append(Document.from_json(document_dict))
|
|
||||||
return documents
|
|
||||||
|
|
||||||
def get_documents(self):
|
|
||||||
"""
|
|
||||||
Retrieves a list of documents from the Invoice Ninja API.
|
|
||||||
|
|
||||||
Returns
|
|
||||||
-------
|
|
||||||
list of Documents
|
|
||||||
A list of Document objects if the request is successful.
|
|
||||||
"""
|
|
||||||
self.client._log_debug(f'Fetching documents from {self.documents_url}')
|
|
||||||
response = requests.get(self.documents_url, headers=self.client.headers)
|
|
||||||
self.client._log_debug(f'Server response: {response.status_code}, {response.text}')
|
|
||||||
|
|
||||||
if response.ok:
|
|
||||||
return self.__get_documents_from_response(response)
|
|
||||||
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
from .client import Client
|
|
||||||
from .clientContact import ClientContact
|
|
||||||
from .clientSettings import ClientSettings
|
|
||||||
from .document import Document
|
|
||||||
|
|
||||||
__all__ = ['Client', 'ClientContact', 'ClientSettings', 'Document']
|
|
||||||
|
|
||||||
@ -1,402 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Type
|
|
||||||
from typing import Union
|
|
||||||
|
|
||||||
# JSON serializer/deserializer
|
|
||||||
import json
|
|
||||||
|
|
||||||
# Local library imports
|
|
||||||
from pyinvoiceninja.models.clientContact import ClientContact
|
|
||||||
from pyinvoiceninja.models.clientInvoice import ClientInvoice
|
|
||||||
from pyinvoiceninja.models.clientPayment import ClientPayment
|
|
||||||
from pyinvoiceninja.models.clientSettings import ClientSettings
|
|
||||||
from pyinvoiceninja.models.document import Document
|
|
||||||
|
|
||||||
class Client:
|
|
||||||
"""
|
|
||||||
A class to represent a client.
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
id (int): The ID of the client.
|
|
||||||
name (str): The name of the client.
|
|
||||||
private_notes (str): Private notes about the client.
|
|
||||||
public_notes (str): Public notes about the client.
|
|
||||||
balance (int): The balance of the client.
|
|
||||||
paid_to_date (int): The amount paid to date by the client.
|
|
||||||
payment_balance (int): The payment balance of the client.
|
|
||||||
credit_balance (int): The credit balance of the client.
|
|
||||||
settings (dict): Client-specific settings.
|
|
||||||
is_deleted (bool): Indicates if the client is deleted.
|
|
||||||
updated_at (int): The timestamp when the client was last updated.
|
|
||||||
archived_at (int): The timestamp when the client was archived.
|
|
||||||
created_at (int): The timestamp when the client was created.
|
|
||||||
display_name (str): The display name of the client.
|
|
||||||
contacts (list): A list of contacts associated with the client.
|
|
||||||
documents (list): A list of documents associated with the client.
|
|
||||||
address (str): The address of the client.
|
|
||||||
city (str): The city of the client.
|
|
||||||
state (str): The state of the client.
|
|
||||||
postal_code (str): The postal code of the client.
|
|
||||||
phone (str): The phone number of the client.
|
|
||||||
custom_value1 (str): Custom field 1.
|
|
||||||
custom_value2 (str): Custom field 2.
|
|
||||||
custom_value3 (str): Custom field 3.
|
|
||||||
custom_value4 (str): Custom field 4.
|
|
||||||
"""
|
|
||||||
|
|
||||||
def __init__(self, client_id: int = 0,
|
|
||||||
name: str = '',
|
|
||||||
private_notes: str = '',
|
|
||||||
public_notes: str = '',
|
|
||||||
balance: int = 0,
|
|
||||||
paid_to_date: int = 0,
|
|
||||||
payment_balance: int = 0,
|
|
||||||
credit_balance: int = 0,
|
|
||||||
settings: Optional[ClientSettings] = None,
|
|
||||||
is_deleted: bool = False,
|
|
||||||
updated_at: int = 0,
|
|
||||||
archived_at: int = 0,
|
|
||||||
created_at: int = 0,
|
|
||||||
display_name: str = '',
|
|
||||||
contacts: Optional[List[ClientContact]] = None,
|
|
||||||
documents: Optional[List[Document]] = None,
|
|
||||||
address: str = '',
|
|
||||||
city: str = '',
|
|
||||||
state: str = '',
|
|
||||||
postal_code: str = '',
|
|
||||||
phone: str = '',
|
|
||||||
custom_value1: str = '',
|
|
||||||
custom_value2: str = '',
|
|
||||||
custom_value3: str = '',
|
|
||||||
custom_value4: str = '',
|
|
||||||
ledger: Optional[List[Union[ClientPayment, ClientInvoice]]] = None):
|
|
||||||
self._id = client_id
|
|
||||||
self._name = name
|
|
||||||
self._private_notes = private_notes
|
|
||||||
self._public_notes = public_notes
|
|
||||||
self._balance = balance
|
|
||||||
self._paid_to_date = paid_to_date
|
|
||||||
self._payment_balance = payment_balance
|
|
||||||
self._credit_balance = credit_balance
|
|
||||||
self._settings = settings or {}
|
|
||||||
self._is_deleted = is_deleted
|
|
||||||
self._updated_at = updated_at
|
|
||||||
self._archived_at = archived_at
|
|
||||||
self._created_at = created_at
|
|
||||||
self._display_name = display_name
|
|
||||||
self._contacts = contacts or []
|
|
||||||
self._documents = documents or []
|
|
||||||
self._address = address
|
|
||||||
self._city = city
|
|
||||||
self._state = state
|
|
||||||
self._postal_code = postal_code
|
|
||||||
self._phone = phone
|
|
||||||
self._custom_value1 = custom_value1
|
|
||||||
self._custom_value2 = custom_value2
|
|
||||||
self._custom_value3 = custom_value3
|
|
||||||
self._custom_value4 = custom_value4
|
|
||||||
self._ledger = ledger
|
|
||||||
|
|
||||||
@property
|
|
||||||
def id(self):
|
|
||||||
return self._id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self):
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@name.setter
|
|
||||||
def name(self, name):
|
|
||||||
self._name = name
|
|
||||||
|
|
||||||
@name.deleter
|
|
||||||
def name(self):
|
|
||||||
self._name = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def private_notes(self):
|
|
||||||
return self._private_notes
|
|
||||||
|
|
||||||
@private_notes.setter
|
|
||||||
def private_notes(self, note):
|
|
||||||
self._private_notes = note
|
|
||||||
|
|
||||||
@private_notes.deleter
|
|
||||||
def private_notes(self):
|
|
||||||
self._private_notes = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def public_notes(self):
|
|
||||||
return self._public_notes
|
|
||||||
|
|
||||||
@public_notes.setter
|
|
||||||
def public_notes(self, note):
|
|
||||||
self._public_notes = note
|
|
||||||
|
|
||||||
@public_notes.deleter
|
|
||||||
def public_notes(self):
|
|
||||||
self._public_notes = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def balance(self):
|
|
||||||
return self._balance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def paid_to_date(self):
|
|
||||||
return self._paid_to_date
|
|
||||||
|
|
||||||
@property
|
|
||||||
def payment_balance(self):
|
|
||||||
return self._payment_balance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def credit_balance(self):
|
|
||||||
return self._credit_balance
|
|
||||||
|
|
||||||
@property
|
|
||||||
def settings(self):
|
|
||||||
return self._settings
|
|
||||||
|
|
||||||
@settings.setter
|
|
||||||
def settings(self, settings):
|
|
||||||
self._settings = settings
|
|
||||||
|
|
||||||
@settings.deleter
|
|
||||||
def settings(self):
|
|
||||||
self._settings = {}
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_deleted(self):
|
|
||||||
return self._is_deleted
|
|
||||||
|
|
||||||
@property
|
|
||||||
def updated_at(self):
|
|
||||||
return self._updated_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def archived_at(self):
|
|
||||||
return self._archived_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def created_at(self):
|
|
||||||
return self._created_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def display_name(self):
|
|
||||||
return self._display_name
|
|
||||||
|
|
||||||
@display_name.setter
|
|
||||||
def display_name(self, name):
|
|
||||||
self._display_name = name
|
|
||||||
|
|
||||||
@display_name.deleter
|
|
||||||
def display_name(self):
|
|
||||||
self._display_name = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contacts(self):
|
|
||||||
return self._contacts
|
|
||||||
|
|
||||||
@contacts.setter
|
|
||||||
def contacts(self, contacts):
|
|
||||||
self._contacts = contacts
|
|
||||||
|
|
||||||
@contacts.deleter
|
|
||||||
def contacts(self):
|
|
||||||
self._contacts = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def documents(self):
|
|
||||||
return self._documents
|
|
||||||
|
|
||||||
@documents.setter
|
|
||||||
def documents(self, documents):
|
|
||||||
self._documents = documents
|
|
||||||
|
|
||||||
@documents.deleter
|
|
||||||
def documents(self):
|
|
||||||
self._documents = []
|
|
||||||
|
|
||||||
@property
|
|
||||||
def address(self):
|
|
||||||
return self._address
|
|
||||||
|
|
||||||
@address.setter
|
|
||||||
def address(self, address):
|
|
||||||
self._address = address
|
|
||||||
|
|
||||||
@address.deleter
|
|
||||||
def address(self):
|
|
||||||
self._address = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def city(self):
|
|
||||||
return self._city
|
|
||||||
|
|
||||||
@city.setter
|
|
||||||
def city(self, city):
|
|
||||||
self._city = city
|
|
||||||
|
|
||||||
@city.deleter
|
|
||||||
def city(self):
|
|
||||||
self._city = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def state(self):
|
|
||||||
return self._state
|
|
||||||
|
|
||||||
@state.setter
|
|
||||||
def state(self, state):
|
|
||||||
self._state = state
|
|
||||||
|
|
||||||
@state.deleter
|
|
||||||
def state(self):
|
|
||||||
self._state = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def postal_code(self):
|
|
||||||
return self._postal_code
|
|
||||||
|
|
||||||
@postal_code.setter
|
|
||||||
def postal_code(self, postal_code):
|
|
||||||
self._postal_code = postal_code
|
|
||||||
|
|
||||||
@postal_code.deleter
|
|
||||||
def postal_code(self):
|
|
||||||
self._postal_code = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value1(self):
|
|
||||||
return self._custom_value1
|
|
||||||
|
|
||||||
@custom_value1.setter
|
|
||||||
def custom_value1(self, value):
|
|
||||||
self._custom_value1 = value
|
|
||||||
|
|
||||||
@custom_value1.deleter
|
|
||||||
def custom_value1(self):
|
|
||||||
self._custom_value1 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value2(self):
|
|
||||||
return self._custom_value2
|
|
||||||
|
|
||||||
@custom_value2.setter
|
|
||||||
def custom_value2(self, value):
|
|
||||||
self._custom_value2 = value
|
|
||||||
|
|
||||||
@custom_value2.deleter
|
|
||||||
def custom_value2(self):
|
|
||||||
self._custom_value2 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value3(self):
|
|
||||||
return self._custom_value3
|
|
||||||
|
|
||||||
@custom_value3.setter
|
|
||||||
def custom_value3(self, value):
|
|
||||||
self._custom_value3 = value
|
|
||||||
|
|
||||||
@custom_value3.deleter
|
|
||||||
def custom_value3(self):
|
|
||||||
self._custom_value3 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value4(self):
|
|
||||||
return self._custom_value4
|
|
||||||
|
|
||||||
@custom_value4.setter
|
|
||||||
def custom_value4(self, value):
|
|
||||||
self._custom_value4 = value
|
|
||||||
|
|
||||||
@custom_value4.deleter
|
|
||||||
def custom_value4(self):
|
|
||||||
self._custom_value4 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def ledger(self):
|
|
||||||
return self._ledger
|
|
||||||
|
|
||||||
@ledger.setter
|
|
||||||
def ledger(self, ledger: List[Dict[str, Any]]):
|
|
||||||
self._ledger = ledger
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls, client: dict):
|
|
||||||
"""
|
|
||||||
Create a Client instance from a JSON-like dictionary.
|
|
||||||
"""
|
|
||||||
contacts = [ClientContact.from_json(contact) for contact in client.get('contacts', [])]
|
|
||||||
documents = [Document.from_json(document) for document in client.get('documents', [])]
|
|
||||||
ledger_processed = []
|
|
||||||
for index, item in enumerate(client.get('ledger', [])):
|
|
||||||
if index % 2 == 0:
|
|
||||||
# Processing ClientInvoice
|
|
||||||
ledger_processed = ClientInvoice.from_json(item)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# Processing ClientPayment
|
|
||||||
ledger_processed = ClientPayment.from_json(item)
|
|
||||||
|
|
||||||
|
|
||||||
return cls(client_id=client['id'],
|
|
||||||
name=client['name'],
|
|
||||||
private_notes=client['private_notes'],
|
|
||||||
public_notes=client['public_notes'],
|
|
||||||
balance=client['balance'],
|
|
||||||
paid_to_date=client['paid_to_date'],
|
|
||||||
payment_balance=client['payment_balance'],
|
|
||||||
credit_balance=client['credit_balance'],
|
|
||||||
settings=ClientSettings.from_json(client['settings']),
|
|
||||||
is_deleted=client['is_deleted'],
|
|
||||||
updated_at=client['updated_at'],
|
|
||||||
archived_at=client['archived_at'],
|
|
||||||
created_at=client['created_at'],
|
|
||||||
display_name=client['display_name'],
|
|
||||||
contacts=contacts,
|
|
||||||
documents=documents,
|
|
||||||
address=client['address1'],
|
|
||||||
city=client['city'],
|
|
||||||
state=client['state'],
|
|
||||||
postal_code=client['postal_code'],
|
|
||||||
phone=client['phone'],
|
|
||||||
custom_value1=client['custom_value1'],
|
|
||||||
custom_value2=client['custom_value2'],
|
|
||||||
custom_value3=client['custom_value3'],
|
|
||||||
custom_value4=client['custom_value4'],
|
|
||||||
ledger=ledger_processed)
|
|
||||||
|
|
||||||
def to_json(self):
|
|
||||||
"""
|
|
||||||
Convert the Client instance to a JSON-like dictionary.
|
|
||||||
"""
|
|
||||||
return json.dumps({'id': self._id,
|
|
||||||
'name': self._name,
|
|
||||||
'private_notes': self._private_notes,
|
|
||||||
'public_notes': self._public_notes,
|
|
||||||
'balance': self._balance,
|
|
||||||
'paid_to_date': self._paid_to_date,
|
|
||||||
'payment_balance': self._payment_balance,
|
|
||||||
'credit_balance': self._credit_balance,
|
|
||||||
'settings': self._settings,
|
|
||||||
'is_deleted': self._is_deleted,
|
|
||||||
'updated_at': self._updated_at,
|
|
||||||
'archived_at': self._archived_at,
|
|
||||||
'created_at': self._created_at,
|
|
||||||
'display_name': self._display_name,
|
|
||||||
'contacts': [contact.to_json() for contact in self._contacts],
|
|
||||||
'documents': [document.to_json() for document in self._documents],
|
|
||||||
'address1': self._address,
|
|
||||||
'city': self._city,
|
|
||||||
'state': self._state,
|
|
||||||
'postal_code': self._postal_code,
|
|
||||||
'phone': self._phone,
|
|
||||||
'custom_value1': self._custom_value1,
|
|
||||||
'custom_value2': self._custom_value2,
|
|
||||||
'custom_value3': self._custom_value3,
|
|
||||||
'custom_value4': self._custom_value4,
|
|
||||||
'ledger': self._ledger})
|
|
||||||
|
|
||||||
@ -1,254 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
|
|
||||||
class ClientContact(object):
|
|
||||||
def __init__(self, contact_id: str = '',
|
|
||||||
first_name: str = '',
|
|
||||||
last_name: str = '',
|
|
||||||
email: str = '',
|
|
||||||
phone: str = '',
|
|
||||||
password: str = '',
|
|
||||||
created_at: int = 0,
|
|
||||||
updated_at: int = 0,
|
|
||||||
archived_at: int = 0,
|
|
||||||
is_primary: bool = False,
|
|
||||||
is_locked: bool = False,
|
|
||||||
send_email: bool = False,
|
|
||||||
contact_key: str = '',
|
|
||||||
last_login: int = 0,
|
|
||||||
portal_link: str = '',
|
|
||||||
custom_value1: str = '',
|
|
||||||
custom_value2: str = '',
|
|
||||||
custom_value3: str = '',
|
|
||||||
custom_value4: str = '') -> None:
|
|
||||||
self._contact_id = contact_id
|
|
||||||
self._first_name = first_name
|
|
||||||
self._last_name = last_name
|
|
||||||
self._email = email
|
|
||||||
self._phone = phone
|
|
||||||
self._password = password
|
|
||||||
self._created_at = created_at
|
|
||||||
self._updated_at = updated_at
|
|
||||||
self._archived_at = archived_at
|
|
||||||
self._is_primary = is_primary
|
|
||||||
self._is_locked = is_locked
|
|
||||||
self._contact_key = contact_key
|
|
||||||
self._last_login = last_login
|
|
||||||
self._send_email = send_email
|
|
||||||
self._portal_link = portal_link
|
|
||||||
self._custom_value1 = custom_value1
|
|
||||||
self._custom_value2 = custom_value2
|
|
||||||
self._custom_value3 = custom_value3
|
|
||||||
self._custom_value4 = custom_value4
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contact_id(self) -> str:
|
|
||||||
return self._contact_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def first_name(self) -> str:
|
|
||||||
return self._first_name
|
|
||||||
|
|
||||||
@first_name.setter
|
|
||||||
def first_name(self, first_name: str) -> None:
|
|
||||||
self._first_name = first_name
|
|
||||||
|
|
||||||
@first_name.deleter
|
|
||||||
def first_name(self) -> None:
|
|
||||||
self._first_name = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def last_name(self) -> str:
|
|
||||||
return self._last_name
|
|
||||||
|
|
||||||
@last_name.setter
|
|
||||||
def last_name(self, last_name: str) -> None:
|
|
||||||
self._last_name = last_name
|
|
||||||
|
|
||||||
@last_name.deleter
|
|
||||||
def last_name(self) -> None:
|
|
||||||
self._last_name = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def email(self) -> str:
|
|
||||||
return self._email
|
|
||||||
|
|
||||||
@email.setter
|
|
||||||
def email(self, email: str) -> None:
|
|
||||||
self._email = email
|
|
||||||
|
|
||||||
@email.deleter
|
|
||||||
def email(self) -> None:
|
|
||||||
self._email = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def phone(self) -> str:
|
|
||||||
return self._phone
|
|
||||||
|
|
||||||
@phone.setter
|
|
||||||
def phone(self, phone: str) -> None:
|
|
||||||
self._phone = phone
|
|
||||||
|
|
||||||
@phone.deleter
|
|
||||||
def phone(self) -> None:
|
|
||||||
self._phone = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def password(self) -> str:
|
|
||||||
return self._password
|
|
||||||
|
|
||||||
@password.setter
|
|
||||||
def password(self, password: str) -> None:
|
|
||||||
self._password = password
|
|
||||||
|
|
||||||
@password.deleter
|
|
||||||
def password(self) -> None:
|
|
||||||
self._password = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def created_at(self) -> int:
|
|
||||||
return self._created_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def updated_at(self) -> int:
|
|
||||||
return self._updated_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def archived_at(self) -> int:
|
|
||||||
return self._archived_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_primary(self) -> bool:
|
|
||||||
return self._is_primary
|
|
||||||
|
|
||||||
@is_primary.setter
|
|
||||||
def is_primary(self, is_primary: bool) -> None:
|
|
||||||
self._is_primary = is_primary
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_locked(self) -> bool:
|
|
||||||
return self._is_locked
|
|
||||||
|
|
||||||
@is_locked.setter
|
|
||||||
def is_locked(self, is_locked: bool) -> None:
|
|
||||||
self._is_locked = is_locked
|
|
||||||
|
|
||||||
@property
|
|
||||||
def contact_key(self) -> str:
|
|
||||||
return self._contact_key
|
|
||||||
|
|
||||||
@property
|
|
||||||
def last_login(self) -> int:
|
|
||||||
return self._last_login
|
|
||||||
|
|
||||||
@property
|
|
||||||
def send_email(self) -> bool:
|
|
||||||
return self._send_email
|
|
||||||
|
|
||||||
@send_email.setter
|
|
||||||
def send_email(self, send_email: bool) -> None:
|
|
||||||
self._send_email = send_email
|
|
||||||
|
|
||||||
@property
|
|
||||||
def portal_link(self) -> str:
|
|
||||||
return self._portal_link
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value1(self) -> str:
|
|
||||||
return self._custom_value1
|
|
||||||
|
|
||||||
@custom_value1.setter
|
|
||||||
def custom_value1(self, value: str) -> None:
|
|
||||||
self._custom_value1 = value
|
|
||||||
|
|
||||||
@custom_value1.deleter
|
|
||||||
def custom_value1(self) -> None:
|
|
||||||
self._custom_value1 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value2(self) -> str:
|
|
||||||
return self._custom_value2
|
|
||||||
|
|
||||||
@custom_value2.setter
|
|
||||||
def custom_value2(self, value: str) -> None:
|
|
||||||
self._custom_value2 = value
|
|
||||||
|
|
||||||
@custom_value2.deleter
|
|
||||||
def custom_value2(self) -> None:
|
|
||||||
self._custom_value2 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value3(self) -> str:
|
|
||||||
return self._custom_value3
|
|
||||||
|
|
||||||
@custom_value3.setter
|
|
||||||
def custom_value3(self, value: str) -> None:
|
|
||||||
self._custom_value3 = value
|
|
||||||
|
|
||||||
@custom_value3.deleter
|
|
||||||
def custom_value3(self) -> None:
|
|
||||||
self._custom_value3 = ''
|
|
||||||
|
|
||||||
@property
|
|
||||||
def custom_value4(self) -> str:
|
|
||||||
return self._custom_value4
|
|
||||||
|
|
||||||
@custom_value4.setter
|
|
||||||
def custom_value4(self, value: str) -> None:
|
|
||||||
self._custom_value4 = value
|
|
||||||
|
|
||||||
@custom_value4.deleter
|
|
||||||
def custom_value4(self) -> None:
|
|
||||||
self._custom_value4 = ''
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls, contact: Dict[str, Any]) -> 'ClientContact':
|
|
||||||
return cls(contact_id=contact['id'],
|
|
||||||
first_name=contact['first_name'],
|
|
||||||
last_name=contact['last_name'],
|
|
||||||
email=contact['email'],
|
|
||||||
created_at=contact['created_at'],
|
|
||||||
updated_at=contact['updated_at'],
|
|
||||||
archived_at=contact['archived_at'],
|
|
||||||
is_primary=contact['is_primary'],
|
|
||||||
is_locked=contact['is_locked'],
|
|
||||||
phone=contact['phone'],
|
|
||||||
custom_value1=contact['custom_value1'],
|
|
||||||
custom_value2=contact['custom_value2'],
|
|
||||||
custom_value3=contact['custom_value3'],
|
|
||||||
custom_value4=contact['custom_value4'],
|
|
||||||
contact_key=contact['contact_key'],
|
|
||||||
send_email=contact['send_email'],
|
|
||||||
last_login=contact['last_login'],
|
|
||||||
password=contact['password'],
|
|
||||||
portal_link=contact['link'])
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json_list(cls, contacts: List[Dict[str, Any]]) -> List['ClientContact']:
|
|
||||||
return [cls.from_json(contact) for contact in contacts]
|
|
||||||
|
|
||||||
def to_json(self) -> str:
|
|
||||||
import json
|
|
||||||
contact = {
|
|
||||||
"contact_id": self._contact_id,
|
|
||||||
"first_name": self._first_name,
|
|
||||||
"last_name": self._last_name,
|
|
||||||
"email": self._email,
|
|
||||||
"phone": self._phone,
|
|
||||||
"created_at": self._created_at,
|
|
||||||
"updated_at": self._updated_at,
|
|
||||||
"archived_at": self._archived_at,
|
|
||||||
"is_primary": self._is_primary,
|
|
||||||
"is_locked": self._is_locked,
|
|
||||||
"send_email": self._send_email,
|
|
||||||
"contact_key": self._contact_key,
|
|
||||||
"last_login": self._last_login,
|
|
||||||
"portal_link": self._portal_link,
|
|
||||||
"custom_value1": self._custom_value1,
|
|
||||||
"custom_value2": self._custom_value2,
|
|
||||||
"custom_value3": self._custom_value3,
|
|
||||||
"custom_value4": self._custom_value4
|
|
||||||
}
|
|
||||||
return json.dumps(contact)
|
|
||||||
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
class ClientInvoice:
|
|
||||||
def __init__(self, invoice_id: Optional[str] = None,
|
|
||||||
notes: Optional[str] = None,
|
|
||||||
balance: Optional[int] = None,
|
|
||||||
adjustment: Optional[int] = None,
|
|
||||||
activity_id: Optional[int] = None,
|
|
||||||
created_at: Optional[int] = None,
|
|
||||||
updated_at: Optional[int] = None,
|
|
||||||
archived_at: Optional[int] = None):
|
|
||||||
self.invoice_id = invoice_id
|
|
||||||
self.notes = notes
|
|
||||||
self.balance = balance
|
|
||||||
self.adjustment = adjustment
|
|
||||||
self.activity_id = activity_id
|
|
||||||
self.created_at = created_at
|
|
||||||
self.updated_at = updated_at
|
|
||||||
self.archived_at = archived_at
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls: Type['ClientInvoice'], invoice: Dict[str, Any]) -> 'ClientInvoice':
|
|
||||||
return cls(invoice_id=invoice.get('invoice_id', None),
|
|
||||||
notes=invoice.get('notes', None),
|
|
||||||
balance=invoice.get('balance', None),
|
|
||||||
adjustment=invoice.get('adjustment', None),
|
|
||||||
activity_id=invoice.get('activity_id', None),
|
|
||||||
created_at=invoice.get('created_at', None),
|
|
||||||
updated_at=invoice.get('updated_at', None),
|
|
||||||
archived_at=invoice.get('archived_at', None))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'Invoice: {self.invoice_id}, {self.balance}'
|
|
||||||
|
|
||||||
def to_json(self) -> str:
|
|
||||||
return json.dumps({'invoice_id': self.invoice_id,
|
|
||||||
'notes': self.notes,
|
|
||||||
'balance': self.balance,
|
|
||||||
'adjustment': self.adjustment,
|
|
||||||
'activity_id': self.activity_id,
|
|
||||||
'created_at': self.created_at,
|
|
||||||
'archive_at': self.archived_at})
|
|
||||||
|
|
||||||
@ -1,48 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
class ClientPayment:
|
|
||||||
def __init__(self, payment_id: Optional[str] = None,
|
|
||||||
notes: Optional[str] = None,
|
|
||||||
balance: Optional[int] = None,
|
|
||||||
adjustment: Optional[int] = None,
|
|
||||||
activity_id: Optional[int] = None,
|
|
||||||
created_at: Optional[int] = None,
|
|
||||||
updated_at: Optional[int] = None,
|
|
||||||
archived_at: Optional[int] = None):
|
|
||||||
self.payment_id = payment_id
|
|
||||||
self.notes = notes
|
|
||||||
self.balance = balance
|
|
||||||
self.adjustment = adjustment
|
|
||||||
self.activity_id = activity_id
|
|
||||||
self.created_at = created_at
|
|
||||||
self.updated_at = updated_at
|
|
||||||
self.archived_at = archived_at
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls: Type['ClientLedger'], ledger: Dict[str, Any]) -> 'ClientLedger':
|
|
||||||
return cls(payment_id=ledger.get('payment_id', None),
|
|
||||||
notes=ledger.get('notes', None),
|
|
||||||
balance=ledger.get('balance', None),
|
|
||||||
adjustment=ledger.get('adjustment', None),
|
|
||||||
activity_id=ledger.get('activity_id', None),
|
|
||||||
created_at=ledger.get('created_at', None),
|
|
||||||
updated_at=ledger.get('updated_at', None),
|
|
||||||
archived_at=ledger.get('archived_at', None))
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'Ledger: {self.payment_id}, {self.balance}'
|
|
||||||
|
|
||||||
def to_json(self) -> str:
|
|
||||||
return json.dumps({'payment_id': self.payment_id,
|
|
||||||
'notes': self.notes,
|
|
||||||
'balance': self.balance,
|
|
||||||
'adjustment': self.adjustment,
|
|
||||||
'activity_id': self.activity_id,
|
|
||||||
'created_at': self.created_at,
|
|
||||||
'archive_at': self.archived_at})
|
|
||||||
|
|
||||||
@ -1,42 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import Optional
|
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
import json
|
|
||||||
|
|
||||||
class ClientSettings:
|
|
||||||
def __init__(self, currency_id: Optional[str] = None,
|
|
||||||
language_id: Optional[str] = None,
|
|
||||||
payment_terms: Optional[int] = None,
|
|
||||||
default_task_rate: Optional[float] = None,
|
|
||||||
send_reminders: Optional[bool] = None,
|
|
||||||
valid_until: Optional[str] = None,
|
|
||||||
enable_client_portal_password: Optional[bool] = None):
|
|
||||||
self.currency_id = currency_id
|
|
||||||
self.language_id = language_id
|
|
||||||
self.payment_terms = payment_terms
|
|
||||||
self.default_task_rate = default_task_rate
|
|
||||||
self.send_reminders = send_reminders
|
|
||||||
self.valid_until = valid_until
|
|
||||||
self.enable_client_portal_password = enable_client_portal_password
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls: Type['ClientSettings'], settings: Dict[str, Any]) -> 'ClientSettings':
|
|
||||||
return cls(currency_id=settings.get('currency_id', None),
|
|
||||||
language_id=settings.get('language_id', None),
|
|
||||||
payment_terms=settings.get('payment_terms', None),
|
|
||||||
default_task_rate=settings.get('default_task_rate', None),
|
|
||||||
send_reminders=settings.get('send_reminders', None),
|
|
||||||
valid_until=settings.get('valid_until', None),
|
|
||||||
enable_client_portal_password=settings.get('enable_client_portal_password', None))
|
|
||||||
|
|
||||||
def to_json(self) -> str:
|
|
||||||
return json.dumps({'currency_id': self.currency_id,
|
|
||||||
'language_id': self.language_id,
|
|
||||||
'payment_terms': self.payment_terms,
|
|
||||||
'default_task_rate': self.default_task_rate,
|
|
||||||
'send_reminders': self.send_reminders,
|
|
||||||
'valid_until': self.valid_until,
|
|
||||||
'enable_client_portal_password': self.enable_client_portal_password})
|
|
||||||
|
|
||||||
@ -1,147 +0,0 @@
|
|||||||
from typing import Any
|
|
||||||
from typing import Dict
|
|
||||||
from typing import List
|
|
||||||
import json
|
|
||||||
|
|
||||||
class Document:
|
|
||||||
def __init__(self, document_id: str = '',
|
|
||||||
user_id: str = '',
|
|
||||||
assigned_user_id: str = '',
|
|
||||||
project_id: str = '',
|
|
||||||
vendor_id: str = '',
|
|
||||||
name: str = '',
|
|
||||||
url: str = '',
|
|
||||||
preview: str = '',
|
|
||||||
document_type: str = '',
|
|
||||||
disk: str = '',
|
|
||||||
document_hash: str = '',
|
|
||||||
is_deleted: bool = False,
|
|
||||||
is_default: bool = False,
|
|
||||||
created_at: int = 0,
|
|
||||||
updated_at: int = 0,
|
|
||||||
deleted_at: int = 0):
|
|
||||||
self._document_id = document_id
|
|
||||||
self._user_id = user_id
|
|
||||||
self._assigned_user_id = assigned_user_id
|
|
||||||
self._project_id = project_id
|
|
||||||
self._vendor_id = vendor_id
|
|
||||||
self._name = name
|
|
||||||
self._url = url
|
|
||||||
self._preview = preview
|
|
||||||
self._document_type = document_type
|
|
||||||
self._disk = disk
|
|
||||||
self._document_hash = document_hash
|
|
||||||
self._is_deleted = is_deleted
|
|
||||||
self._is_default = is_default
|
|
||||||
self._created_at = created_at
|
|
||||||
self._updated_at = updated_at
|
|
||||||
self._deleted_at = deleted_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def document_id(self) -> str:
|
|
||||||
return self._document_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def user_id(self) -> str:
|
|
||||||
return self._user_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def assigned_user_id(self) -> str:
|
|
||||||
return self._assigned_user_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def project_id(self) -> str:
|
|
||||||
return self._project_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def vendor_id(self) -> str:
|
|
||||||
return self._vendor_id
|
|
||||||
|
|
||||||
@property
|
|
||||||
def name(self) -> str:
|
|
||||||
return self._name
|
|
||||||
|
|
||||||
@property
|
|
||||||
def url(self) -> str:
|
|
||||||
return self._url
|
|
||||||
|
|
||||||
@property
|
|
||||||
def preview(self) -> str:
|
|
||||||
return self._preview
|
|
||||||
|
|
||||||
@property
|
|
||||||
def document_type(self) -> str:
|
|
||||||
return self._document_type
|
|
||||||
|
|
||||||
@property
|
|
||||||
def disk(self) -> str:
|
|
||||||
return self._disk
|
|
||||||
|
|
||||||
@property
|
|
||||||
def document_hash(self) -> str:
|
|
||||||
return self._document_hash
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_deleted(self) -> bool:
|
|
||||||
return self._is_deleted
|
|
||||||
|
|
||||||
@property
|
|
||||||
def is_default(self) -> bool:
|
|
||||||
return self._is_default
|
|
||||||
|
|
||||||
@property
|
|
||||||
def created_at(self) -> int:
|
|
||||||
return self._created_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def updated_at(self) -> int:
|
|
||||||
return self._updated_at
|
|
||||||
|
|
||||||
@property
|
|
||||||
def deleted_at(self) -> int:
|
|
||||||
return self._deleted_at
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json(cls, document: Dict[str, Any]) -> 'Document':
|
|
||||||
return cls(document_id=document.get('id', ''),
|
|
||||||
user_id=document.get('user_id', ''),
|
|
||||||
assigned_user_id=document.get('assigned_user_id', ''),
|
|
||||||
project_id=document.get('project_id', ''),
|
|
||||||
vendor_id=document.get('vendor_id', ''),
|
|
||||||
name=document.get('name', ''),
|
|
||||||
url=document.get('url', ''),
|
|
||||||
preview=document.get('preview', ''),
|
|
||||||
document_type=document.get('type', ''),
|
|
||||||
disk=document.get('disk', ''),
|
|
||||||
document_hash=document.get('hash', ''),
|
|
||||||
is_deleted=document.get('is_deleted', False),
|
|
||||||
is_default=document.get('is_default', False),
|
|
||||||
created_at=document.get('created_at', 0),
|
|
||||||
updated_at=document.get('updated_at', 0),
|
|
||||||
deleted_at=document.get('deleted_at', 0))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def from_json_list(cls, documents: List[Dict[str, Any]]) -> List['Document']:
|
|
||||||
return [cls.from_json(document) for document in documents]
|
|
||||||
|
|
||||||
def to_json(self) -> str:
|
|
||||||
return json.dumps({'document_id': self._document_id,
|
|
||||||
'user_id': self._user_id,
|
|
||||||
'assigned_user_id': self._assigned_user_id,
|
|
||||||
'project_id': self._project_id,
|
|
||||||
'vendor_id': self._vendor_id,
|
|
||||||
'name': self._name,
|
|
||||||
'url': self._url,
|
|
||||||
'preview': self._preview,
|
|
||||||
'document_type': self._document_type,
|
|
||||||
'disk': self._disk,
|
|
||||||
'document_hash': self._document_hash,
|
|
||||||
'is_deleted': self._is_deleted,
|
|
||||||
'is_default': self._is_default,
|
|
||||||
'created_at': self._created_at,
|
|
||||||
'updated_at': self._updated_at,
|
|
||||||
'deleted_at': self._deleted_at})
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return f'Document({self._document_id}, {self._name})'
|
|
||||||
|
|
||||||
26
shell.nix
26
shell.nix
@ -1,25 +1,13 @@
|
|||||||
{ pkgs ? import (fetchTarball "https://github.com/NixOS/nixpkgs/archive/nixos-24.05.tar.gz") {} }:
|
{ pkgs ? import <nixpkgs> {} }:
|
||||||
|
|
||||||
|
|
||||||
with pkgs;
|
with pkgs;
|
||||||
pkgs.mkShell {
|
pkgs.mkShell {
|
||||||
nativeBuildInputs = [
|
nativeBuildInputs = [
|
||||||
inlyne # markdown viewer
|
# Python development environment
|
||||||
|
(python3.withPackages(ps: with ps; [
|
||||||
# Python development environment
|
requests
|
||||||
(python3.withPackages(ps: with ps; [
|
]))
|
||||||
# For SDK configuration
|
];
|
||||||
python-dotenv
|
|
||||||
|
|
||||||
# Logging
|
|
||||||
loguru
|
|
||||||
|
|
||||||
# HTTP client
|
|
||||||
requests
|
|
||||||
|
|
||||||
# Testing
|
|
||||||
pytest
|
|
||||||
]))
|
|
||||||
];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +0,0 @@
|
|||||||
# Library imports
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
####
|
|
||||||
# Global fixtures
|
|
||||||
####
|
|
||||||
|
|
||||||
# App client for testing
|
|
||||||
@pytest.fixture(scope='module')
|
|
||||||
def client():
|
|
||||||
app = create_app(config_class='tests/testing_config.py')
|
|
||||||
with app.app_context():
|
|
||||||
yield app
|
|
||||||
|
|
||||||
@ -1,6 +0,0 @@
|
|||||||
# Base URL of an Invoice Ninja instance
|
|
||||||
BASE_URL=
|
|
||||||
|
|
||||||
# API token for an Invoice Ninja instance
|
|
||||||
API_TOKEN=
|
|
||||||
|
|
||||||
Loading…
x
Reference in New Issue
Block a user