API client for FreshBooks
Project description
FreshBooks Python SDK
The FreshBooks Python SDK allows you to more easily utilize the FreshBooks API.
Installation
pip install freshbooks-sdk
Usage
See https://freshbooks.github.io/freshbooks-python-sdk/ for module documentation.
Configuring the API client
You can create an instance of the API client in one of two ways:
- By providing your application's OAuth2
client_idandclient_secretand following through the auth flow, which when complete will return an access token - Or if you already have a valid access token, you can instantiate the client directly using that token, however token refresh flows will not function without the application id and secret.
from freshbooks import Client
freshBooksClient = Client(
client_id=<your application id>,
client_secret=<your application secret>,
redirect_uri=<your redirect uri>
)
and then proceed with the auth flow (see below).
Or
from freshbooks import Client
freshBooksClient = Client(
client_id=<your application id>,
access_token=<a valid token>
)
Authoization flow
This is a brief summary of the OAuth2 authorization flow and the methods in the FreshBooks API Client around them. See the FreshBooks API - Authentication documentation.
First, instantiate your Client with client_id, client_secret, and redirect_uri as above.
To get an access token, the user must first authorize your application. This can be done by sending the user to
the FreshBooks authorization page. Once the user has clicked accept there, they will be redirected to your
redirect_uri with an access grant code. The authorization URL can be obtained by calling
freshBooksClient.get_auth_request_url(). This method also accepts a list of scopes that you wish the user to
authorize your application for.
auth_url = freshBooksClient.get_auth_request_url(['user:profile:read', 'user:clients:read'])
Once the user has been redirected to your redirect_uri and you have obtained the access grant code, you can exchange
that code for a valid access token.
auth_results = freshBooksClient.get_access_token(access_grant_code)
This call both sets the access_token, refresh_token, and access_token_expires_at fields on you Client instance,
and returns those values.
>>> auth_results.access_token
<some token>
>>> auth_results.refresh_token
<some refresh token>
>>> auth_results.access_token_expires_at
<datetime object>
When the token expires, it can be refreshed with the refresh_token value in the Client:
>>> auth_results = freshBooksClient.refresh_access_token()
>>> auth_results.access_token
<a new token>
or you can pass the refresh token yourself:
>>> auth_results = freshBooksClient.refresh_access_token(stored_refresh_token)
>>> auth_results.access_token
<a new token>
Current User
FreshBooks users are uniquely identified by their email across our entire product. One user may act on several Businesses in different ways, and our Identity model is how we keep track of it. Each unique user has an Identity, and each Identity has Business Memberships which define the permissions they have.
See FreshBooks API - Business, Roles, and Identity and FreshBooks API - The Identity Model.
The current user can be accessed by:
>>> current_user = freshBooksClient.current_user()
>>> current_user.email
<some email>
>>> current_user.business_memberships
<list of businesses>
Making API Calls
Each resource in the client has provides calls for get, list, create, update and delete calls. Please note that
some API resources are scoped to a FreshBooks account_id while others are scoped to a business_id. In general these
fall along the lines of accounting resources vs projects/time tracking resources, but that is not precise.
client = freshBooksClient.clients.get(account_id, client_user_id)
project = freshBooksClient.projects.get(business_id, project_id)
Get and List
API calls which return a single resource return a Result object with the returned data accessible via attributes.
The raw json-parsed dictionary can also be accessed via the data attribute.
client = freshBooksClient.clients.get(account_id, client_user_id)
assert client.organization == "FreshBooks"
assert client.userid == client_user_id
assert client.data["organization"] == "FreshBooks"
assert client.data["userid"] == client_user_id
vis_state returns an Enum. See FreshBooks API - Active and Deleted Objects
for details.
from freshbooks import VisState
assert client.vis_state == VisState.ACTIVE
assert client.vis_state == 0
assert client.data['vis_state'] == VisState.ACTIVE
assert client.data['vis_state'] == 0
API calls which return a list of resources return a ListResult object. The resources in the list can be accessed by
index and iterated over. Similarly, the raw dictionary can be accessed via the data attribute.
clients = freshBooksClient.clients.list(account_id)
assert clients[0].organization == "FreshBooks"
assert clients.data["clients"][0]["organization"] == "FreshBooks"
for client in clients:
assert client.organization == "FreshBooks"
assert client.data["organization"] == "FreshBooks"
Create, Update, and Delete
API calls to create and update take a dictionary of the resource data. A successful call will return a Result object
as if a get call.
Create:
payload = {"email": "john.doe@abcorp.com"}
new_client = FreshBooksClient.clients.create(account_id, payload)
client_id = new_client.userid
Update:
payload = {"email": "john.doe@abcorp.ca"}
client = freshBooksClient.clients.update(account_id, client_id, payload)
assert client.email == "john.doe@abcorp.ca"
Delete:
client = freshBooksClient.clients.delete(account_id, client_id)
assert client.vis_state == VisState.DELETED
Error Handling
Calls made to the FreshBooks API with a non-2xx response are wrapped in a FreshBooksError exception.
This exception class contains the error message, HTTP response code, FreshBooks-specific error number if one exists,
and the HTTP response body.
Example:
from freshbooks import FreshBooksError
try:
client = freshBooksClient.clients.get(account_id, client_id)
except FreshBooksError as e:
assert str(e) == "Client not found."
assert e.status_code == 404
assert e.error_code == 1012
assert e.raw_response == ("{'response': {'errors': [{'errno': 1012, "
"'field': 'userid', 'message': 'Client not found.', "
"'object': 'client', 'value': '134'}]}}")
Not all resources have full CRUD methods available. For example expense categories have list and get
calls, but are not deletable. If you attempt to call a method that does not exist, the SDK will raise a
FreshBooksNotImplementedError exception, but this is not something you will likely have to account
for outside of development.
Pagination, Filters, and Includes
list calls take a list of builder objects that can be used to paginate, filter, and include
optional data in the response. See FreshBooks API - Parameters documentation.
Pagination
Pagination results are included in list responses in the pages attribute:
>>> clients = freshBooksClient.clients.list(account_id)
>>> clients.pages
PageResult(page=1, pages=1, per_page=30, total=6)
>>> clients.pages.total
6
To make a paginated call, first create a PaginateBuilder object that can be passed into the list method.
>>> from freshbooks import PaginateBuilder
>>> paginator = PaginateBuilder(2, 4)
>>> paginator
PaginateBuilder(page=2, per_page=4)
>>> clients = freshBooksClient.clients.list(account_id, builders=[paginator])
>>> clients.pages
PageResult(page=2, pages=3, per_page=4, total=9)
PaginateBuilder has methods page and per_page to return or set the values. When setting the values the calls
can be chained.
>>> paginator = PaginateBuilder(1, 3)
>>> paginator
PaginateBuilder(page=1, per_page=3)
>>> paginator.page()
1
>>> paginator.page(2).per_page(4)
>>> paginator
PaginateBuilder(page=2, per_page=4)
ListResults can be combined, allowing your to use pagination to get all the results of a resource.
paginator = PaginateBuilder(1, 100)
clients = freshBooksClient.clients.list(self.account_id, builders=[paginator])
while clients.pages.page < clients.pages.pages:
paginator.page(clients.pages.page + 1)
new_clients = freshBooksClient.clients.list(self.account_id, builders=[paginator])
clients = clients + new_clients
Filters
To filter which results are return by list method calls, construct a FilterBuilder and pass that
in the list of builders to the list method.
>>> from freshbooks import FilterBuilder
>>> filter = FilterBuilder()
>>> filter.equals("userid", 123)
>>> clients = freshBooksClient.clients.list(account_id, builders=[filter])
Filters can be builts with the methods: equals, in_list, like, between, and boolean,
which can be chained together.
>>> f = FilterBuilder()
>>> f.like("email_like", "@freshbooks.com")
FilterBuilder(&search[email_like]=@freshbooks.com)
>>> f = FilterBuilder()
>>> f.in_list("clientids", [123, 456]).boolean("active", False)
FilterBuilder(&search[clientids][]=123&search[clientids][]=456&active=False)
>>> f = FilterBuilder()
>>> f.boolean("active", False).in_list("clientids", [123, 456])
FilterBuilder(&active=False&search[clientids][]=123&search[clientids][]=456)
>>> f = FilterBuilder()
>>> f.between("amount", 1, 10)
FilterBuilder(&search[amount_min]=1&search[amount_max]=10)
>>> f = FilterBuilder()
>>> f.between("start_date", date.today())
FilterBuilder(&search[start_date]=2020-11-21)
Includes
To include additional relationships, sub-resources, or data in a response an IncludesBuilder
can be constructed.
>>> from freshbooks import IncludesBuilder
>>> includes = IncludesBuilder()
>>> includes.include("outstanding_balance")
IncludesBuilder(&include[]=outstanding_balance)
Which can then be passed into list or get calls:
>>> clients = freshBooksClient.clients.list(account_id, builders=[includes])
>>> clients[0].outstanding_balance
[{'amount': {'amount': '100.00', 'code': 'USD'}}]
>>> client = freshBooksClient.clients.get(account_id, client_id, includes=includes)
>>> client.outstanding_balance
[{'amount': {'amount': '100.00', 'code': 'USD'}}]
Includes can also be passed into create and update calls to include the data in the response of the updated resource:
>>> payload = {"email": "john.doe@abcorp.com"}
>>> new_client = FreshBooksClient.clients.create(account_id, payload, includes=includes)
>>> new_client.outstanding_balance
[] # New client has no balance
Sorting
TODO:
Dates and Times
For historical reasons, some resources in the FreshBooks API (mostly accounting-releated) return date/times in
"US/Eastern" timezone. Some effort is taken to return datetime objects as zone-aware and normalized to UTC. In these
cases, the raw response string will differ from the attribute. For example:
from datetime import datetime, timezone
assert client.data["updated"] == "2021-04-16 10:31:59" # Zone-naive string in "US/Eastern"
assert client.updated.isoformat() == '2021-04-16T14:31:59+00:00' # Zone-aware datetime in UTC
assert client.updated == datetime(year=2021, month=4, day=16, hour=14, minute=31, second=59, tzinfo=timezone.utc)
Development
Testing
To run all tests:
make test
To run a single test with pytest:
py.test path/to/test/file.py
py.test path/to/test/file.py::TestClass::test_case
Documentations
You can generate the documentation via:
make generate-docs
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file freshbooks-sdk-1.0.0.tar.gz.
File metadata
- Download URL: freshbooks-sdk-1.0.0.tar.gz
- Upload date:
- Size: 24.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.7.1 importlib_metadata/4.8.2 pkginfo/1.8.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b403c306179605cafafd81d1c42444b579edacebd6f7f160a4efca380353cef1
|
|
| MD5 |
9f32d93aad1c464dcb34144361226b3a
|
|
| BLAKE2b-256 |
2ffcb65081332ff89a4eabd384d095da49530eb8ae05bc86e2b5614e30a539c0
|
File details
Details for the file freshbooks_sdk-1.0.0-py2.py3-none-any.whl.
File metadata
- Download URL: freshbooks_sdk-1.0.0-py2.py3-none-any.whl
- Upload date:
- Size: 29.3 kB
- Tags: Python 2, Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.7.1 importlib_metadata/4.8.2 pkginfo/1.8.2 requests/2.26.0 requests-toolbelt/0.9.1 tqdm/4.62.3 CPython/3.10.0
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
71ffab2b424b6d911d852db5ce061815cc9a573c37e4e457019d6541f254a651
|
|
| MD5 |
a9e7fb6416b5014cbdccb2f3c12b9bb1
|
|
| BLAKE2b-256 |
64f445fbf3fc484e6c06bced6501016493efb6c174cbf196b1b08530533c73af
|