Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

exactonline

Package Overview
Dependencies
Maintainers
2
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

exactonline

Exact Online REST API Library in Python

  • 0.4.0
  • PyPI
  • Socket score

Maintainers
2

|EXACTAPI|

Exact Online REST API Library in Python

.. image:: https://bettercodehub.com/edge/badge/ossobv/exactonline :alt: BetterCodeHub rating

.. image:: https://api.travis-ci.com/ossobv/exactonline.svg :alt: Continuous Integration Status

Exact Online provides accounting software in a Software-as-a-Service delivery model. It implements an API through a REST interface. This library aims to ease its use.

Quick jump

  • Usage by example_
  • Using element adapters_
  • Setting up the link_
  • Implemented resources_
  • Other benefits_
  • License_
  • TODO_
  • Further reading_

Usage by example

Set up the basics:

.. code-block:: python

from exactonline.api import ExactApi
from exactonline.exceptions import ObjectDoesNotExist
from exactonline.resource import GET, POST, PUT, DELETE
from exactonline.storage.ini import IniStorage

# Create a function to get the api with your own storage backend.
def get_api():
    # NOTE: The IniStorage is really simple does not synchronize with
    # other instances. You should create your own storage. See below.
    storage = IniStorage('/path/to/config.ini')
    return ExactApi(storage=storage)
api = get_api()

Get an invoice:

.. code-block:: python

# Get an invoice by your own invoice number (YourRef).
# Returns a dictionary, or raises ObjectDoesNotExist.
invoice = api.invoices.get(invoice_number='F0005555')

It looks somewhat like this:

.. code-block:: python

invoice == {
    u'AmountDC': 50.8,
    u'AmountFC': 50.8,
# ...
    u'SalesEntryLines': [
        {u'AmountDC': 41.98,
         u'AmountFC': 41.98,
# ...
         u'Description': u'Omzet backups',
         u'VATBaseAmountDC': 41.98,
         u'VATBaseAmountFC': 41.98},
    ],
# ...
    u'VATAmountDC': 8.82,
    u'VATAmountFC': 8.82,
    u'YourRef': u'F0005555',
    u'__metadata': {u'type': u'Exact.Web.Api.Models.SalesEntry',
                    u'uri': u"https://start.exactonline.nl/api/v1/..."},
}

Get relations:

.. code-block:: python

relations_limit_2 = api.relations.filter(top=2)
# that was cheaper than: api.relations.all()[0:2]

relations_limit_2 == [
    {u'Code': u'              1068',
     u'ID': u'11111111-2222-3333-4444-555555555555',
     u'Name': u'ACME Corporation',
     u'__metadata': {u'type': u'Exact.Web.Api.Models.Account',
                     u'uri': u"https://start.exactonline.nl/api/v1/...')"}},
    {u'Code': u'               555',
     u'ID': u'22222222-3333-4444-5555-666666666666',
     u'Name': u'Daffy Duck Ltd.',
     u'__metadata': {u'type': u'Exact.Web.Api.Models.Account',
                     u'uri': u"https://start.exactonline.nl/api/v1/...')"}}
]

Update a relation:

.. code-block:: python

daffy_duck = api.relations.get(relation_code='555')
api.relations.update(daffy_duck['ID'], {'Name': 'Daffy Duck and sons'})

Delete a relation:

.. code-block:: python

daffy_duck = api.relations.get(relation_code='555')
api.relations.delete(daffy_duck['ID'])

Create an invoice:

.. code-block:: python

