The V1 API is officially deprecated as of March 31, 2020.
As of Sep 2020, if you are still using API V1, you are in-fact accessing V2.
The V2 API is mostly backwards compatible with the V1 API.
===============================
Fulfil IO Python Client
===============================
.. image:: https://img.shields.io/pypi/v/fulfil_client.svg
:target: https://pypi.python.org/pypi/fulfil_client
.. image:: https://img.shields.io/travis/fulfilio/fulfil_client.svg
:target: https://travis-ci.org/fulfilio/fulfil-python-api
.. image:: https://readthedocs.org/projects/fulfil-python-api/badge/?version=latest
:target: https://readthedocs.org/projects/fulfil-python-api/?badge=latest
:alt: Documentation Status
Fulfil REST API Client in Python
* Free software: ISC license
* Documentation: https://fulfil-python-api.readthedocs.org.
* Examples: https://github.com/fulfilio/fulfil-python-api/tree/master/examples.
Features
--------
* Ability to call models
Installation
------------
.. code:: sh
pip install fulfil_client
Quickstart
----------
.. code:: python
from fulfil_client import Client
client = Client('<subdomain>', '<api_key>')
Product = client.model('product.product')
# find products
some_products = Product.find()
# find products that have a name similar to iphone
iphones = Product.find(['name', 'ilike', 'iphone'])
Contacts
--------
Contact can have multiple addresses and contact mechanisms i.e. phone,
email.
.. code:: python
from fulfil_client import Client
client = Client('<subdomain>', '<api_key>')
Contact = client.model('party.party')
Country = client.model('country.country')
Subdivision = client.model('country.subdivision')
country_usa, = Country.find([('code', '=', 'US')])
state_california, = Subdivision.find([('code', '=', 'US-CA')])
# Creating a contact with address and contact mechanisms
contact, = Contact.create([{
'name': 'Jon Doe',
'addresses': [('create', [{
'name': 'Jone Doe Apartment',
'street': '9805 Kaiden Grove',
'city': 'New Leland',
'zip': '57726',
'country': country_usa['id'],
'subdivision': state_california['id']
}])],
'contact_mechanisms': [('create', [{
'type': 'phone',
'value': '243243234'
}, {
'email': 'email',
'value': 'hello@jondoe.com'
}])]
}])
print contact
# Searching for a contact
contact, = Contact.find([('name', '=', 'Jon Doe')])
print contact
# Get a contact by ID
contact = Contact.get(contact['id'])
print contact
Products
--------
Products are grouped by templates, which have common information shared by
products a.k.a. variants.
.. code:: python
from decimal import Decimal
# Creating a Product Template
Template = client.model('product.template')
iphone, = Template.create([{
'name': 'iPhone',
'account_category': True,
}])
# Creating products
Product = client.model('product.product')
iphone6, = Product.create([{
'template': iphone['id'],
'variant_name': 'iPhone 6',
'code': 'IPHONE-6',
'list_price': Decimal('699'),
'cost_price': Decimal('599'),
}])
# Another variation
iphone6s, = Product.create([{
'template': iphone['id'],
'variant_name': 'iPhone 6S',
'code': 'IPHONE-6S',
'list_price': Decimal('899'),
'cost_price': Decimal('699'),
}])
Sale
----
.. code:: python
contact = Contact.get(contact['id'])
iphone6 = Product.get(iphone6['id'])
iphone6s = Product.get(iphone6s['id'])
# Creating a Sale
Sale = client.model('sale.sale')
sale, = Sale.create([{
'party': contact['id'],
'shipment_address': contact['addresses'][0],
'invoice_address': contact['addresses'][0],
'lines': [('create', [{
'product': iphone6['id'],
'description': iphone6['rec_name'],
'unit': iphone6['default_uom'],
'unit_price': iphone6['list_price'],
'quantity': 3
}, {
'product': iphone6s['id'],
'description': iphone6s['rec_name'],
'unit': iphone6['default_uom'],
'unit_price': iphone6s['list_price'],
'quantity': 1
}])]
}])
Fetching an interactive report (sales by month)
-----------------------------------------------
The report data (including rendering) information can be fetched
over the API.
Below is the example code to fetch sales by month report.
.. code:: python
report = client.interactive_report('sales_by_month.ireport')
data = report.execute(start_date=date(2017,1,1), end_date=date(2017, 12,1))
Using Session Auth
------------------
.. code:: python
from fulfil_client import Client, SessionAuth
client = Client('subdomain')
user_id, session = client.login('username', 'password')
client.set_auth(SessionAuth(user_id, session))
Using Bearer Auth
-----------------
.. code:: python
from fulfil_client import Client, BearerAuth
client = Client('subdomain')
client.set_auth(BearerAuth(bearer_token))
Using OAuth Session
-------------------
Flask example
.. code:: python
from fulfil_client.oauth import Session
from fulfil_client import Client, BearerAuth
Session.setup(CLIENT_ID, CLIENT_SECRET)
fulfil_session = Session('localhost') # Provide subdomain
@app.route('/')
def index():
callback_url = url_for('authorized')
if 'oauth_token' not in session:
authorization_url, state = fulfil_session.create_authorization_url(
redirect_uri=callback_url, scope=['user_session']
)
session['oauth_state'] = state
return redirect(authorization_url)
client = Client('subdomain')
client.set_auth(BearerAuth(session['oauth_token']['access_token']))
Party = client.model('party.party')
return jsonify(Party.find())
@app.route('/authorized')
def authorized():
"""Callback route to fetch access token from grant code
"""
token = fulfil_session.get_token(code=request.args.get('code'))
session['oauth_token'] = token
return jsonify(oauth_token=token)
Verify Webhooks
---------------
There is a convenience function that can verify the webhooks originating
from Fulfil for you.
.. code-block:: python
from fulfil_client import verify_webhook
@app.route('/webhook', methods=['POST'])
def webhook():
data = flask.request.get_data()
verified = verify_webhook(
data,
secret, # This should be saved somewhere
flask.request.headers.get('X-Fulfil-Hmac-SHA256')
)
if not verified:
abort(401)
Testing
-------
The libary also provides a mocking function powered by the mock library
of python.
For example, if you want to test the function below
.. code-block:: python
def api_calling_method():
client = fulfil_client.Client('apple', 'apples-api-key')
Product = client.model('product.product')
products = Product.search_read_all([], None, ['id'])
Product.write(
[p['id'] for p in products],
{'active': False}
)
return client
Then the test case can mock the API call
.. code-block:: python
def test_mock_1():
with MockFulfil('fulfil_client.Client') as mocked_fulfil:
Product = mocked_fulfil.model('product.product')
# Set the return value of the search call without
# hitting the server.
Product.search_read_all.return_value = [
{'id': 1},
{'id': 2},
{'id': 3},
]
# Call the function
api_calling_method()
# Now assert
Product.search_read_all.assert_called()
Product.search_read_all.assert_called_with([], None, ['id'])
Product.write.assert_called_with(
[1, 2, 3], {'active': False}
)
The `Product` object returned is a `mock.Mock` object and supports all
of the `assertions supported
<https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_called>`_
by python Mock objects.
Credits
---------
Fulfil.IO Inc.
=======
History
=======
0.2.0 (2016-6-22)
-----------------
* Change the behavior of `search` method.
The method now reflects the fulfil search API which returns a list of
ids. The previous behavior which returned an object with `id` and
`rec_name` (record name) is now called `find`.
0.1.1 (2016-3-3)
------------------
* Add method to create resource.
* Add examples on how to create contacts, products and sales.
0.1.0 (2016-1-22)
------------------
* First release on PyPI.
Raw data
{
"_id": null,
"home_page": "https://github.com/fulfilio/fulfil-python-api",
"name": "fulfil-client",
"maintainer": null,
"docs_url": null,
"requires_python": null,
"maintainer_email": null,
"keywords": "fulfil_client",
"author": "Fulfil.IO Inc.",
"author_email": "hello@fulfil.io",
"download_url": "https://files.pythonhosted.org/packages/12/fe/9c21f63abe4c789df74bebde2ae8f02a07274efa269b64b83d0b9e6f6276/fulfil_client-3.0.0.tar.gz",
"platform": null,
"description": " The V1 API is officially deprecated as of March 31, 2020.\n\n As of Sep 2020, if you are still using API V1, you are in-fact accessing V2.\n The V2 API is mostly backwards compatible with the V1 API.\n\n===============================\nFulfil IO Python Client\n===============================\n\n.. image:: https://img.shields.io/pypi/v/fulfil_client.svg\n :target: https://pypi.python.org/pypi/fulfil_client\n\n.. image:: https://img.shields.io/travis/fulfilio/fulfil_client.svg\n :target: https://travis-ci.org/fulfilio/fulfil-python-api\n\n.. image:: https://readthedocs.org/projects/fulfil-python-api/badge/?version=latest\n :target: https://readthedocs.org/projects/fulfil-python-api/?badge=latest\n :alt: Documentation Status\n\n\nFulfil REST API Client in Python\n\n* Free software: ISC license\n* Documentation: https://fulfil-python-api.readthedocs.org.\n* Examples: https://github.com/fulfilio/fulfil-python-api/tree/master/examples.\n\nFeatures\n--------\n\n* Ability to call models\n\nInstallation\n------------\n\n.. code:: sh\n\n pip install fulfil_client\n\n\nQuickstart\n----------\n\n.. code:: python\n\n from fulfil_client import Client\n\n client = Client('<subdomain>', '<api_key>')\n\n Product = client.model('product.product')\n\n # find products\n some_products = Product.find()\n\n # find products that have a name similar to iphone\n iphones = Product.find(['name', 'ilike', 'iphone'])\n\n\n\nContacts\n--------\n\nContact can have multiple addresses and contact mechanisms i.e. phone,\nemail.\n\n.. code:: python\n\n from fulfil_client import Client\n client = Client('<subdomain>', '<api_key>')\n\n Contact = client.model('party.party')\n Country = client.model('country.country')\n Subdivision = client.model('country.subdivision')\n\n country_usa, = Country.find([('code', '=', 'US')])\n state_california, = Subdivision.find([('code', '=', 'US-CA')])\n\n # Creating a contact with address and contact mechanisms\n contact, = Contact.create([{\n 'name': 'Jon Doe',\n 'addresses': [('create', [{\n 'name': 'Jone Doe Apartment',\n 'street': '9805 Kaiden Grove',\n 'city': 'New Leland',\n 'zip': '57726',\n 'country': country_usa['id'],\n 'subdivision': state_california['id']\n }])],\n 'contact_mechanisms': [('create', [{\n 'type': 'phone',\n 'value': '243243234'\n }, {\n 'email': 'email',\n 'value': 'hello@jondoe.com'\n }])]\n }])\n print contact\n\n # Searching for a contact\n contact, = Contact.find([('name', '=', 'Jon Doe')])\n print contact\n\n # Get a contact by ID\n contact = Contact.get(contact['id'])\n print contact\n\n\nProducts\n--------\n\nProducts are grouped by templates, which have common information shared by\nproducts a.k.a. variants.\n\n.. code:: python\n\n from decimal import Decimal\n\n # Creating a Product Template\n Template = client.model('product.template')\n\n iphone, = Template.create([{\n 'name': 'iPhone',\n 'account_category': True,\n }])\n\n # Creating products\n Product = client.model('product.product')\n iphone6, = Product.create([{\n 'template': iphone['id'],\n 'variant_name': 'iPhone 6',\n 'code': 'IPHONE-6',\n 'list_price': Decimal('699'),\n 'cost_price': Decimal('599'),\n }])\n\n # Another variation\n iphone6s, = Product.create([{\n 'template': iphone['id'],\n 'variant_name': 'iPhone 6S',\n 'code': 'IPHONE-6S',\n 'list_price': Decimal('899'),\n 'cost_price': Decimal('699'),\n }])\n\n\nSale\n----\n\n.. code:: python\n\n contact = Contact.get(contact['id'])\n iphone6 = Product.get(iphone6['id'])\n iphone6s = Product.get(iphone6s['id'])\n\n # Creating a Sale\n Sale = client.model('sale.sale')\n sale, = Sale.create([{\n 'party': contact['id'],\n 'shipment_address': contact['addresses'][0],\n 'invoice_address': contact['addresses'][0],\n 'lines': [('create', [{\n 'product': iphone6['id'],\n 'description': iphone6['rec_name'],\n 'unit': iphone6['default_uom'],\n 'unit_price': iphone6['list_price'],\n 'quantity': 3\n }, {\n 'product': iphone6s['id'],\n 'description': iphone6s['rec_name'],\n 'unit': iphone6['default_uom'],\n 'unit_price': iphone6s['list_price'],\n 'quantity': 1\n }])]\n }])\n\n\nFetching an interactive report (sales by month)\n-----------------------------------------------\n\nThe report data (including rendering) information can be fetched\nover the API.\n\nBelow is the example code to fetch sales by month report.\n\n.. code:: python\n\n report = client.interactive_report('sales_by_month.ireport')\n data = report.execute(start_date=date(2017,1,1), end_date=date(2017, 12,1))\n\n\n\nUsing Session Auth\n------------------\n\n.. code:: python\n\n from fulfil_client import Client, SessionAuth\n\n client = Client('subdomain')\n user_id, session = client.login('username', 'password')\n client.set_auth(SessionAuth(user_id, session))\n\n\nUsing Bearer Auth\n-----------------\n\n.. code:: python\n\n from fulfil_client import Client, BearerAuth\n\n client = Client('subdomain')\n client.set_auth(BearerAuth(bearer_token))\n\n\nUsing OAuth Session\n-------------------\n\nFlask example\n\n.. code:: python\n\n from fulfil_client.oauth import Session\n from fulfil_client import Client, BearerAuth\n\n Session.setup(CLIENT_ID, CLIENT_SECRET)\n fulfil_session = Session('localhost') # Provide subdomain\n\n @app.route('/')\n def index():\n callback_url = url_for('authorized')\n if 'oauth_token' not in session:\n authorization_url, state = fulfil_session.create_authorization_url(\n redirect_uri=callback_url, scope=['user_session']\n )\n session['oauth_state'] = state\n return redirect(authorization_url)\n client = Client('subdomain')\n client.set_auth(BearerAuth(session['oauth_token']['access_token']))\n Party = client.model('party.party')\n return jsonify(Party.find())\n\n @app.route('/authorized')\n def authorized():\n \"\"\"Callback route to fetch access token from grant code\n \"\"\"\n token = fulfil_session.get_token(code=request.args.get('code'))\n session['oauth_token'] = token\n return jsonify(oauth_token=token)\n\n\nVerify Webhooks\n---------------\n\nThere is a convenience function that can verify the webhooks originating\nfrom Fulfil for you.\n\n.. code-block:: python\n\n from fulfil_client import verify_webhook\n\n\n @app.route('/webhook', methods=['POST'])\n def webhook():\n data = flask.request.get_data()\n verified = verify_webhook(\n data,\n secret, # This should be saved somewhere\n flask.request.headers.get('X-Fulfil-Hmac-SHA256')\n )\n\n if not verified:\n abort(401)\n\nTesting\n-------\n\nThe libary also provides a mocking function powered by the mock library\nof python.\n\nFor example, if you want to test the function below\n\n.. code-block:: python\n\n def api_calling_method():\n client = fulfil_client.Client('apple', 'apples-api-key')\n Product = client.model('product.product')\n products = Product.search_read_all([], None, ['id'])\n Product.write(\n [p['id'] for p in products],\n {'active': False}\n )\n return client\n\n\nThen the test case can mock the API call\n\n.. code-block:: python\n\n def test_mock_1():\n with MockFulfil('fulfil_client.Client') as mocked_fulfil:\n Product = mocked_fulfil.model('product.product')\n # Set the return value of the search call without\n # hitting the server.\n Product.search_read_all.return_value = [\n {'id': 1},\n {'id': 2},\n {'id': 3},\n ]\n\n # Call the function\n api_calling_method()\n\n # Now assert\n Product.search_read_all.assert_called()\n Product.search_read_all.assert_called_with([], None, ['id'])\n Product.write.assert_called_with(\n [1, 2, 3], {'active': False}\n )\n\nThe `Product` object returned is a `mock.Mock` object and supports all\nof the `assertions supported\n<https://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.assert_called>`_\nby python Mock objects.\n\n\nCredits\n---------\n\nFulfil.IO Inc.\n\n\n=======\nHistory\n=======\n\n0.2.0 (2016-6-22)\n-----------------\n\n* Change the behavior of `search` method.\n\n The method now reflects the fulfil search API which returns a list of\n ids. The previous behavior which returned an object with `id` and\n `rec_name` (record name) is now called `find`.\n\n0.1.1 (2016-3-3)\n------------------\n\n* Add method to create resource.\n* Add examples on how to create contacts, products and sales.\n\n0.1.0 (2016-1-22)\n------------------\n\n* First release on PyPI.\n",
"bugtrack_url": null,
"license": "ISCL",
"summary": "Fulfil REST API Client in Python",
"version": "3.0.0",
"project_urls": {
"Homepage": "https://github.com/fulfilio/fulfil-python-api"
},
"split_keywords": [
"fulfil_client"
],
"urls": [
{
"comment_text": null,
"digests": {
"blake2b_256": "ce25d1ff04071418ab04724da12f3c169dbf19025aadb3a3172409cb6713bcdf",
"md5": "e10f247135c2f735ab075e4405440336",
"sha256": "d400e1e05ebe76c6d784f1a78afb5e8e7c438a1423e56d5e637dee00102344a0"
},
"downloads": -1,
"filename": "fulfil_client-3.0.0-py2.py3-none-any.whl",
"has_sig": false,
"md5_digest": "e10f247135c2f735ab075e4405440336",
"packagetype": "bdist_wheel",
"python_version": "py2.py3",
"requires_python": null,
"size": 23351,
"upload_time": "2025-08-18T15:23:36",
"upload_time_iso_8601": "2025-08-18T15:23:36.247972Z",
"url": "https://files.pythonhosted.org/packages/ce/25/d1ff04071418ab04724da12f3c169dbf19025aadb3a3172409cb6713bcdf/fulfil_client-3.0.0-py2.py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": null,
"digests": {
"blake2b_256": "12fe9c21f63abe4c789df74bebde2ae8f02a07274efa269b64b83d0b9e6f6276",
"md5": "49cd7a8150a1489f38ecb6608c358456",
"sha256": "9defb07eaed007e6f75df6fb4885e5fc52516514708fc858c0507261cb3688df"
},
"downloads": -1,
"filename": "fulfil_client-3.0.0.tar.gz",
"has_sig": false,
"md5_digest": "49cd7a8150a1489f38ecb6608c358456",
"packagetype": "sdist",
"python_version": "source",
"requires_python": null,
"size": 29300,
"upload_time": "2025-08-18T15:23:37",
"upload_time_iso_8601": "2025-08-18T15:23:37.329203Z",
"url": "https://files.pythonhosted.org/packages/12/fe/9c21f63abe4c789df74bebde2ae8f02a07274efa269b64b83d0b9e6f6276/fulfil_client-3.0.0.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2025-08-18 15:23:37",
"github": true,
"gitlab": false,
"bitbucket": false,
"codeberg": false,
"github_user": "fulfilio",
"github_project": "fulfil-python-api",
"travis_ci": true,
"coveralls": false,
"github_actions": true,
"tox": true,
"lcname": "fulfil-client"
}