From ac404ded41aa14079e66d1a590522c742f360d56 Mon Sep 17 00:00:00 2001 From: Hernan Grecco Date: Sun, 3 Dec 2023 20:09:47 -0300 Subject: [PATCH] Modernize project infrastructure - Add publish github action - Add lint autoupdate github action - Updated pre-commit-config - Changed readme from RST to MD - Changed setup.py/setup.cfg to pyproject.toml - Write requirements in separate file. --- .github/workflows/ci.yml | 59 ++++----- .github/workflows/lint-autoupdate.yml | 46 +++++++ .github/workflows/lint.yml | 2 +- .github/workflows/publish.yml | 27 ++++ .pre-commit-config.yaml | 30 +++-- README.md | 126 ++++++++++++++++++ README.rst | 134 -------------------- pyproject.toml | 58 +++++++++ supported_pkgs.txt => requirements.full.txt | 0 requirements.test.txt | 2 + requirements.txt | 3 + setup.cfg | 73 ----------- setup.py | 5 - version.py | 6 - 14 files changed, 310 insertions(+), 261 deletions(-) create mode 100644 .github/workflows/lint-autoupdate.yml create mode 100644 .github/workflows/publish.yml create mode 100644 README.md delete mode 100644 README.rst create mode 100644 pyproject.toml rename supported_pkgs.txt => requirements.full.txt (100%) create mode 100644 requirements.test.txt create mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py delete mode 100644 version.py diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 94cc594..312635b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,55 +1,37 @@ name: CI -on: [push, pull_request] +on: [push] jobs: test-linux: + runs-on: ubuntu-latest strategy: matrix: - python-version: [3.6, 3.7, 3.8, 3.9, '3.10'] - extras: [true, false] - - runs-on: ubuntu-latest + python-version: ["3.9", "3.10", "3.11", "3.12"] env: - TEST_OPTS: "-rfsxEX -s --cov=serialize --cov-config=.coveragerc" + TEST_OPTS: "-rfsxEX -s --cov=. --cov-config=.coveragerc" steps: - - uses: actions/checkout@v2 - with: - fetch-depth: 100 - - - name: Get tags - run: git fetch --depth=1 origin +refs/tags/*:refs/tags/* + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} + cache: 'pip' - - name: Get pip cache dir - id: pip-cache - run: echo "::set-output name=dir::$(pip cache dir)" + - name: Upgrade pip + run: python -m pip install --upgrade pip - - name: Setup caching - uses: actions/cache@v2 - with: - path: ${{ steps.pip-cache.outputs.dir }} - key: pip-${{ matrix.python-version }} - restore-keys: | - pip-${{ matrix.python-version }} + - name: Install coverage + run: pip install pytest-cov - - name: Install dependencies - run: | - pip install .[test] + - name: Install package + run: python -m pip install .[test] - - name: Install extras - if: ${{ matrix.extras != false }} - run: pip install -r supported_pkgs.txt - - - name: Run Tests - run: | - pytest $TEST_OPTS + - name: Test with pytest + run: pytest $TEST_OPTS - name: Coverage report run: coverage report -m @@ -72,9 +54,20 @@ jobs: with: python-version: 3.x - name: Coveralls Finished + continue-on-error: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} COVERALLS_SERVICE_NAME: github run: | pip install coveralls coveralls --finish + + # Dummy task to summarize all. See https://github.com/bors-ng/bors-ng/issues/1300 + ci-success: + name: ci + if: ${{ success() }} + needs: test-linux + runs-on: ubuntu-latest + steps: + - name: CI succeeded + run: exit 0 diff --git a/.github/workflows/lint-autoupdate.yml b/.github/workflows/lint-autoupdate.yml new file mode 100644 index 0000000..a08298e --- /dev/null +++ b/.github/workflows/lint-autoupdate.yml @@ -0,0 +1,46 @@ +name: pre-commit + +on: + schedule: + - cron: "0 0 * * 0" # every Sunday at 00:00 UTC + workflow_dispatch: + + +jobs: + autoupdate: + name: autoupdate + runs-on: ubuntu-latest + if: github.repository == 'hgrecco/serialize' + steps: + - name: checkout + uses: actions/checkout@v2 + - name: Cache pip and pre-commit + uses: actions/cache@v2 + with: + path: | + ~/.cache/pre-commit + ~/.cache/pip + key: ${{ runner.os }}-pre-commit-autoupdate + - name: setup python + uses: actions/setup-python@v2 + with: + python-version: 3.x + - name: upgrade pip + run: python -m pip install --upgrade pip + - name: install dependencies + run: python -m pip install --upgrade pre-commit + - name: version info + run: python -m pip list + - name: autoupdate + uses: technote-space/create-pr-action@bfd4392c80dbeb54e0bacbcf4750540aecae6ed4 + with: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + EXECUTE_COMMANDS: | + python -m pre_commit autoupdate + python -m pre_commit run --all-files + COMMIT_MESSAGE: 'pre-commit: autoupdate hook versions' + COMMIT_NAME: 'github-actions[bot]' + COMMIT_EMAIL: 'github-actions[bot]@users.noreply.github.com' + PR_TITLE: 'pre-commit: autoupdate hook versions' + PR_BRANCH_PREFIX: 'pre-commit/' + PR_BRANCH_NAME: 'autoupdate-${PR_ID}' diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index b10a674..e2d2638 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -14,4 +14,4 @@ jobs: - name: Lint uses: pre-commit/action@v2.0.0 with: - extra_args: --all-files --show-diff-on-failure \ No newline at end of file + extra_args: --all-files --show-diff-on-failure diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..3cf9f79 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,27 @@ +name: Build and publish to PyPI + +on: + push: + tags: + - '*' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: actions/setup-python@v4 + with: + python-version: '3.x' + + - name: Install dependencies + run: python -m pip install build + + - name: Build package + run: python -m build + + - name: Publish to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + password: ${{ secrets.PYPI_API_TOKEN }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 524fd13..8420694 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,13 +1,25 @@ repos: -- repo: https://github.com/psf/black - rev: 21.12b0 +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 hooks: - - id: black -- repo: https://github.com/pycqa/isort - rev: 5.10.1 + - id: check-yaml + - id: end-of-file-fixer + - id: trailing-whitespace +- repo: https://github.com/charliermarsh/ruff-pre-commit + rev: v0.1.5 hooks: - - id: isort -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 + - id: ruff + args: ["--fix"] + - id: ruff-format +- repo: https://github.com/executablebooks/mdformat + rev: 0.7.16 hooks: - - id: flake8 \ No newline at end of file + - id: mdformat + additional_dependencies: + - mdformat-gfm # GitHub-flavored Markdown + - mdformat-black +- repo: https://github.com/kynan/nbstripout + rev: 0.6.1 + hooks: + - id: nbstripout + args: [--extra-keys=metadata.kernelspec metadata.language_info.version] diff --git a/README.md b/README.md new file mode 100644 index 0000000..a27b600 --- /dev/null +++ b/README.md @@ -0,0 +1,126 @@ +[![Latest Version](https://img.shields.io/pypi/v/serialize.svg)](https://pypi.python.org/pypi/serialize) + +[![License](https://img.shields.io/pypi/l/serialize.svg)](https://pypi.python.org/pypi/serialize) + +[![Python Versions](https://img.shields.io/pypi/pyversions/serialize.svg)](https://pypi.python.org/pypi/serialize) + +[![CI](https://github.com/hgrecco/serialize/workflows/CI/badge.svg)](https://github.com/hgrecco/serialize/actions?query=workflow%3ACI) + +[![LINTER](https://github.com/hgrecco/serialize/workflows/Lint/badge.svg)](https://github.com/hgrecco/serialize/actions?query=workflow%3ALint) + +[![Coverage](https://coveralls.io/repos/github/hgrecco/serialize/badge.svg?branch=master)](https://coveralls.io/github/hgrecco/serialize?branch=master) + +# Serialize: A common Python API for multiple serialization formats + +``` +There are multiple serialization formats out there ... + ... and great packages to use them. +``` + +But they all have a different API and switching among them is not so +simple as it should be. Serialize helps you to do it, including dealing +with custom classes. Let's dump a dict using the +pickle format: + +```python +>>> from serialize import dumps, loads +>>> dumps(dict(answer=42), fmt='pickle') +b'\x80\x03}q\x00X\x06\x00\x00\x00answerq\x01K*s.' +>>> loads(_, fmt='pickle') +{'answer': 42} +``` + +And here comes the cool thing, you can just change the serialization +format without having to learn a new API. Let's now dump it using +msgpack: + +```python +>>> dumps(dict(answer=42), fmt='msgpack') +b'\x81\xa6answer*' +>>> loads(_, fmt='msgpack') +{'answer': 42} +``` + +Serialize currently support 8 different formats: +bson, +dill, json +(builtin or with simplejson package), +msgpack, +phpserialize, +pickle, +serpent and +yaml. Serialize does not implement these +formats but rather relies on established, well tested packages. If they +are installed, serialize will use them. + +``` +** Serialize allows you to use them all with the same API! ** +``` + +You can also use the dump and +load to write directly to file-like +object: + +```python +>>> from serialize import dump, load +>>> with open('output.yaml', 'wb') as fp: +... dump(dict(answer=42), fp, fmt='yaml') +>>> with open('output.yaml', 'rb') as fp: +... load(fp, fmt='yaml') +{'answer': 42} +``` + +or use directly the filename and the format will be inferred: + +```python +>>> dump(dict(answer=42), 'output.yaml') +>>> load('output.yaml') +{'answer': 42} +``` + +A very common case is to dump and load objects from custom classes such +as: + +```python +>>> class User: +... def __init__(self, name, age): +... self.name = name +... self.age = age +... +>>> john = User('John Smith', 27) +``` + +But some serialization packages do not support this important feature +and the rest usually have very different API between them. Serialize +provides you a common, simple interface for this. You just need to +define a function that is able to convert the object to an instance of a +builtin type and the converse: + +```python +>>> from serialize import register_class +>>> def user_to_builtin(u): +... return (u.name, u.age) +... +>>> def user_from_builtin(c): +... return User(c[0], c[1]) +... + +>>> register_class(User, user_to_builtin, user_from_builtin) +``` + +And that's all. You can then use it directly without any hassle: + +```python +>>> dumps(john, fmt='bson') +b"y\x00\x00\x00\x03__bson_follow__\x00c\x00\x00\x00\x04__dumped_obj__ +\x00\x1e\x00\x00\x00\x020\x00\x0b\x00\x00\x00John Smith\x00\x101\x00 +\x1b\x00\x00\x00\x00\x02__class_name__\x00\x1c\x00\x00\x00\x00\x00\x00" +>>> v = loads(_, fmt='bson') +>>> v.name +'John Smith' +>>> v.age +27 +``` + +Enjoy! diff --git a/README.rst b/README.rst deleted file mode 100644 index e556e99..0000000 --- a/README.rst +++ /dev/null @@ -1,134 +0,0 @@ -.. image:: https://img.shields.io/pypi/v/serialize.svg - :target: https://pypi.python.org/pypi/serialize - :alt: Latest Version - -.. image:: https://img.shields.io/pypi/l/serialize.svg - :target: https://pypi.python.org/pypi/serialize - :alt: License - -.. image:: https://img.shields.io/pypi/pyversions/serialize.svg - :target: https://pypi.python.org/pypi/serialize - :alt: Python Versions - -.. image:: https://github.com/hgrecco/serialize/workflows/CI/badge.svg - :target: https://github.com/hgrecco/serialize/actions?query=workflow%3ACI - :alt: CI - -.. image:: https://github.com/hgrecco/serialize/workflows/Lint/badge.svg - :target: https://github.com/hgrecco/serialize/actions?query=workflow%3ALint - :alt: LINTER - - -.. image:: https://coveralls.io/repos/github/hgrecco/serialize/badge.svg?branch=master - :target: https://coveralls.io/github/hgrecco/serialize?branch=master - :alt: Coverage - - -Serialize: A common Python API for multiple serialization formats -================================================================= - -:: - - There are multiple serialization formats out there ... - ... and great packages to use them. - -But they all have a different API and switching among them is not so simple -as it should be. Serialize helps you to do it, including dealing with custom -classes. Let's dump a dict using the `pickle` format: - -.. code-block:: python - - >>> from serialize import dumps, loads - >>> dumps(dict(answer=42), fmt='pickle') - b'\x80\x03}q\x00X\x06\x00\x00\x00answerq\x01K*s.' - >>> loads(_, fmt='pickle') - {'answer': 42} - -And here comes the cool thing, you can just change the serialization format -without having to learn a new API. Let's now dump it using msgpack: - -.. code-block:: python - - >>> dumps(dict(answer=42), fmt='msgpack') - b'\x81\xa6answer*' - >>> loads(_, fmt='msgpack') - {'answer': 42} - -Serialize currently support 8 different formats: `bson`, `dill`, `json` (builtin or with simplejson package), `msgpack`, -`phpserialize`, `pickle`, `serpent` and `yaml`. Serialize does not implement these -formats but rather relies on established, well tested packages. If they are installed, -serialize will use them. - -:: - - ** Serialize allows you to use them all with the same API! ** - - -You can also use the `dump` and `load` to write directly to file-like object: - -.. code-block:: python - - >>> from serialize import dump, load - >>> with open('output.yaml', 'wb') as fp: - ... dump(dict(answer=42), fp, fmt='yaml') - >>> with open('output.yaml', 'rb') as fp: - ... load(fp, fmt='yaml') - {'answer': 42} - -or use directly the filename and the format will be inferred: - -.. code-block:: python - - >>> dump(dict(answer=42), 'output.yaml') - >>> load('output.yaml') - {'answer': 42} - -A very common case is to dump and load objects from custom classes such as: - -.. code-block:: python - - >>> class User: - ... def __init__(self, name, age): - ... self.name = name - ... self.age = age - ... - >>> john = User('John Smith', 27) - - -But some serialization packages do not support this important feature and the -rest usually have very different API between them. Serialize provides -you a common, simple interface for this. You just need to define a function -that is able to convert the object to an instance of a builtin type and the -converse: - -.. code-block:: python - - >>> from serialize import register_class - >>> def user_to_builtin(u): - ... return (u.name, u.age) - ... - >>> def user_from_builtin(c): - ... return User(c[0], c[1]) - ... - - >>> register_class(User, user_to_builtin, user_from_builtin) - - -And that's all. You can then use it directly without any hassle: - -.. code-block:: python - - >>> dumps(john, fmt='bson') - b"y\x00\x00\x00\x03__bson_follow__\x00c\x00\x00\x00\x04__dumped_obj__ - \x00\x1e\x00\x00\x00\x020\x00\x0b\x00\x00\x00John Smith\x00\x101\x00 - \x1b\x00\x00\x00\x00\x02__class_name__\x00\x1c\x00\x00\x00\x00\x00\x00" - >>> v = loads(_, fmt='bson') - >>> v.name - 'John Smith' - >>> v.age - 27 - - -Enjoy! - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..08cd882 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,58 @@ +[project] +name = "Serialize" +authors = [ + {name="Hernan E. Grecco", email="hernan.grecco@gmail.com"} +] +license = {text = "BSD-3-Clause"} +description = "A common API for multiple serialization formats with support for custom classes" +readme = "README.rst" +maintainers = [ + {name="Hernan E. Grecco", email="hernan.grecco@gmail.com"}, +] +keywords = ["serialization", "deserialization", "packing", "unpacking"] +classifiers = [ + "Development Status :: 4 - Beta", + "Intended Audience :: Developers", + "Intended Audience :: End Users/Desktop", + "License :: OSI Approved :: BSD License", + "Operating System :: MacOS :: MacOS X", + "Operating System :: Microsoft :: Windows", + "Operating System :: POSIX", + "Programming Language :: Python", + "Topic :: Software Development :: Libraries", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", +] +requires-python = ">=3.9" +dynamic = ["dependencies", "optional-dependencies", "version"] + +[tool.setuptools.package-data] +serialize = ["py.typed", ] + +[tool.setuptools] +packages = ["serialize"] + +[build-system] +requires = ["setuptools", "setuptools-scm"] +build-backend = "setuptools.build_meta" + +[tool.setuptools.dynamic] +dependencies = {file = "requirements.txt"} +optional-dependencies.test = {file = "requirements.test.txt"} +optional-dependencies.full = {file = "requirements.full.txt"} + +[project.urls] +"Homepage" = "https://github.com/hgrecco/serialize" +"Bug Tracker" = "https://github.com/hgrecco/serialize/issues" + +[tool.setuptools_scm] + +[tool.pytest.ini_options] +addopts = "--import-mode=importlib --doctest-modules" +pythonpath = "src" + +[tool.ruff] +select = ["E", "F", "I"] +extend-include = ["*.ipynb"] diff --git a/supported_pkgs.txt b/requirements.full.txt similarity index 100% rename from supported_pkgs.txt rename to requirements.full.txt diff --git a/requirements.test.txt b/requirements.test.txt new file mode 100644 index 0000000..095959c --- /dev/null +++ b/requirements.test.txt @@ -0,0 +1,2 @@ + pytest + pytest-cov diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..152e558 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +setuptools +packaging +typing_extensions diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 290d474..0000000 --- a/setup.cfg +++ /dev/null @@ -1,73 +0,0 @@ -[metadata] -name = Serialize -version = 0.3.dev0 -author = Hernan E. Grecco -author_email = hernan.grecco@gmail.com -license = BSD -description = A common API for multiple serialization formats with support for custom classes -long_description = file: README.rst -keywords = serialization, deserialization, packing, unpacking -url = https://github.com/hgrecco/serialize -classifiers = - Development Status :: 4 - Beta - Intended Audience :: Developers - Intended Audience :: End Users/Desktop - License :: OSI Approved :: BSD License - Operating System :: MacOS :: MacOS X - Operating System :: Microsoft :: Windows - Operating System :: POSIX - Programming Language :: Python - Topic :: Software Development :: Libraries - Programming Language :: Python :: 3.6 - Programming Language :: Python :: 3.7 - Programming Language :: Python :: 3.8 - Programming Language :: Python :: 3.9 - Programming Language :: Python :: 3.10 - -[options] -packages = serialize -zip_safe = True -include_package_data = True -python_requires = >=3.6 -install_requires = - packaging - importlib-metadata; python_version < '3.8' -setup_requires = setuptools; setuptools_scm -test_suite = serialize.testsuite.testsuite - -[options.extras_require] -test = - pytest - pytest-cov - -[build-system] -requires = ["setuptools", "setuptools_scm", "wheel"] - -[flake8] -ignore= - # whitespace before ':' - doesn't work well with black - E203 - E402 - # line too long - let black worry about that - E501 - # do not assign a lambda expression, use a def - E731 - # line break before binary operator - W503 -exclude= - build - -[tool:pytest] -addopts = --pyargs - -[isort] -default_section=THIRDPARTY -known_first_party=serialize -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -use_parentheses=True -line_length=88 - -[zest.releaser] -python-file-with-version = version.py \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index f4f9665..0000000 --- a/setup.py +++ /dev/null @@ -1,5 +0,0 @@ -#!/usr/bin/env python3 -from setuptools import setup - -if __name__ == "__main__": - setup() diff --git a/version.py b/version.py deleted file mode 100644 index b29725f..0000000 --- a/version.py +++ /dev/null @@ -1,6 +0,0 @@ -# This is just for zest.releaser. Do not touch -# flake8: noqa - -# fmt: off -__version__ = '0.3.dev0' -# fmt: on