diff --git a/.gitignore b/.gitignore index 4c842ae..33ccf84 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ +*~ +*.pyc +dist/ rattail_woocommerce.egg-info/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 73b40f5..088d546 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to rattail-woocommerce will be documented in this file. The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html). +## v0.2.0 (2024-06-11) + +### Feat + +- switch from setup.cfg to pyproject.toml + hatchling + ## [0.1.0] - 2021-01-21 ### Added - Initial version. diff --git a/README.md b/README.md new file mode 100644 index 0000000..f4786ea --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ + +# rattail-woocommerce + +Rattail is a retail software framework, released under the GNU General Public +License. + +This package contains software interfaces for the +[WooCommerce](https://woocommerce.com/) system. + +Please see the [Rattail Project](https://rattailproject.org/) for more +information. diff --git a/README.rst b/README.rst deleted file mode 100644 index 4c84e9e..0000000 --- a/README.rst +++ /dev/null @@ -1,14 +0,0 @@ - -rattail-woocommerce -=================== - -Rattail is a retail software framework, released under the GNU General Public -License. - -This package contains software interfaces for the `WooCommerce`_ system. - -.. _WooCommerce: https://woocommerce.com/ - -Please see the `Rattail Project`_ for more information. - -.. _`Rattail Project`: https://rattailproject.org/ diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..17a034b --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + + +[project] +name = "rattail-woocommerce" +version = "0.2.0" +description = "Rattail Software Interfaces for WooCommerce" +readme = "README.md" +authors = [{name = "Lance Edgar", email = "lance@edbob.org"}] +license = {text = "GNU GPL v3+"} +classifiers = [ + "Development Status :: 3 - Alpha", + "Environment :: Console", + "Environment :: Web Environment", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python", + "Programming Language :: Python :: 3", + "Topic :: Office/Business", + "Topic :: Software Development :: Libraries :: Python Modules", +] +dependencies = [ + "rattail", + "woocommerce", +] + + +[project.entry-points."rattail.typer_imports"] +rattail_woocommerce = "rattail_woocommerce.commands" + + +[project.urls] +Homepage = "https://rattailproject.org" +Repository = "https://kallithea.rattailproject.org/rattail-project/rattail-woocommerce" +Changelog = "https://kallithea.rattailproject.org/rattail-project/rattail-woocommerce/files/master/CHANGELOG.md" + + +[tool.commitizen] +version_provider = "pep621" +tag_format = "v$version" +update_changelog_on_bump = true diff --git a/rattail_woocommerce/_version.py b/rattail_woocommerce/_version.py index e41b669..b78065c 100644 --- a/rattail_woocommerce/_version.py +++ b/rattail_woocommerce/_version.py @@ -1,3 +1,6 @@ # -*- coding: utf-8; -*- -__version__ = '0.1.0' +from importlib.metadata import version + + +__version__ = version('rattail-woocommerce') diff --git a/rattail_woocommerce/commands.py b/rattail_woocommerce/commands.py index 565ef34..816af6c 100644 --- a/rattail_woocommerce/commands.py +++ b/rattail_woocommerce/commands.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar +# Copyright © 2010-2024 Lance Edgar # # This file is part of Rattail. # @@ -24,22 +24,44 @@ Rattail/WooCommerce Commands """ -from rattail import commands +import typer + +from rattail.commands import rattail_typer +from rattail.commands.typer import importer_command, typer_get_runas_user +from rattail.commands.importing import ImportCommandHandler -class ExportWooCommerce(commands.ImportSubcommand): +@rattail_typer.command() +@importer_command +def export_woocommerce( + ctx: typer.Context, + **kwargs +): """ Export data to WooCommerce """ - name = 'export-woocommerce' - description = __doc__.strip() - handler_spec = 'rattail_woocommerce.woocommerce.importing.rattail:FromRattailToWooCommerce' + config = ctx.parent.rattail_config + progress = ctx.parent.rattail_progress + handler = ImportCommandHandler( + config, + import_handler_spec='rattail_woocommerce.woocommerce.importing.rattail:FromRattailToWooCommerce') + kwargs['user'] = typer_get_runas_user(ctx) + handler.run(kwargs, progress=progress) -class ImportWooCommerce(commands.ImportSubcommand): +@rattail_typer.command() +@importer_command +def import_woocommerce( + ctx: typer.Context, + **kwargs +): """ Import data from WooCommerce """ - name = 'import-woocommerce' - description = __doc__.strip() - handler_spec = 'rattail_woocommerce.importing.woocommerce:FromWooCommerceToRattail' + config = ctx.parent.rattail_config + progress = ctx.parent.rattail_progress + handler = ImportCommandHandler( + config, + import_handler_spec='rattail_woocommerce.importing.woocommerce:FromWooCommerceToRattail') + kwargs['user'] = typer_get_runas_user(ctx) + handler.run(kwargs, progress=progress) diff --git a/rattail_woocommerce/datasync/woocommerce.py b/rattail_woocommerce/datasync/woocommerce.py index a7738ee..c39fc86 100644 --- a/rattail_woocommerce/datasync/woocommerce.py +++ b/rattail_woocommerce/datasync/woocommerce.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -61,9 +61,9 @@ class FromRattailToWooCommerce(FromRattailConsumer): model = self.model if change.payload_type == 'Product': - return session.query(model.Product).get(change.payload_key) + return session.get(model.Product, change.payload_key) if change.payload_type == 'ProductPrice': - price = session.query(model.ProductPrice).get(change.payload_key) + price = session.get(model.ProductPrice, change.payload_key) if price: return price.product diff --git a/rattail_woocommerce/importing/woocommerce.py b/rattail_woocommerce/importing/woocommerce.py index b5bdd62..c6e6a2c 100644 --- a/rattail_woocommerce/importing/woocommerce.py +++ b/rattail_woocommerce/importing/woocommerce.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -25,6 +25,7 @@ WooCommerce -> Rattail data import """ import datetime +from collections import OrderedDict from sqlalchemy import orm @@ -32,9 +33,9 @@ from woocommerce import API as WooAPI from rattail import importing from rattail.core import get_uuid -from rattail.util import OrderedDict from rattail.time import localtime, make_utc from rattail_woocommerce import importing as rattail_woocommerce_importing +from rattail_woocommerce.woocommerce.util import get_woocommerce_products class FromWooCommerceToRattail(importing.ToRattailHandler): @@ -61,23 +62,13 @@ class FromWooCommerce(importing.Importer): 'consumer_secret': self.config.require('woocommerce', 'api_consumer_secret'), 'version': 'wc/v3', } + + timeout = self.config.getint('woocommerce', 'api_timeout') + if timeout: + kwargs['timeout'] = timeout + self.api = WooAPI(**kwargs) - def get_woocommerce_products(self): - products = [] - page = 1 - while True: - block = self.api.get('products', params={'per_page': 100, - 'page': page}) - products.extend(block.json()) - link = block.headers.get('Link') - if link and 'rel="next"' in link: - page += 1 - else: - break - - return products - class ProductImporter(FromWooCommerce, rattail_woocommerce_importing.model.ProductImporter): """ @@ -109,7 +100,7 @@ class ProductImporter(FromWooCommerce, rattail_woocommerce_importing.model.Produ query=query) def get_host_objects(self): - return self.get_woocommerce_products() + return get_woocommerce_products(self.api) def find_rattail_product(self, api_product): product = self.get_product_by_woo_id(api_product['id']) @@ -266,7 +257,7 @@ class WooCacheProductImporter(FromWooCommerce, rattail_woocommerce_importing.mod pass def get_host_objects(self): - return self.get_woocommerce_products() + return get_woocommerce_products(self.api) def normalize_host_object(self, api_product): data = dict(api_product) diff --git a/rattail_woocommerce/woocommerce/importing/model.py b/rattail_woocommerce/woocommerce/importing/model.py index 8628c82..4a8ccb4 100644 --- a/rattail_woocommerce/woocommerce/importing/model.py +++ b/rattail_woocommerce/woocommerce/importing/model.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar +# Copyright © 2010-2022 Lance Edgar # # This file is part of Rattail. # @@ -27,6 +27,7 @@ WooCommerce model importers from woocommerce import API as WooAPI from rattail import importing +from rattail_woocommerce.woocommerce.util import get_woocommerce_products class ToWooCommerce(importing.Importer): @@ -114,23 +115,11 @@ class ProductImporter(ToWooCommerce): Fetch existing products from WooCommerce. """ cache = {} - page = 1 - while True: - response = self.api.get('products', params={'per_page': 100, - 'page': page}) - for product in response.json(): - data = self.normalize_local_object(product) - normal = self.normalize_cache_object(product, data) - key = self.get_cache_key(product, normal) - cache[key] = normal - - # TODO: this seems a bit hacky, is there a better way? - link = response.headers.get('Link') - if link and 'rel="next"' in link: - page += 1 - else: - break - + for product in get_woocommerce_products(self.api): + data = self.normalize_local_object(product) + normal = self.normalize_cache_object(product, data) + key = self.get_cache_key(product, normal) + cache[key] = normal return cache def get_single_local_object(self, key): diff --git a/rattail_woocommerce/woocommerce/importing/rattail.py b/rattail_woocommerce/woocommerce/importing/rattail.py index 8e59192..0619b50 100644 --- a/rattail_woocommerce/woocommerce/importing/rattail.py +++ b/rattail_woocommerce/woocommerce/importing/rattail.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar +# Copyright © 2010-2023 Lance Edgar # # This file is part of Rattail. # @@ -24,9 +24,10 @@ Rattail -> WooCommerce importing """ +from collections import OrderedDict + from rattail import importing from rattail.db import model -from rattail.util import OrderedDict from rattail_woocommerce.db.model import WooCacheProduct from rattail_woocommerce.woocommerce import importing as woocommerce_importing diff --git a/rattail_woocommerce/woocommerce/util.py b/rattail_woocommerce/woocommerce/util.py new file mode 100644 index 0000000..dc5ca04 --- /dev/null +++ b/rattail_woocommerce/woocommerce/util.py @@ -0,0 +1,53 @@ +# -*- coding: utf-8; -*- +################################################################################ +# +# Rattail -- Retail Software Framework +# Copyright © 2010-2022 Lance Edgar +# +# This file is part of Rattail. +# +# Rattail is free software: you can redistribute it and/or modify it under the +# terms of the GNU General Public License as published by the Free Software +# Foundation, either version 3 of the License, or (at your option) any later +# version. +# +# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +# details. +# +# You should have received a copy of the GNU General Public License along with +# Rattail. If not, see . +# +################################################################################ +""" +WooCommerce utilities +""" + + +def get_woocommerce_products(api): + """ + Fetch and return all products from Woo API. + """ + products = [] + page = 1 + while True: + + # TODO: 100 seems to be the max allowed per page? + # although docs do not seem to mention a limit.. + # https://woocommerce.github.io/woocommerce-rest-api-docs/?python#list-all-products + response = api.get('products', params={'per_page': 100, + 'page': page, + 'orderby': 'id', + 'order': 'asc'}) + + products.extend(response.json()) + + # TODO: this seems a bit hacky, is there a better way? + link = response.headers.get('Link') + if link and 'rel="next"' in link: + page += 1 + else: + break + + return products diff --git a/setup.py b/setup.py deleted file mode 100644 index 975d524..0000000 --- a/setup.py +++ /dev/null @@ -1,103 +0,0 @@ -# -*- coding: utf-8; -*- -################################################################################ -# -# Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar -# -# This file is part of Rattail. -# -# Rattail is free software: you can redistribute it and/or modify it under the -# terms of the GNU General Public License as published by the Free Software -# Foundation, either version 3 of the License, or (at your option) any later -# version. -# -# Rattail is distributed in the hope that it will be useful, but WITHOUT ANY -# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS -# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more -# details. -# -# You should have received a copy of the GNU General Public License along with -# Rattail. If not, see . -# -################################################################################ - -import os -from setuptools import setup, find_packages - - -here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'rattail_woocommerce', '_version.py')).read()) -README = open(os.path.join(here, 'README.rst')).read() - - -requires = [ - # - # Version numbers within comments below have specific meanings. - # Basically the 'low' value is a "soft low," and 'high' a "soft high." - # In other words: - # - # If either a 'low' or 'high' value exists, the primary point to be - # made about the value is that it represents the most current (stable) - # version available for the package (assuming typical public access - # methods) whenever this project was started and/or documented. - # Therefore: - # - # If a 'low' version is present, you should know that attempts to use - # versions of the package significantly older than the 'low' version - # may not yield happy results. (A "hard" high limit may or may not be - # indicated by a true version requirement.) - # - # Similarly, if a 'high' version is present, and especially if this - # project has laid dormant for a while, you may need to refactor a bit - # when attempting to support a more recent version of the package. (A - # "hard" low limit should be indicated by a true version requirement - # when a 'high' version is present.) - # - # In any case, developers and other users are encouraged to play - # outside the lines with regard to these soft limits. If bugs are - # encountered then they should be filed as such. - # - # package # low high - - 'rattail', # 0.9.153 - 'woocommerce', # 2.1.1 -] - - -setup( - name = "rattail-woocommerce", - version = __version__, - author = "Lance Edgar", - author_email = "lance@edbob.org", - url = "https://rattailproject.org/", - license = "GNU GPL v3", - description = "Rattail Software Interfaces for WooCommerce", - long_description = README, - - classifiers = [ - 'Development Status :: 3 - Alpha', - 'Environment :: Console', - 'Environment :: Web Environment', - 'Intended Audience :: Developers', - 'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)', - 'Natural Language :: English', - 'Operating System :: OS Independent', - 'Programming Language :: Python', - 'Programming Language :: Python :: 3', - 'Topic :: Office/Business', - 'Topic :: Software Development :: Libraries :: Python Modules', - ], - - install_requires = requires, - packages = find_packages(), - include_package_data = True, - zip_safe = False, - - entry_points = { - - 'rattail.commands': [ - 'export-woocommerce = rattail_woocommerce.commands:ExportWooCommerce', - 'import-woocommerce = rattail_woocommerce.commands:ImportWooCommerce', - ], - }, -) diff --git a/tasks.py b/tasks.py index 28e11c4..be2931a 100644 --- a/tasks.py +++ b/tasks.py @@ -2,7 +2,7 @@ ################################################################################ # # Rattail -- Retail Software Framework -# Copyright © 2010-2021 Lance Edgar +# Copyright © 2010-2024 Lance Edgar # # This file is part of Rattail. # @@ -30,15 +30,14 @@ import shutil from invoke import task -here = os.path.abspath(os.path.dirname(__file__)) -exec(open(os.path.join(here, 'rattail_woocommerce', '_version.py')).read()) - - @task -def release(ctx): +def release(c): """ Release a new version of 'rattail-woocommerce'. """ - shutil.rmtree('rattail_woocommerce.egg-info') - ctx.run('python setup.py sdist --formats=gztar') - ctx.run('twine upload dist/rattail-woocommerce-{}.tar.gz'.format(__version__)) + if os.path.exists('dist'): + shutil.rmtree('dist') + if os.path.exists('rattail_woocommerce.egg-info'): + shutil.rmtree('rattail_woocommerce.egg-info') + c.run('python -m build --sdist') + c.run('twine upload dist/*')