customer_data = api.relations.get(relation_code='123')  # local relation_code
customer_guid = customer_data['ID']
invoice_data = {
    'AmountDC': str(amount_with_vat),  # DC = default currency
    'AmountFC': str(amount_with_vat),  # FC = foreign currency
    'EntryDate': invoice_date.strftime('%Y-%m-%dT%H:%M:%SZ'),  # pretend we're in UTC
    'Customer': customer_guid,
    'Description': u'Invoice description',
    'Journal': remote_journal,  # 70 "Verkoopboek"
    'ReportingPeriod': invoice_date.month,
    'ReportingYear': invoice_date.year,
    'SalesEntryLines': [],
    'VATAmountDC': str(vat_amount),
    'VATAmountFC': str(vat_amount),
    'YourRef': local_invoice_number,
    # must start uniquely at the start of a year, defaults to:
    # YYJJ0001 where YY=invoice_date.year, and JJ=remote_journal
    'InvoiceNumber': '%d%d%04d' % (invoice_date.year, remote_journal,
                                   int(local_invoice_number)),
}
# The SalesEntryLines need to be filled with a bunch of dictionaries
# with these keys: AmountDC, AmountFC, Description, GLAccount,
# VATCode where GLAccount holds the Journal remote GUID, and the
# amounts are without VAT.

api.invoices.create(invoice_dict)

You may need to play around a bit to find out which fields are mandatory, and what kind of values the fields need. The Exact Online REST resources list_ isn't always clear on that.

Using element adapters

Using the above works, but it's not really object oriented. If available, you may be better off using one of the adaptable classes in exactonline.elements and subclassing that.

For example, this is how you could create your own interface to an Exact Online customer.

.. code-block:: python

# Assuming you have a MyRelation that looks like this:
class MyRelation(object):
    relcode = 12345
    first_name = 'John'
    last_name = 'Doe'
    billing_address = None
    # ...

# You could create an adapter subclass of ExactCustomer like this:
class MyExactCustomer(ExactCustomer):
    def __init__(self, my_relation=None, **kwargs):
        super(MyExactCustomer, self).__init__(**kwargs)
        self._my_relation = my_relation

    def get_code(self):
        return str(self._my_relation.relcode)

    def get_name(self):
        return ' '.join([
            self._my_relation.first_name,
            self._my_relation.last_name])

    def get_address(self):
        address = self._my_relation.billing_address
        if address:
            return {
                'AddressLine1': address.street_and_number(),
                'Postcode': address.zipcode,
                'City': address.city.name,
            }
        return {}

If you have the above set up, and have unique customer codes, then writing/updating an Exact Online relation is as convenient as this:

.. code-block:: python

johndoe = MyRelation(...)
exactonline_relation = MyExactCustomer(my_relation=johndoe, api=api)
ret = exactonline_relation.commit()

These adaptable elements are currently implemented for writing customers (ExactCustomer) and invoices (ExactInvoice). See the files in exactonline/elements/ for more info.

You'll need a storage backend. The default IniStorage can be taken from exactonline.storage.

.. code-block:: python

from exactonline.storage.ini import IniStorage

class MyIniStorage(IniStorage):
    def get_response_url(self):
        "Configure your custom response URL."
        return self.get_base_url() + '/oauth/success/'

storage = MyIniStorage('/path/to/config.ini')

.. note:: The IniStorage does not synchronize with other instances

*This means that you cannot use multiple IniStorage instances at the same
time. If you want that, or thread-safe/multi-process-safe operations, or
better yet, integration with your project storage (database?), you should
create your custom storage backend. Look in ``exactonline/storage/base.py``
to see how. It's really easy.*

You need to set up access to your Exact Online SaaS instance, by creating an export link. See creating Exact Online credentials_ for more info.

Take that info, and configure it in your config.ini.

.. code-block:: ini

[server]
auth_url = https://start.exactonline.co.uk/api/oauth2/auth
rest_url = https://start.exactonline.co.uk/api
token_url = https://start.exactonline.co.uk/api/oauth2/token

[application]
base_url = https://example.com
client_id = {12345678-abcd-1234-abcd-0123456789ab}
client_secret = ZZZ999xxx000
; optional config:
iteration_limit = 50

Create an initial URL:

.. code-block:: python

api = ExactApi(storage=storage)
url = api.create_auth_request_url()

The URL will look like this; redirect the user there so he may authenticate and allow your application access to Exact Online (this is OAuth)::

https://start.exactonline.nl/api/oauth2/auth?
  client_id=%7B12345678-abcd-1234-abcd-0123456789ab%7D&
  redirect_uri=https%3A//example.com/oauth/success/&
  response_type=code

