Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: publish

on:
push:
tags: ["v*"]

jobs:
publish:
runs-on: ubuntu-latest
environment: pypi
permissions:
id-token: write
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
- run: uv sync --all-extras
- run: uv run pytest -q
- run: uv build
- uses: pypa/gh-action-pypi-publish@release/v1
21 changes: 21 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: tests

on:
push:
pull_request:

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v6
with:
python-version: ${{ matrix.python-version }}
- run: uv sync --all-extras
- run: uv run pytest -q
- run: uv build
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.DS_Store
.venv/
build/
dist/
*.egg-info/
__pycache__/
.pytest_cache/
8 changes: 0 additions & 8 deletions .travis.yml

This file was deleted.

30 changes: 30 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
Changelog
=========

4.0.0 (unreleased)
------------------

First release of the maintained fork of fulfilio/python-magento.

* Published on PyPI as ``magento1-client``; the import name is still
``magento``.

* Requires Python 3.9 or later. Python 2 is no longer supported.
* Replace the unmaintained ``suds-jurko`` with ``suds-community>=1.0``.
``suds-jurko`` does not build with setuptools 58 or later, so 3.1 no
longer installs on current Python (fulfilio#30, fulfilio#33).
* Packaging moved from ``setup.py`` to ``pyproject.toml``. ``requests`` is
now an optional ``rest`` extra.
* Add a test suite (run against a local stand-in for the Magento XML-RPC
API) and GitHub Actions CI on Python 3.9 to 3.14. The tests confirm every
documented API (``client.product`` etc.) is available (fulfilio#15).
* README: fix typos and a broken indent in the examples, explain how to check that a
connection and credentials work (fulfilio#25), document the
``product.create`` data format (fulfilio#27), and explain that
``order.search`` needs the Fulfil.IO connector extension and is not in
the stock Magento 1 API (fulfilio#26).

3.1 (2019-02-22)
----------------

Last release by Fulfil.IO.
1 change: 0 additions & 1 deletion MANIFEST.in

This file was deleted.

151 changes: 137 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,7 +1,39 @@
Magento Python API
==================

Python library to connect to Magento Webservices.
Python client for the Magento 1 Core API (XML-RPC and SOAP). It works with
Magento 1.x and with `OpenMage LTS <https://www.openmage.org/>`_, the
maintained continuation of Magento 1.

This is a maintained fork of `fulfilio/python-magento
<https://github.com/fulfilio/python-magento>`_, which has had no release
since 2019. Version 4.0 runs on Python 3.9 and later and replaces the
unmaintained ``suds-jurko`` dependency with ``suds-community``, so it
installs on current Python and setuptools again.

**Magento 2 / Adobe Commerce:** this library talks to the Magento 1 API.
For Magento 2 use `PyMagento <https://github.com/Bixoto/PyMagento>`_.

Installation
------------

.. code-block:: sh

pip install magento1-client

The package is published as ``magento1-client`` but is still imported as
``magento``, so existing code keeps working. Uninstall the old ``magento``
package first (``pip uninstall magento``); both install the same
``magento`` module. PyMagento also installs a ``magento`` module, so it
cannot be installed alongside this one.

Credentials
-----------

The username and password are those of a *Web Services* user, not a
Magento admin login. Create one in the Magento admin under
*System > Web Services > SOAP/XML-RPC - Users*, and give it a role under
*SOAP/XML-RPC - Roles* that allows the resources you need.

Usage
-----
Expand All @@ -17,22 +49,96 @@ Usage
# Create an instance of API
client = magento.API(url, apiuser, apipass)

# A filter expression as dictionary.
order_filter = {'created_at':{'from':'2011-09-15 00:00:00'}}
products = client.product.list(order_filter)
# A filter expression as dictionary.
product_filter = {'created_at': {'from': '2011-09-15 00:00:00'}}
products = client.product.list(product_filter)

# Get a list of product types
product_types = client.product_types.list()

# Get a specific product
sku = 'prod1'
product = client.product.info(sku)

# Add comment to an order
order_increment_id = '100000001 '
order_increment_id = '100000001'
status = 'canceled'
client.order.addcomment(order_increment_id, status)

Each API (``client.product``, ``client.order``, ...) logs in the first
time you use it and keeps its session for later calls.

Checking the connection
-----------------------

``client.connect()`` only builds the connection object; it does not
contact Magento. To check that the URL and credentials work, make a cheap
call and catch the error:

.. code-block:: python

from xmlrpc.client import Fault, ProtocolError

client = magento.API(url, apiuser, apipass)
try:
info = client.magento.info()
except Fault as e:
# e.g. Fault 2 'Access denied.' for a wrong user or API key
print('Magento refused the call:', e.faultCode, e.faultString)
except (ProtocolError, OSError) as e:
# wrong URL (e.g. 404 at index.php/api/xmlrpc) or host unreachable
print('Could not reach the API:', e)
else:
print('Connected to Magento', info['magento_version'])

Filters
-------

The ``list`` methods take filters in Magento's
``{<attribute>: {<operator>: <value>}}`` format, for example
``{'status': {'eq': 'pending'}}`` or ``{'sku': {'like': 'MUG-%'}}``.
Operators include ``eq``, ``neq``, ``like``, ``in``, ``nin``, ``gt``,
``lt``, ``gteq``, ``lteq``, ``from`` and ``to``.

Creating a product
------------------

``client.product.create(product_type, attribute_set_id, sku, data)``
takes the product type (``'simple'``, ``'configurable'``, ...), the ID of
an attribute set, the SKU, and a dictionary of attribute values. The SKU
is passed on its own and does not go in ``data``.

.. code-block:: python

# Find the attribute set to use ('Default' is usually 4)
attribute_sets = client.product_attribute_set.list()

product_id = client.product.create('simple', 4, 'MUG-1', {
'name': 'Coffee mug',
'description': 'A large coffee mug',
'short_description': 'Coffee mug',
'price': '9.95',
'weight': '0.4',
'status': 1, # 1 = enabled, 2 = disabled
'visibility': 4, # 4 = catalog and search
'tax_class_id': 2, # 2 = taxable goods on a default install
'websites': [1],
'stock_data': {'qty': 10, 'is_in_stock': 1},
})

``Fault 102: 'Invalid data given'`` usually means a required attribute of
the attribute set is missing from ``data``, or a value has the wrong
format. Look at an existing product with ``client.product.info(sku)`` to
see the attribute names and formats your store expects.

Searching orders
----------------

``client.order.search()`` calls ``sales_order.search``, which is not part
of the stock Magento 1 API. It needs the Fulfil.IO / Openlabs connector
extension on the Magento side; without it Magento answers
``Fault 3: Invalid api path``. On a stock install use
``client.order.list(filters)`` instead.

All available APIs
-------------------
Expand Down Expand Up @@ -66,12 +172,12 @@ All available APIs
* Shipment (`client.shipment`)
* Store (`client.store`)

Old deprecated example
----------------------
Old style: one API object per resource
--------------------------------------

The API was originally written with the requirement to call APIs
individually with all the credentials. This led to very verbose code and a
lot of repitition. While the behavior has not been deprecated, it is
lot of repetition. While the behavior has not been deprecated, it is
recommended to write all new code with the pattern shown in the above
example.

Expand All @@ -84,7 +190,7 @@ example.
apipass = 'password'

with magento.Order(url, apiuser, apipass) as order_api:
order_increment_id = '100000001 '
order_increment_id = '100000001'
status = 'canceled'
order_api.addcomment(order_increment_id, status)

Expand All @@ -93,19 +199,21 @@ example.
store_view_info = store_api.info(store_id)
store_views = store_api.list()

with magento.Magento(url, apiuser, apipass) as magento_api:
with magento.Magento(url, apiuser, apipass) as magento_api:
magento_info = magento_api.info()


Calling custom classes from your own API extensions
---------------------------------------------------

You can ddirectly invoke the underlying `call` method
to make calls directly.
You can directly invoke the underlying `call` method
to make calls directly. Unlike the named APIs above, the client itself
only logs in inside a ``with`` block:

.. code-block:: python

result = client.call('custom_model.list', [])
with client:
result = client.call('custom_model.list', [])


Alternatively, you can also build sub classes of API to have
Expand All @@ -117,6 +225,8 @@ instance, always have all your imports on the top of your magento module.

.. code-block:: python

from magento.api import API

class CustomModel(API):
def list(self):
return self.call('custom_model.list', [])
Expand All @@ -138,6 +248,19 @@ metaclass. So you can now use

client.custom_model.list()

Protocols
---------

XML-RPC is the default. Pass ``protocol='soap'`` to use the SOAP v1 API
instead:

.. code-block:: python

client = magento.API(url, apiuser, apipass, protocol='soap')

A minimal ``protocol='rest'`` client for the Magento 2 REST API is also
included, but it only supports GET requests and needs ``requests``
installed (the ``rest`` extra). For Magento 2 work, PyMagento is the better choice.

License
-------
Expand Down
6 changes: 6 additions & 0 deletions magento/sales.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ def search(self, filters=None, fields=None, limit=None, page=1):
Retrieve order list by options using search api. Using this result can
be paginated

.. note:: ``sales_order.search`` is not part of the stock Magento 1
API. It needs the Fulfil.IO / Openlabs connector extension
installed on the Magento side; without it Magento answers
with ``Fault 3: Invalid api path``. Use :meth:`list` on a
stock install.

:param options: Dictionary of options.

:param filters: `{<attribute>:{<operator>:<value>}}`
Expand Down
2 changes: 1 addition & 1 deletion magento/version.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@
installing the module

"""
VERSION = '3.1'
VERSION = '4.0.0'
55 changes: 55 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
[build-system]
requires = ["setuptools>=64"]
build-backend = "setuptools.build_meta"

[project]
name = "magento1-client"
dynamic = ["version"]
description = "Magento 1 / OpenMage Core API client (XML-RPC and SOAP)"
readme = "README.rst"
license = { file = "LICENSE" }
authors = [{ name = "Sharoon Thomas, Openlabs Technologies", email = "info@fulfil.io" }]
maintainers = [{ name = "Adrian Beale" }]
requires-python = ">=3.9"
dependencies = [
"suds-community>=1.0",
"six",
]
classifiers = [
"Development Status :: 6 - Mature",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3 :: Only",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: 3.14",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
]

[project.optional-dependencies]
rest = ["requests"]

[project.urls]
Homepage = "https://github.com/TinBane/python-magento"
Issues = "https://github.com/TinBane/python-magento/issues"
Changelog = "https://github.com/TinBane/python-magento/blob/develop/CHANGELOG.rst"

[tool.setuptools]
packages = ["magento"]

[tool.setuptools.dynamic]
version = { attr = "magento.version.VERSION" }

[dependency-groups]
dev = ["pytest>=8"]

[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["tests"]
Loading
Loading