Giter Site home page Giter Site logo

pyfortnox's Introduction

pyfortnox

Fortnox API V3 library client for Python

Installation

pyfortnox package can be installed either via pip or easy_install:

$ pip install --upgrade pyfortnox

or

$ easy_install --upgrade pyfortnox

You can install from the source code as well. First clone the repo and then execute:

$ python setup.py install

After installing, import pyfortnox package:

import fortnox

Usage

import fortnox

Build a client

Using this api without authentication gives an error

# Then we instantiate a client (as shown below)
client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)

Client Options

The following options are available while instantiating a client:

  • access_token: Personal access token
  • client_secret: Private/public integration app's client secret
  • base_url: Base url for the api
  • timeout: Request timeout
  • verbose: Verbose/debug mode

Architecture

The library follows few architectural principles you should understand before digging deeper.

  1. Interactions with resources are done via service objects.
  2. Service objects are exposed as properties on client instances.
  3. Service objects expose resource-oriented actions.
  4. Actions return dictionaries that support attribute-style access, a la JavaScript (thanks to Bunch and it's form Munch).

For example, to interact with projects API you will use fortnox.ProjectService, which you can get if you call:

client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
client.projects # fortnox.ProjectService

To retrieve list of resources and use filtering you will call list() method:

client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
client.projects.list(organization_id=google.id, hot=True) # list(dict|Munch)

or simply client.projects.list()

To find custom field by name and its value pass kwargs as an argument:

client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
client.projects.list(**{'ProjectNumber': 1})

To find a resource by its unique identifier use retrieve() method:

client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
client.projects.retrieve(id=1)

When you'd like to create a resource, or update it's attributes you want to use either create() or update() methods. For example if you want to create a new project you will call:

client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
project = client.projects.create(Description='Website design', Status='ONGOING')

project.ProjectNumber = 1
project.Status = 'ONGOING'
project.Description = 'Website redesign'

client.projects.update(project.ProjectNumber, StartDate='2014-02-28')

To destroy a resource use destroy() method:

client = fortnox.Client(
    access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
client.projects.destroy(project.ProjectNumber)

There other non-CRUD operations supported as well. Please contact corresponding service files for in-depth documentation.

Full example

# Obtain an access token using a new authorization code. This access token will
# then be used for any further API calls.
client = fortnox.Client(
    authorization_code='<YOUR_APP_INTEGRATION_AUTHORIZATION_CODE>',
    client_secret='<YOUR_APPS_CLIENT_SECRET>'
)
obtained_token = client.token.access_token()
access_token = obtained_token.AccessToken

Handling Exceptions

When you instantiate a client or make any request via service objects, exceptions can be raised for multiple of reasons e.g. a network error, an authentication error, an invalid param error etc.

Sample below shows how to properly handle exceptions:

try:
    # Instantiate a client.
    client = fortnox.Client(
        access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
        client_secret='<YOUR_APPS_CLIENT_SECRET>'
    )
    project = client.projects.create(Description='Website design', Status='ONGOING')
    print(project)
except fortnox.ConfigurationError as e:
    #  Invalid client configuration option
    pass
except fortnox.ResourceError as e:
    # Resource related error
    print('Http status = ' + e.http_status)
    print('Request ID = ' + e.logref)
    for error in e.errors:
        print('field = ' + error.field)
        print('code = ' + error.code)
        print('message = ' + error.message)
        print('details = ' + error.details)
except fortnox.RequestError as e:
    # Invalid query parameters, authentication error etc.
    pass
except Exception as e:
    # Other kind of exceptioni, probably connectivity related
    pass

Sample below shows how to send files via inbox service with handled exceptions:

try:
    # Instantiate a client.
    client = fortnox.Client(
        access_token='<YOUR_PERSONAL_ACCESS_TOKEN>',
        client_secret='<YOUR_APPS_CLIENT_SECRET>'
    )
    
    from io import BytesIO
    file =  open('/your/local/file/path/voucher_file.jpeg', 'rb')
    
    buffered_file = BytesIO(file.read())
    file_name = 'voucher1.jpg'
    
    voucher_file = client.inbox.create(path='inbox_v', file=buffered_file, file_name=file_name)
    
    print(voucher_file)
except fortnox.ConfigurationError as e:
    #  Invalid client configuration option
    pass
except fortnox.ResourceError as e:
    # Resource related error
    print('Http status = ' + e.http_status)
    print('Request ID = ' + e.logref)
    for error in e.errors:
        print('field = ' + error.field)
        print('code = ' + error.code)
        print('message = ' + error.message)
        print('details = ' + error.details)
except fortnox.RequestError as e:
    # Invalid query parameters, authentication error etc.
    pass
except Exception as e:
    # Other kind of exceptioni, probably connectivity related
    pass

Resources and actions

Documentation for every action can be found under fortnox/services/ files.

To know about available services, see Fortnox's Official Developer Documentation https://developer.fortnox.se/documentation/

N.B. Below services are not implemented in the latest release of pyfortnox:

  1. Digital Receipt
  2. Warehouse Custom Inbound Documents
  3. Warehouse Custom Outbound Documents
  4. Warehouse Information
  5. Warehouse Item Summary
  6. Warehouse Resource Specific Fields

Tests

First you need to set these environment variables:

$ export FORTNOX_ACCESS_TOKEN='YOUR-FORTNOX-ACCESS-TOKEN'
$ export FORTNOX_CLIENT_SECRET='YOUR-FORTNOX-CLIENT-SECRET'

To run all test suites:

$ pytest tests

And to run a single suite:

$ pytest tests/services/employee_services

Thanks

I would like to give huge thanks to my wife, colleagues, mentors and friends for their continuous inspiration and supports to contribute to this package. pyfortnox was named from pythonic fortnox and I was lucky to publish wrapper under pyfortnox name.

Thank You!

License

MIT

Bug Reports

Report here

Contact

Mahmudul Hasan ([email protected])

Powered By

JetBrains Logo

pyfortnox's People

Contributors

akhterarif avatar alimony avatar prantoamt avatar xalien10 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

pyfortnox's Issues

Need to send additional arguments in retrieve method of AccountService class.

Users perhaps needs to submit the financialyear value while retrieving an account.
An example of such account retrieving end-point is provided: https://api.fortnox.se/3/accounts/1010?financialyear=4

However, the retrieve() method of AccountService does not accept additional arguments like list() methods. It only accepts id.

def retrieve(self, id):
        """
        Retrieve a single Accounts

        Returns a single Account according to the unique Account ID provided
        If the specified Account does not exist, this query returns an error

        :calls: ``get /accounts/{id}``
        :param int id: Unique identifier of a Account.
        :return: Dictionary that support attriubte-style access and represent Accounts resource.
        :rtype: dict
        """
        _, _, account = self.http_client.get("/accounts/{id}".format(id=id))
        return account

Add changelog

Would be nice with a changelog file to see what the changes were in each version.

Invoking the list() method of AccountsService class returns only the 1st page's items of paginated response.

Invoking the list() method of AccountsService returns only the 1st page's item of paginated response.

For instance,
if the Fortnox returns the following data:

{
    "MetaInformation": {
        "@TotalResources": 1296,
        "@TotalPages": 13,
        "@CurrentPage": 1
    },
    "Accounts": [
        {
            "@url": "someurl",
            "Active": true,
            "BalanceBroughtForward": 0,
            "CostCenter": "",
            "CostCenterSettings": "ALLOWED",
            "Description": "Utvecklingsutgifter",
            "Number": 1010,
            "Project": "",
            "ProjectSettings": "ALLOWED",
            "SRU": 7201,
            "Year": 4,
            "VATCode": null
        },
       .
       .
       .
        {
            "@url": "someurl",
            "Active": true,
            "BalanceBroughtForward": 0,
            "CostCenter": "",
            "CostCenterSettings": "ALLOWED",
            "Description": "Balanserade utgifter för forskning och utveckling",
            "Number": 1011,
            "Project": "",
            "ProjectSettings": "ALLOWED",
            "SRU": 7201,
            "Year": 4,
            "VATCode": null
        }
}

The list() function returns the current page's items (1st 100 items).

Support for the new OAuth authentication

Fortnox are moving to an OAuth authentication method early in Januaty 2022. I believe the current: User creates an API Code, FN then provides an Access Token 'process' will be removed. Are you intending to add support for the new processes/API?

`CustomerNumber` when creating new customer is ignored

client = fortnox.Client(access_token="foo", client_secret="bar")
new_customer = client.customers.create(CustomerNumber=123, Name="Test Name")
print(new_customer.CustomerNumber)
'3096'

where 3096 is just the next in order, created automatically by Fortnox. I can confirm that calling the API directly with the same data properly creates a new object with the correct passed customer number and name.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.