After authentication he will get redirected back to::

https://example.com/oauth/success/?code=...

You should implement a view on that URL, that does basically this:

.. code-block:: python

api.request_token(code)

At this point, you should configure your default division, if you haven't already:

.. code-block:: python

division_choices, current_division = api.get_divisions()
api.set_division(division_choices[0][0])  # select ID of first division

Now you're all set!

Implemented resources

View exactonline/api/__init__.py to see which resource helpers are implemented.

Currently, it looks like this:

.. code-block:: python

contacts = Contacts.as_property()
invoices = Invoices.as_property()
ledgeraccounts = LedgerAccounts.as_property()
receivables = Receivables.as_property()
relations = Relations.as_property()
vatcodes = VatCodes.as_property()

But you can call resources which don't have a helper directly. The following two three are equivalent:

.. code-block:: python

api.relations.all()
api.restv1(GET('crm/Accounts'))
api.rest(GET('v1/%d/crm/Accounts' % selected_division))

As are the following three:

.. code-block:: python

api.relations.filter(top=2)
api.restv1(GET('crm/Accounts?$top=2'))
api.rest(GET('v1/%d/crm/Accounts?$top=2' % selected_division))

And these:

.. code-block:: python

api.invoices.filter(filter="EntryDate gt datetime'2015-01-01'")
api.restv1(GET('salesentry/SalesEntries?' +
  '$filter=EntryDate%20gt%20datetime%272015-01-01%27'))
