freshbooks-sdk


Namefreshbooks-sdk JSON
Version 1.2.1 PyPI version JSON
download
home_pagehttps://github.com/freshbooks/freshbooks-python-sdk
SummaryAPI client for FreshBooks
upload_time2023-04-24 13:15:07
maintainerFreshBooks
docs_urlNone
authorAndrew McIntosh
requires_python
licenseMIT
keywords freshbooks
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # FreshBooks Python SDK

[![PyPI](https://img.shields.io/pypi/v/freshbooks-sdk)](https://pypi.org/project/freshbooks-sdk/)
![PyPI - Python Version](https://img.shields.io/pypi/pyversions/freshbooks-sdk)
[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/freshbooks/freshbooks-python-sdk/Run%20Tests)](https://github.com/freshbooks/freshbooks-python-sdk/actions?query=workflow%3A%22Run+Tests%22)

The FreshBooks Python SDK allows you to more easily utilize the [FreshBooks API](https://www.freshbooks.com/api).

## Installation

```bash
pip install freshbooks-sdk
```

## Usage

See the [full documentation](https://freshbooks-python-sdk.readthedocs.io/) or check out some of our [examples](https://github.com/freshbooks/freshbooks-python-sdk/tree/main/examples).

### 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_id` and `client_secret` and 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.

```python
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

```python
from freshbooks import Client

freshBooksClient = Client(
    client_id=<your application id>,
    access_token=<a valid token>
)
```

#### Authorization 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](https://www.freshbooks.com/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.

```python
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.

```python
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.

```python
>>> 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:

```python
>>> auth_results = freshBooksClient.refresh_access_token()
>>> auth_results.access_token
<a new token>
```

or you can pass the refresh token yourself:

```python
>>> 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](https://www.freshbooks.com/api/me_endpoint) and
[FreshBooks API - The Identity Model](https://www.freshbooks.com/api/identity_model).

The current user can be accessed by:

```python
>>> 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 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.

```python
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.

```python
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](https://www.freshbooks.com/api/active_deleted)
for details.

```python
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.

```python
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:

```python
payload = {"email": "john.doe@abcorp.com"}
new_client = FreshBooksClient.clients.create(account_id, payload)

client_id = new_client.userid
```

Update:

```python
payload = {"email": "john.doe@abcorp.ca"}
client = freshBooksClient.clients.update(account_id, client_id, payload)

assert client.email == "john.doe@abcorp.ca"
```

Delete:

```python
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:

```python
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](https://www.freshbooks.com/api/parameters) documentation.

##### Pagination

Pagination results are included in `list` responses in the `pages` attribute:

```python
>>> 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.

```python
>>> 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.

```python
>>> 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.

```python
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.

```python
>>> from freshbooks import FilterBuilder

>>> filter = FilterBuilder()
>>> filter.equals("userid", 123)

>>> clients = freshBooksClient.clients.list(account_id, builders=[filter])
```

Filters can be built with the methods: `equals`, `in_list`, `like`, `between`, and `boolean`,
which can be chained together.

Please see [FreshBooks API - Active and Deleted Objects](https://www.freshbooks.com/api/active_deleted)
for details on filtering active, archived, and deleted resources.

```python
>>> f = FilterBuilder()
>>> f.in_list("clientids", [123, 456])
FilterBuilder(&search[clientids][]=123&search[clientids][]=456)

>>> f = FilterBuilder()
>>> f.like("email_like", "@freshbooks.com")
FilterBuilder(&search[email_like]=@freshbooks.com)

>>> f = FilterBuilder()
>>> f.between("amount", 1, 10)
FilterBuilder(&search[amount_min]=1&search[amount_max]=10)

>>> f = FilterBuilder()
>>> f.between("amount", min=15)  # For just minimum
FilterBuilder(&search[amount_min]=15)

>>> f = FilterBuilder()
>>> f.between("amount_min", 15)  # Alternatively
FilterBuilder(&search[amount_min]=15)

>>> f = FilterBuilder()
>>> f.between("start_date", date.today())
FilterBuilder(&search[start_date]=2020-11-21)

>>> f = FilterBuilder()
>>> f.boolean("complete", False) # Boolean filters are mostly used on Project-like resources
FilterBuilder(&complete=False)

>>> last_week = date.today() - timedelta(days=7)
>>> f = FilterBuilder()
>>> f.equals("vis_state", VisState.ACTIVE).between("updated", last_week, date.today()) # Chaining filters
FilterBuilder(&search[vis_state]=0&search[updated_min]=2020-11-14&search[updated_max]=2020-11-21)
```

##### Includes

To include additional relationships, sub-resources, or data in a response an `IncludesBuilder`
can be constructed.

```python
>>> from freshbooks import IncludesBuilder

>>> includes = IncludesBuilder()
>>> includes.include("outstanding_balance")
IncludesBuilder(&include[]=outstanding_balance)
```

Which can then be passed into `list` or `get` calls:

```python
>>> 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:

```python
>>> 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:

```python
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:

```bash
make test
```

To run a single test with pytest:

```bash
py.test path/to/test/file.py
py.test path/to/test/file.py::TestClass::test_case
```

### Documentations

You can generate the documentation via:

```bash
make generate-docs
```

            

Raw data

            {
    "_id": null,
    "home_page": "https://github.com/freshbooks/freshbooks-python-sdk",
    "name": "freshbooks-sdk",
    "maintainer": "FreshBooks",
    "docs_url": null,
    "requires_python": "",
    "maintainer_email": "dev@freshbooks.com",
    "keywords": "FreshBooks",
    "author": "Andrew McIntosh",
    "author_email": "andrew@amcintosh.net",
    "download_url": "https://files.pythonhosted.org/packages/cb/de/c85523946c89fe865fcc5079a8b192ce83804024c8aff71dc482d0331bc3/freshbooks-sdk-1.2.1.tar.gz",
    "platform": null,
    "description": "# FreshBooks Python SDK\n\n[![PyPI](https://img.shields.io/pypi/v/freshbooks-sdk)](https://pypi.org/project/freshbooks-sdk/)\n![PyPI - Python Version](https://img.shields.io/pypi/pyversions/freshbooks-sdk)\n[![GitHub Workflow Status](https://img.shields.io/github/workflow/status/freshbooks/freshbooks-python-sdk/Run%20Tests)](https://github.com/freshbooks/freshbooks-python-sdk/actions?query=workflow%3A%22Run+Tests%22)\n\nThe FreshBooks Python SDK allows you to more easily utilize the [FreshBooks API](https://www.freshbooks.com/api).\n\n## Installation\n\n```bash\npip install freshbooks-sdk\n```\n\n## Usage\n\nSee the [full documentation](https://freshbooks-python-sdk.readthedocs.io/) or check out some of our [examples](https://github.com/freshbooks/freshbooks-python-sdk/tree/main/examples).\n\n### Configuring the API client\n\nYou can create an instance of the API client in one of two ways:\n\n- By providing your application's OAuth2 `client_id` and `client_secret` and following through the auth flow, which when\ncomplete will return an access token\n- Or if you already have a valid access token, you can instantiate the client directly using that token, however token\nrefresh flows will not function without the application id and secret.\n\n```python\nfrom freshbooks import Client\n\nfreshBooksClient = Client(\n    client_id=<your application id>,\n    client_secret=<your application secret>,\n    redirect_uri=<your redirect uri>\n)\n```\n\nand then proceed with the auth flow (see below).\n\nOr\n\n```python\nfrom freshbooks import Client\n\nfreshBooksClient = Client(\n    client_id=<your application id>,\n    access_token=<a valid token>\n)\n```\n\n#### Authorization flow\n\n_This is a brief summary of the OAuth2 authorization flow and the methods in the FreshBooks API Client\naround them. See the [FreshBooks API - Authentication](https://www.freshbooks.com/api/authentication) documentation._\n\nFirst, instantiate your Client with `client_id`, `client_secret`, and `redirect_uri` as above.\n\nTo get an access token, the user must first authorize your application. This can be done by sending the user to\nthe FreshBooks authorization page. Once the user has clicked accept there, they will be redirected to your\n`redirect_uri` with an access grant code. The authorization URL can be obtained by calling\n`freshBooksClient.get_auth_request_url()`. This method also accepts a list of scopes that you wish the user to\nauthorize your application for.\n\n```python\nauth_url = freshBooksClient.get_auth_request_url(['user:profile:read', 'user:clients:read'])\n```\n\nOnce the user has been redirected to your `redirect_uri` and you have obtained the access grant code, you can exchange\nthat code for a valid access token.\n\n```python\nauth_results = freshBooksClient.get_access_token(access_grant_code)\n```\n\nThis call both sets the `access_token`, `refresh_token`, and `access_token_expires_at` fields on you Client instance,\nand returns those values.\n\n```python\n>>> auth_results.access_token\n<some token>\n\n>>> auth_results.refresh_token\n<some refresh token>\n\n>>> auth_results.access_token_expires_at\n<datetime object>\n```\n\nWhen the token expires, it can be refreshed with the `refresh_token` value in the Client:\n\n```python\n>>> auth_results = freshBooksClient.refresh_access_token()\n>>> auth_results.access_token\n<a new token>\n```\n\nor you can pass the refresh token yourself:\n\n```python\n>>> auth_results = freshBooksClient.refresh_access_token(stored_refresh_token)\n>>> auth_results.access_token\n<a new token>\n```\n\n### Current User\n\nFreshBooks users are uniquely identified by their email across our entire product. One user may act on several\nBusinesses in different ways, and our Identity model is how we keep track of it. Each unique user has an Identity, and\neach Identity has Business Memberships which define the permissions they have.\n\nSee [FreshBooks API - Business, Roles, and Identity](https://www.freshbooks.com/api/me_endpoint) and\n[FreshBooks API - The Identity Model](https://www.freshbooks.com/api/identity_model).\n\nThe current user can be accessed by:\n\n```python\n>>> current_user = freshBooksClient.current_user()\n>>> current_user.email\n<some email>\n\n>>> current_user.business_memberships\n<list of businesses>\n```\n\n### Making API Calls\n\nEach resource in the client provides calls for `get`, `list`, `create`, `update` and `delete` calls. Please note that\nsome API resources are scoped to a FreshBooks `account_id` while others are scoped to a `business_id`. In general these\nfall along the lines of accounting resources vs projects/time tracking resources, but that is not precise.\n\n```python\nclient = freshBooksClient.clients.get(account_id, client_user_id)\nproject = freshBooksClient.projects.get(business_id, project_id)\n```\n\n#### Get and List\n\nAPI calls which return a single resource return a `Result` object with the returned data accessible via attributes.\nThe raw json-parsed dictionary can also be accessed via the `data` attribute.\n\n```python\nclient = freshBooksClient.clients.get(account_id, client_user_id)\n\nassert client.organization == \"FreshBooks\"\nassert client.userid == client_user_id\n\nassert client.data[\"organization\"] == \"FreshBooks\"\nassert client.data[\"userid\"] == client_user_id\n```\n\n`vis_state` returns an Enum. See [FreshBooks API - Active and Deleted Objects](https://www.freshbooks.com/api/active_deleted)\nfor details.\n\n```python\nfrom freshbooks import VisState\n\nassert client.vis_state == VisState.ACTIVE\nassert client.vis_state == 0\nassert client.data['vis_state'] == VisState.ACTIVE\nassert client.data['vis_state'] == 0\n```\n\nAPI calls which return a list of resources return a `ListResult` object. The resources in the list can be accessed by\nindex and iterated over. Similarly, the raw dictionary can be accessed via the `data` attribute.\n\n```python\nclients = freshBooksClient.clients.list(account_id)\n\nassert clients[0].organization == \"FreshBooks\"\n\nassert clients.data[\"clients\"][0][\"organization\"] == \"FreshBooks\"\n\nfor client in clients:\n    assert client.organization == \"FreshBooks\"\n    assert client.data[\"organization\"] == \"FreshBooks\"\n```\n\n#### Create, Update, and Delete\n\nAPI calls to create and update take a dictionary of the resource data. A successful call will return a `Result` object\nas if a `get` call.\n\nCreate:\n\n```python\npayload = {\"email\": \"john.doe@abcorp.com\"}\nnew_client = FreshBooksClient.clients.create(account_id, payload)\n\nclient_id = new_client.userid\n```\n\nUpdate:\n\n```python\npayload = {\"email\": \"john.doe@abcorp.ca\"}\nclient = freshBooksClient.clients.update(account_id, client_id, payload)\n\nassert client.email == \"john.doe@abcorp.ca\"\n```\n\nDelete:\n\n```python\nclient = freshBooksClient.clients.delete(account_id, client_id)\n\nassert client.vis_state == VisState.DELETED\n```\n\n#### Error Handling\n\nCalls made to the FreshBooks API with a non-2xx response are wrapped in a `FreshBooksError` exception.\nThis exception class contains the error message, HTTP response code, FreshBooks-specific error number if one exists,\nand the HTTP response body.\n\nExample:\n\n```python\nfrom freshbooks import FreshBooksError\n\ntry:\n    client = freshBooksClient.clients.get(account_id, client_id)\nexcept FreshBooksError as e:\n    assert str(e) == \"Client not found.\"\n    assert e.status_code == 404\n    assert e.error_code == 1012\n    assert e.raw_response ==  (\"{'response': {'errors': [{'errno': 1012, \"\n                               \"'field': 'userid', 'message': 'Client not found.', \"\n                               \"'object': 'client', 'value': '134'}]}}\")\n```\n\nNot all resources have full CRUD methods available. For example expense categories have `list` and `get`\ncalls, but are not deletable. If you attempt to call a method that does not exist, the SDK will raise a\n`FreshBooksNotImplementedError` exception, but this is not something you will likely have to account\nfor outside of development.\n\n#### Pagination, Filters, and Includes\n\n`list` calls take a list of builder objects that can be used to paginate, filter, and include\noptional data in the response. See [FreshBooks API - Parameters](https://www.freshbooks.com/api/parameters) documentation.\n\n##### Pagination\n\nPagination results are included in `list` responses in the `pages` attribute:\n\n```python\n>>> clients = freshBooksClient.clients.list(account_id)\n>>> clients.pages\nPageResult(page=1, pages=1, per_page=30, total=6)\n\n>>> clients.pages.total\n6\n```\n\nTo make a paginated call, first create a `PaginateBuilder` object that can be passed into the `list` method.\n\n```python\n>>> from freshbooks import PaginateBuilder\n\n>>> paginator = PaginateBuilder(2, 4)\n>>> paginator\nPaginateBuilder(page=2, per_page=4)\n\n>>> clients = freshBooksClient.clients.list(account_id, builders=[paginator])\n>>> clients.pages\nPageResult(page=2, pages=3, per_page=4, total=9)\n```\n\n`PaginateBuilder` has methods `page` and `per_page` to return or set the values. When setting the values the calls\ncan be chained.\n\n```python\n>>> paginator = PaginateBuilder(1, 3)\n>>> paginator\nPaginateBuilder(page=1, per_page=3)\n\n>>> paginator.page()\n1\n\n>>> paginator.page(2).per_page(4)\n>>> paginator\nPaginateBuilder(page=2, per_page=4)\n```\n\nListResults can be combined, allowing your to use pagination to get all the results of a resource.\n\n```python\npaginator = PaginateBuilder(1, 100)\nclients = freshBooksClient.clients.list(self.account_id, builders=[paginator])\nwhile clients.pages.page < clients.pages.pages:\n    paginator.page(clients.pages.page + 1)\n    new_clients = freshBooksClient.clients.list(self.account_id, builders=[paginator])\n    clients = clients + new_clients\n```\n\n##### Filters\n\nTo filter which results are return by `list` method calls, construct a `FilterBuilder` and pass that\nin the list of builders to the `list` method.\n\n```python\n>>> from freshbooks import FilterBuilder\n\n>>> filter = FilterBuilder()\n>>> filter.equals(\"userid\", 123)\n\n>>> clients = freshBooksClient.clients.list(account_id, builders=[filter])\n```\n\nFilters can be built with the methods: `equals`, `in_list`, `like`, `between`, and `boolean`,\nwhich can be chained together.\n\nPlease see [FreshBooks API - Active and Deleted Objects](https://www.freshbooks.com/api/active_deleted)\nfor details on filtering active, archived, and deleted resources.\n\n```python\n>>> f = FilterBuilder()\n>>> f.in_list(\"clientids\", [123, 456])\nFilterBuilder(&search[clientids][]=123&search[clientids][]=456)\n\n>>> f = FilterBuilder()\n>>> f.like(\"email_like\", \"@freshbooks.com\")\nFilterBuilder(&search[email_like]=@freshbooks.com)\n\n>>> f = FilterBuilder()\n>>> f.between(\"amount\", 1, 10)\nFilterBuilder(&search[amount_min]=1&search[amount_max]=10)\n\n>>> f = FilterBuilder()\n>>> f.between(\"amount\", min=15)  # For just minimum\nFilterBuilder(&search[amount_min]=15)\n\n>>> f = FilterBuilder()\n>>> f.between(\"amount_min\", 15)  # Alternatively\nFilterBuilder(&search[amount_min]=15)\n\n>>> f = FilterBuilder()\n>>> f.between(\"start_date\", date.today())\nFilterBuilder(&search[start_date]=2020-11-21)\n\n>>> f = FilterBuilder()\n>>> f.boolean(\"complete\", False) # Boolean filters are mostly used on Project-like resources\nFilterBuilder(&complete=False)\n\n>>> last_week = date.today() - timedelta(days=7)\n>>> f = FilterBuilder()\n>>> f.equals(\"vis_state\", VisState.ACTIVE).between(\"updated\", last_week, date.today()) # Chaining filters\nFilterBuilder(&search[vis_state]=0&search[updated_min]=2020-11-14&search[updated_max]=2020-11-21)\n```\n\n##### Includes\n\nTo include additional relationships, sub-resources, or data in a response an `IncludesBuilder`\ncan be constructed.\n\n```python\n>>> from freshbooks import IncludesBuilder\n\n>>> includes = IncludesBuilder()\n>>> includes.include(\"outstanding_balance\")\nIncludesBuilder(&include[]=outstanding_balance)\n```\n\nWhich can then be passed into `list` or `get` calls:\n\n```python\n>>> clients = freshBooksClient.clients.list(account_id, builders=[includes])\n>>> clients[0].outstanding_balance\n[{'amount': {'amount': '100.00', 'code': 'USD'}}]\n\n>>> client = freshBooksClient.clients.get(account_id, client_id, includes=includes)\n>>> client.outstanding_balance\n[{'amount': {'amount': '100.00', 'code': 'USD'}}]\n```\n\nIncludes can also be passed into `create` and `update` calls to include the data in the response of the updated resource:\n\n```python\n>>> payload = {\"email\": \"john.doe@abcorp.com\"}\n>>> new_client = FreshBooksClient.clients.create(account_id, payload, includes=includes)\n>>> new_client.outstanding_balance\n[]  # New client has no balance\n```\n\n##### Sorting\n\nTODO:\n\n#### Dates and Times\n\nFor historical reasons, some resources in the FreshBooks API (mostly accounting-releated) return date/times in\n\"US/Eastern\" timezone. Some effort is taken to return `datetime` objects as zone-aware and normalized to UTC. In these\ncases, the raw response string will differ from the attribute. For example:\n\n```python\nfrom datetime import datetime, timezone\n\nassert client.data[\"updated\"] == \"2021-04-16 10:31:59\"  # Zone-naive string in \"US/Eastern\"\nassert client.updated.isoformat() == '2021-04-16T14:31:59+00:00'  # Zone-aware datetime in UTC\nassert client.updated == datetime(year=2021, month=4, day=16, hour=14, minute=31, second=59, tzinfo=timezone.utc)\n```\n\n## Development\n\n### Testing\n\nTo run all tests:\n\n```bash\nmake test\n```\n\nTo run a single test with pytest:\n\n```bash\npy.test path/to/test/file.py\npy.test path/to/test/file.py::TestClass::test_case\n```\n\n### Documentations\n\nYou can generate the documentation via:\n\n```bash\nmake generate-docs\n```\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "API client for FreshBooks",
    "version": "1.2.1",
    "split_keywords": [
        "freshbooks"
    ],
    "urls": [
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "9fedb33769a8f35e4585a66769496d9f6be1a565859da4b98b665dfba3be40ff",
                "md5": "cdd4e55f1069b352d4e1f0cecf147aa5",
                "sha256": "7fc8db196e5b6038d735238eb5c8d96bfe74a5ed14a3a479f6d71fb8ec278dea"
            },
            "downloads": -1,
            "filename": "freshbooks_sdk-1.2.1-py2.py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "cdd4e55f1069b352d4e1f0cecf147aa5",
            "packagetype": "bdist_wheel",
            "python_version": "py2.py3",
            "requires_python": null,
            "size": 32943,
            "upload_time": "2023-04-24T13:15:05",
            "upload_time_iso_8601": "2023-04-24T13:15:05.555846Z",
            "url": "https://files.pythonhosted.org/packages/9f/ed/b33769a8f35e4585a66769496d9f6be1a565859da4b98b665dfba3be40ff/freshbooks_sdk-1.2.1-py2.py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": "",
            "digests": {
                "blake2b_256": "cbdec85523946c89fe865fcc5079a8b192ce83804024c8aff71dc482d0331bc3",
                "md5": "14b678261d2757ec090881b1a12d2926",
                "sha256": "a3c3442ae2d46d6129a90b2f2c4f3fd8345f4985a0ac22af2b013a75fd0d2728"
            },
            "downloads": -1,
            "filename": "freshbooks-sdk-1.2.1.tar.gz",
            "has_sig": false,
            "md5_digest": "14b678261d2757ec090881b1a12d2926",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": null,
            "size": 27037,
            "upload_time": "2023-04-24T13:15:07",
            "upload_time_iso_8601": "2023-04-24T13:15:07.696841Z",
            "url": "https://files.pythonhosted.org/packages/cb/de/c85523946c89fe865fcc5079a8b192ce83804024c8aff71dc482d0331bc3/freshbooks-sdk-1.2.1.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2023-04-24 13:15:07",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "github_user": "freshbooks",
    "github_project": "freshbooks-python-sdk",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "requirements": [],
    "lcname": "freshbooks-sdk"
}
        
Elapsed time: 0.07629s