fulfil-client


Namefulfil-client JSON
Version 3.0.0 PyPI version JSON
download
home_pagehttps://github.com/fulfilio/fulfil-python-api
SummaryFulfil REST API Client in Python
upload_time2025-08-18 15:23:37
maintainerNone
docs_urlNone
authorFulfil.IO Inc.
requires_pythonNone
licenseISCL
keywords fulfil_client
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI
coveralls test coverage No coveralls.
                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"
}
        
Elapsed time: 4.24936s