api.rest(GET('v1/%d/salesentry/SalesEntries?' +
  '$filter=EntryDate%%20gt%%20datetime%%272015-01-01%%27' %
  selected_division)
# convinced yet that the helpers are useful?

See the Exact Online REST resources list_ for all available resources.

Other benefits

The ExactApi class ensures that:

  • Tokens are refreshed as needed (see: exactonline/api/autorefresh.py).
  • Paginated lists are automatically downloaded in full (see: exactonline/api/unwrap.py).

Creating Exact Online credentials

Previously, one could create an API from the Exact Online interface directly. This was removed at some point between 2014 and 2015.

According to the "how can I create an application key?" FAQ entry_ you must now create one through the App Center.

*Why am I unable to see the Register an API link and how can I
create an application key?*

All registrations are now configured through the App Center.
Previously you were able to generate an Application Key and/or create an
OAuth registration within your Exact Online.

In Exact Online you can create an app registration for private use
(customer account) or an app registration for commercial use (partner
account). Go to Target groups and site maps for more information.

If the Register API Key link is not visible in the App Center
menu you do not have the correct rights to view it. To make the
link visible go to, Username > My Exact Online > Rights and
select Manage subscription.

Log into the Exact Online App Center_, click MANAGE APPS (APPS BEHEREN); it should be a large link visible on the Top Right. Make sure the redirect URI has the same transport+domainname as the site that you wish to connect.

For sites with an internal URI only, you may need to alter the hostname temporarily when registering. Generate the register URL with api.create_auth_request_url and alter it as appropriate.

After creating the App, you can go back and fetch the the Client ID and the Client secret.

License

Exact Online REST API Library in Python is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, version 3 or any later version.

TODO

  • Fix so file Copyright headers are auto-populated (and date-updated).

Further reading

  • Exact Online REST API_.
  • Exact Online REST resources list_.
  • Tips by Bas van Beek_.

.. _Exact Online App Center: https://apps.exactonline.com/ .. _Exact Online REST API: https://developers.exactonline.com/#RestIntro.html%3FTocPath%3DExact%2520Online%2520REST%2520API%7C_____0 .. _Exact Online REST resources list: https://start.exactonline.co.uk/docs/HlpRestAPIResources.aspx?SourceAction=10 .. _Tips by Bas van Beek: http://www.basvanbeek.nl/exact-online-tips/

.. _"how can I create an application key?" FAQ entry: https://developers.exactonline.com/#FAQ_General.htm%3FTocPath%3DApp%2520Center%7C_____5

.. |EXACTAPI| image:: assets/exactapi-head.png :alt: ExactAPI

Changes

  • v0.4.0:

    • Added ratelimiter, requested and tested by various people.

    • Added debug logs, by Martijn Hemeryck (@mhemeryck).

    • Cleanup. Also by Eike Dehling (@EikeDehling).

  • v0.3.6:

    • Try refreshing tokens before receiving a 401 per the request of Alexandar Mechev (@apmechev).
  • v0.3.5:

    • Implement quotations. Thanks Ties (@ties-v).

    • Updated/added assets, tests and docs.

  • v0.3.4:

    • Added bankaccounts manager. Closes #21. Thanks Edwin van de Ven (@edwinvandeven).

    • Added global configurable iteration limit parameter.

      Default unpagination was limited to 50 to avoid infinite loops in case of bugs or bad data. Some users require more pages of output.

      Set the iteration_limit in your config storage to a higher value if you run into "Iteration ... limit reached!".

      Closes #22. Thanks George Viorel Visniuc (@gvisniuc).

    • Move storage/config backends to separate storage subdir. This way we don't require the configparse if you're not using the IniStorage. Shows a DeprecationWarning so you have time to update your code.

      Closes #19.

  • v0.3.3:

    • Additions to ExactInvoice element:

      • Add optional "month" to get_ledger_lines() for accrued/deferred income.
      • Add get_vatcode_for_ledger_line() for customizable VATCodes.
  • v0.3.2:

    • Fix ConfigParser deprecationwarning.
    • Add VatCodes API support. Thanks Stani Michiels (@stanim). Closes #12.
  • v0.3.1:

    • Add Contacts API support. Thanks @Imperatus. Closes #8 and #11.
  • v0.3.0:

    • Refactor code to use fewer arguments. This makes the BetterCodeHub checks happy and that makes me happy. This is a BACKWARDS INCOMPATIBLE CHANGE. All your code that looked like this::

      ret = self.rest('GET', 'v1/current/Me?$select=CurrentDivision')
      

      will have to be changed to this::

      from exactonline.resource import GET
      ret = self.rest(GET('v1/current/Me?$select=CurrentDivision'))
      

      Closes #7.

    • Add IniStorage default return value for get_default_url(), returning get_base_url(). This makes life easier for those who did not read the README. Closes #6.

  • v0.2.5:

    • Fix set_tokens() bug in Python3; make sure json.loads() gets an unistr. Reported by @Imperatus, @LordGaav. Closes #9 and #10.
  • v0.2.4:

    • Improve HTTP functions for better error reporting and better Py23 compatibility.
  • v0.2.3:

    • Add api.invoices.filter(invoice_number__in=LIST) filter.
    • Iterate over resultsets instead of using recursion (api.unwrap).
    • Fix python3 compatibility issue: bytestring found on autorefresh. Thanks @wpxgit.
  • v0.2.2:

    • Correct RST source in CHANGES file. Did not display properly on PyPI.
  • v0.2.1:

    • Correct example Python code in docs. Thanks @alexBaizeau.
    • Add exactonline.elements that were missing in the 0.2.0 distributed package. Thanks @hcwsegers.
    • Add overridable get_ledger_code_to_guid_maps() helper to the invoices elements class.
  • v0.2.0:

    • Update build/PyPI info: move to Stable, add Python 3.4 and 3.5 version, fix Python 3 compatibility.
    • Fix a few README RST issues. Update OAuth documentation.
    • Add initial exactonline.elements to use for easier object oriented data submissions.
    • Add and improve SSL, API and Python3 tests.
  • v0.1.3:

    • Add receivables manager to the API. This manager allows you to build a list similar to the Outstanding Receivables page of Financial Reporting.
    • Add api.invoices.map_exact2foreign_invoice_numbers and api.invoices.map_foreign2exact_invoice_numbers methods to quickly get a mapping between our own and the ExactOnline invoice numbers.
    • Python3 compatibility.
    • Minor fixes.

FAQs


Did you know?

Socket

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Install

Related posts

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc