Repository: brainelectronics/micropython-modules (language: python)

Issue:
Support mip
As of MicroPython 1.20.0, `upip` is decomissioned and replaced by `mip`, see [MicroPython Release Notes 1.20.0](https://github.com/micropython/micropython/releases/tag/v1.20.0)


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index e8b817f..029e191 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -1,5 +1,7 @@
-# this file is *not* meant to cover or endorse the use of GitHub Actions, but rather to
-# help make automated releases for this project
+---
+
+# this file is *not* meant to cover or endorse the use of GitHub Actions, but
+# rather to help make automated releases for this project
 
 name: Upload Python Package
 
@@ -15,42 +17,42 @@ jobs:
   deploy:
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v3
-    - name: Set up Python
-      uses: actions/setup-python@v3
-      with:
-        python-version: '3.9'
-    - name: Install build dependencies
-      run: |
-        if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi
-    - name: Build package
-      run: |
-        changelog2version \
-          --changelog_file changelog.md \
-          --version_file be_helpers/version.py \
-          --version_file_type py \
-          --debug
-        python setup.py sdist
-        rm dist/*.orig
-      # sdist call create non conform twine files *.orig, remove them
-    - name: Publish package
-      uses: pypa/gh-action-pypi-publish@release/v1.5
-      with:
-        password: ${{ secrets.PYPI_API_TOKEN }}
-        skip_existing: true
-        verbose: true
-        print_hash: true
-    - name: 'Create changelog based release'
-      uses: brainelectronics/changelog-based-release@v1
-      with:
-        # note you'll typically need to create a personal access token
-        # with permissions to create releases in the other repo
-        # or you set the "contents" permissions to "write" as in this example
-        changelog-path: changelog.md
-        tag-name-prefix: ''
-        tag-name-extension: ''
-        release-name-prefix: ''
-        release-name-extension: ''
-        draft-release: true
-        prerelease: false
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Set up Python
+        uses: actions/setup-python@v3
+        with:
+          python-version: '3.9'
+      - name: Install build dependencies
+        run: |
+          if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi
+      - name: Build package
+        run: |
+          changelog2version \
+            --changelog_file changelog.md \
+            --version_file be_helpers/version.py \
+            --version_file_type py \
+            --debug
+          python setup.py sdist
+          rm dist/*.orig
+        # sdist call create non conform twine files *.orig, remove them
+      - name: Publish package
+        uses: pypa/gh-action-pypi-publish@release/v1.5
+        with:
+          password: ${{ secrets.PYPI_API_TOKEN }}
+          skip_existing: true
+          verbose: true
+          print_hash: true
+      - name: 'Create changelog based release'
+        uses: brainelectronics/changelog-based-release@v1
+        with:
+          # note you'll typically need to create a personal access token
+          # with permissions to create releases in the other repo
+          # or you set the "contents" permissions to "write" as in this example
+          changelog-path: changelog.md
+          tag-name-prefix: ''
+          tag-name-extension: ''
+          release-name-prefix: ''
+          release-name-extension: ''
+          draft-release: true
+          prerelease: false
diff --git a/.yamllint b/.yamllint
new file mode 100644
index 0000000..24d0614
--- /dev/null
+++ b/.yamllint
@@ -0,0 +1,13 @@
+---
+
+extends: default
+
+ignore:
+  - .tox
+  - .venv
+
+rules:
+  line-length:
+    level: warning
+    ignore:
+      - .github/workflows/*
diff --git a/README.md b/README.md
index 6a05f23..f235102 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,10 @@ brainelectronics projects.
 - [Installation](#installation)
     - [Install required tools](#install-required-tools)
 - [Setup](#setup)
-    - [Install package with pip](#install-package-with-pip)
+    - [Install package](#install-package)
+        - [General](#general)
+        - [Specific version](#specific-version)
+        - [Test version](#test-version)
     - [Manually](#manually)
     - [Generic Helper](#generic-helper)
     - [LED Helper](#led-helper)
@@ -67,7 +70,7 @@ pip install -r requirements.txt
 
 ## Setup
 
-### Install package with pip
+### Install package
 
 Connect your MicroPython board to a network
 
@@ -79,14 +82,58 @@ station.connect('SSID', 'PASSWORD')
 station.isconnected()
 ```
 
-and install this lib on the MicroPython device like this
+#### General
+
+Install the latest package version of this lib on the MicroPython device
+
+```python
+import mip
+mip.install("github:brainelectronics/micropython-modules")
+```
+
+For MicroPython versions below 1.19.1 use the `upip` package instead of `mip`
 
 ```python
 import upip
 upip.install('micropython-brainelectronics-helpers')
-# its dependencies will be installed alongside
 ```
 
+#### Specific version
+
+Install a specific, fixed package version of this lib on the MicroPython device
+
+```python
+import mip
+# install a verions of a specific branch
+mip.install("github:brainelectronics/micropython-modules", version="feature/support-mip")
+# install a tag version
+mip.install("github:brainelectronics/micropython-modules", version="1.7.0")
+```
+
+#### Test version
+
+Install a specific release candidate version uploaded to
+[Test Python Package Index](https://test.pypi.org/) on every PR on the
+MicroPython device. If no specific version is set, the latest stable version
+will be used.
+
+```python
+import mip
+mip.install("github:brainelectronics/micropython-modules", version="1.7.0-rc5.dev22")
+```
+
+For MicroPython versions below 1.19.1 use the `upip` package instead of `mip`
+
+```python
+import upip
+# overwrite index_urls to only take artifacts from test.pypi.org
+upip.index_urls = ['https://test.pypi.org/pypi']
+upip.install('micropython-brainelectronics-helpers')
+```
+
+See also [brainelectronics Test PyPi Server in Docker][ref-brainelectronics-test-pypiserver]
+for a test PyPi server running on Docker.
+
 ### Manually
 
 Copy the module to the MicroPython board and import them as shown below
@@ -110,11 +157,6 @@ cp be_helpers/* /pyboard/lib/be_helpers
 Install required dependencies (requires network connection, see may use the
 [`WifiHelper`][ref-wifi-helper])
 
-```python
-import upip
-upip.install('micropython-ulogging')
-```
-
 ### Generic Helper
 
 Generic helper class with different usecases and functions.
@@ -184,6 +226,11 @@ This example demonstrates how to interact with the onboard LED on the BE32-01
 ##### Basics
 
 The onboard LED is availabe on Pin 4 on the BE32-01 board in inverted mode.
+For the Raspberry Pi Pico (W) initialise the LED like this:
+```python
+from be_helpers.led_helper import Led
+led = Led(led_pin="LED", inverted=False)
+```
 
 ```python
 from be_helpers.led_helper import Led
@@ -340,7 +387,15 @@ pixel.color = 'DarlingColor'
 This requires [brainelectronics MicroPython Modbus][ref-be-upy-modbus]. Forked
 and extended from [SFERALABS Exo Sense Py][ref-sferalabs-exo-sense].
 
-Connect the board to a network and install the package like this
+Connect the board to a network and install the package like this for
+MicroPython 1.20.0 or never
+
+```python
+import mip
+mip.install("github:brainelectronics/micropython-modbus")
+```
+
+For MicroPython versions below 1.19.1 use the `upip` package instead of `mip`
 
 ```python
 import upip
@@ -469,6 +524,7 @@ print('Quality of strongest network {}: {}%'.format(strongest_net, quality))
 
 <!-- Links -->
 [ref-remote-upy-shell]: https://github.com/dhylands/rshell
+[ref-brainelectronics-test-pypiserver]: https://github.com/brainelectronics/test-pypiserver
 [ref-wifi-helper]: wifi_helper.py
 [ref-be-upy-modbus]: https://github.com/brainelectronics/micropython-modbus
 [ref-sferalabs-exo-sense]: https://github.com/sfera-labs/exo-sense-py-modbus
diff --git a/be_helpers/generic_helper.py b/be_helpers/generic_helper.py
index 1edc935..44b935f 100644
--- a/be_helpers/generic_helper.py
+++ b/be_helpers/generic_helper.py
@@ -9,7 +9,6 @@
 
 import gc
 import json
-import ulogging as logging
 import machine
 import os
 import random
@@ -19,6 +18,7 @@
 # custom packages
 # typing not natively supported on MicroPython
 from .typing import Optional, Union
+from . import ulogging as logging
 
 
 class GenericHelper(object):
diff --git a/be_helpers/ulogging.py b/be_helpers/ulogging.py
new file mode 100644
index 0000000..0d1c8d5
--- /dev/null
+++ b/be_helpers/ulogging.py
@@ -0,0 +1,260 @@
+#!/usr/bin/env python3
+# -*- coding: UTF-8 -*-
+
+"""
+This file has been copied from micropython-lib
+
+https://github.com/micropython/micropython-lib/blob/7128d423c2e7c0309ac17a1e6ba873b909b24fcc/python-stdlib/logging/logging.py
+"""
+
+try:
+    from micropython import const  # noqa: F401
+except ImportError:
+
+    def const(x):
+        return x
+
+
+import sys
+import time
+
+CRITICAL = const(50)
+ERROR = const(40)
+WARNING = const(30)
+INFO = const(20)
+DEBUG = const(10)
+NOTSET = const(0)
+
+_DEFAULT_LEVEL = const(WARNING)
+
+_level_dict = {
+    CRITICAL: "CRITICAL",
+    ERROR: "ERROR",
+    WARNING: "WARNING",
+    INFO: "INFO",
+    DEBUG: "DEBUG",
+    NOTSET: "NOTSET",
+}
+
+_loggers = {}
+_stream = sys.stderr
+_default_fmt = "%(levelname)s:%(name)s:%(message)s"
+_default_datefmt = "%Y-%m-%d %H:%M:%S"
+
+
+class LogRecord:
+    def set(self, name, level, message):
+        self.name = name
+        self.levelno = level
+        self.levelname = _level_dict[level]
+        self.message = message
+        self.ct = time.time()
+        self.msecs = int((self.ct - int(self.ct)) * 1000)
+        self.asctime = None
+
+
+class Handler:
+    def __init__(self, level=NOTSET):
+        self.level = level
+        self.formatter = None
+
+    def close(self):
+        pass
+
+    def setLevel(self, level):
+        self.level = level
+
+    def setFormatter(self, formatter):
+        self.formatter = formatter
+
+    def format(self, record):
+        return self.formatter.format(record)
+
+
+class StreamHandler(Handler):
+    def __init__(self, stream=None):
+        self.stream = _stream if stream is None else stream
+        self.terminator = "\n"
+
+    def close(self):
+        if hasattr(self.stream, "flush"):
+            self.stream.flush()
+
+    def emit(self, record):
+        if record.levelno >= self.level:
+            self.stream.write(self.format(record) + self.terminator)
+
+
+class FileHandler(StreamHandler):
+    def __init__(self, filename, mode="a", encoding="UTF-8"):
+        super().__init__(stream=open(filename, mode=mode, encoding=encoding))
+
+    def close(self):
+        super().close()
+        self.stream.close()
+
+
+class Formatter:
+    def __init__(self, fmt=None, datefmt=None):
+        self.fmt = _default_fmt if fmt is None else fmt
+        self.datefmt = _default_datefmt if datefmt is None else datefmt
+
+    def usesTime(self):
+        return "asctime" in self.fmt
+
+    def formatTime(self, datefmt, record):
+        if hasattr(time, "strftime"):
+            return time.strftime(datefmt, time.localtime(record.ct))
+        return None
+
+    def format(self, record):
+        if self.usesTime():
+            record.asctime = self.formatTime(self.datefmt, record)
+        return self.fmt % {
+            "name": record.name,
+            "message": record.message,
+            "msecs": record.msecs,
+            "asctime": record.asctime,
+            "levelname": record.levelname,
+        }
+
+
+class Logger:
+    def __init__(self, name, level=NOTSET):
+        self.name = name
+        self.level = level
+        self.handlers = []
+        self.record = LogRecord()
+
+    def setLevel(self, level):
+        self.level = level
+
+    def isEnabledFor(self, level):
+        return level >= self.getEffectiveLevel()
+
+    def getEffectiveLevel(self):
+        return self.level or getLogger().level or _DEFAULT_LEVEL
+
+    def log(self, level, msg, *args):
+        if self.isEnabledFor(level):
+            if args:
+                if isinstance(args[0], dict):
+                    args = args[0]
+                msg = msg % args
+            self.record.set(self.name, level, msg)
+            handlers = self.handlers
+            if not handlers:
+                handlers = getLogger().handlers
+            for h in handlers:
+                h.emit(self.record)
+
+    def debug(self, msg, *args):
+        self.log(DEBUG, msg, *args)
+
+    def info(self, msg, *args):
+        self.log(INFO, msg, *args)
+
+    def warning(self, msg, *args):
+        self.log(WARNING, msg, *args)
+
+    def error(self, msg, *args):
+        self.log(ERROR, msg, *args)
+
+    def critical(self, msg, *args):
+        self.log(CRITICAL, msg, *args)
+
+    def exception(self, msg, *args):
+        self.log(ERROR, msg, *args)
+        if hasattr(sys, "exc_info"):
+            sys.print_exception(sys.exc_info()[1], _stream)
+
+    def addHandler(self, handler):
+        self.handlers.append(handler)
+
+    def hasHandlers(self):
+        return len(self.handlers) > 0
+
+
+def getLogger(name=None):
+    if name is None:
+        name = "root"
+    if name not in _loggers:
+        _loggers[name] = Logger(name)
+        if name == "root":
+            basicConfig()
+    return _loggers[name]
+
+
+def log(level, msg, *args):
+    getLogger().log(level, msg, *args)
+
+
+def debug(msg, *args):
+    getLogger().debug(msg, *args)
+
+
+def info(msg, *args):
+    getLogger().info(msg, *args)
+
+
+def warning(msg, *args):
+    getLogger().warning(msg, *args)
+
+
+def error(msg, *args):
+    getLogger().error(msg, *args)
+
+
+def critical(msg, *args):
+    getLogger().critical(msg, *args)
+
+
+def exception(msg, *args):
+    getLogger().exception(msg, *args)
+
+
+def shutdown():
+    for k, logger in _loggers.items():
+        for h in logger.handlers:
+            h.close()
+        _loggers.pop(logger, None)
+
+
+def addLevelName(level, name):
+    _level_dict[level] = name
+
+
+def basicConfig(
+    filename=None,
+    filemode="a",
+    format=None,
+    datefmt=None,
+    level=WARNING,
+    stream=None,
+    encoding="UTF-8",
+    force=False,
+):
+    if "root" not in _loggers:
+        _loggers["root"] = Logger("root")
+
+    logger = _loggers["root"]
+
+    if force or not logger.handlers:
+        for h in logger.handlers:
+            h.close()
+        logger.handlers = []
+
+        if filename is None:
+            handler = StreamHandler(stream)
+        else:
+            handler = FileHandler(filename, filemode, encoding)
+
+        handler.setLevel(level)
+        handler.setFormatter(Formatter(format, datefmt))
+
+        logger.setLevel(level)
+        logger.addHandler(handler)
+
+
+if hasattr(sys, "atexit"):
+    sys.atexit(shutdown)
diff --git a/changelog.md b/changelog.md
index a153da6..5aa6104 100644
--- a/changelog.md
+++ b/changelog.md
@@ -13,6 +13,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 -->
 
 ## Released
+## [1.7.0] - 2023-05-27
+### Added
+- `ulogging` is part of `be_helpers` to be independent from other packages, see #20
+- `package.json` file and installation instruction for `mip` usage, see #21
+- Content of `package.json` is validated on each test workflow run
+
+### Removed
+- Dependency to `micropython-ulogging` package in `setup.py` file
+
+### Fixed
+- YAML syntax in all workflow files
+
 ## [1.6.1] - 2023-01-29
 ### Fixed
 - Typing hints for `Callable`, `Dict`, `List`, `Optional`, `Tuple` and `Union` are now subscriptable
@@ -172,8 +184,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
 - [`WifiHelper`](wifi_helper.py) module converted into class
 
 <!-- Links -->
-[Unreleased]: https://github.com/brainelectronics/micropython-modules/compare/1.6.1...develop
+[Unreleased]: https://github.com/brainelectronics/micropython-modules/compare/1.7.0...develop
 
+[1.7.0]: https://github.com/brainelectronics/micropython-modules/tree/1.7.0
 [1.6.1]: https://github.com/brainelectronics/micropython-modules/tree/1.6.1
 [1.6.0]: https://github.com/brainelectronics/micropython-modules/tree/1.6.0
 [1.5.0]: https://github.com/brainelectronics/micropython-modules/tree/1.5.0
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..9282730
--- /dev/null
+++ b/package.json
@@ -0,0 +1,58 @@
+{
+    "urls": [
+        [
+            "be_helpers/__init__.py",
+            "github:brainelectronics/micropython-modules/be_helpers/__init__.py"
+        ],
+        [
+            "be_helpers/generic_helper.py",
+            "github:brainelectronics/micropython-modules/be_helpers/generic_helper.py"
+        ],
+        [
+            "be_helpers/led_helper.py",
+            "github:brainelectronics/micropython-modules/be_helpers/led_helper.py"
+        ],
+        [
+            "be_helpers/message.py",
+            "github:brainelectronics/micropython-modules/be_helpers/message.py"
+        ],
+        [
+            "be_helpers/modbus_bridge.py",
+            "github:brainelectronics/micropython-modules/be_helpers/modbus_bridge.py"
+        ],
+        [
+            "be_helpers/path_helper.py",
+            "github:brainelectronics/micropython-modules/be_helpers/path_helper.py"
+        ],
+        [
+            "be_helpers/queue.py",
+            "github:brainelectronics/micropython-modules/be_helpers/queue.py"
+        ],
+        [
+            "be_helpers/time_helper.py",
+            "github:brainelectronics/micropython-modules/be_helpers/time_helper.py"
+        ],
+        [
+            "be_helpers/typing.py",
+            "github:brainelectronics/micropython-modules/be_helpers/typing.py"
+        ],
+        [
+            "be_helpers/ulogging.py",
+            "github:brainelectronics/micropython-modules/be_helpers/ulogging.py"
+        ],
+        [
+            "be_helpers/update_helper.py",
+            "github:brainelectronics/micropython-modules/be_helpers/update_helper.py"
+        ],
+        [
+            "be_helpers/version.py",
+            "github:brainelectronics/micropython-modules/be_helpers/version.py"
+        ],
+        [
+            "be_helpers/wifi_helper.py",
+            "github:brainelectronics/micropython-modules/be_helpers/wifi_helper.py"
+        ]
+    ],
+    "deps": [],
+    "version": "1.7.0"
+}
\ No newline at end of file
diff --git a/setup.py b/setup.py
index 142fa48..040b969 100644
--- a/setup.py
+++ b/setup.py
@@ -26,6 +26,7 @@
         'Development Status :: 4 - Beta',
         'Intended Audience :: Developers',
         'License :: OSI Approved :: MIT License',
+        'Programming Language :: Python :: Implementation :: MicroPython',
     ],
     keywords='micropython, brainelectronics, modules, library',
     project_urls={
@@ -35,5 +36,5 @@
     license='MIT',
     cmdclass={'sdist': sdist_upip.sdist},
     packages=['be_helpers'],
-    install_requires=['micropython-ulogging']
+    install_requires=[]
 )


Test patch:
diff --git a/.github/workflows/test-release.yaml b/.github/workflows/test-release.yaml
index 66ba8a7..3196dc8 100644
--- a/.github/workflows/test-release.yaml
+++ b/.github/workflows/test-release.yaml
@@ -1,5 +1,7 @@
-# this file is *not* meant to cover or endorse the use of GitHub Actions, but rather to
-# help make automated releases for this project
+---
+
+# this file is *not* meant to cover or endorse the use of GitHub Actions, but
+# rather to help make automated test releases for this project
 
 name: Upload Python Package to test.pypi.org
 
@@ -12,55 +14,56 @@ jobs:
   test-deploy:
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v3
-    - name: Set up Python
-      uses: actions/setup-python@v3
-      with:
-        python-version: '3.9'
-    - name: Install build dependencies
-      run: |
-        if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi
-    - name: Build package
-      run: |
-        changelog2version \
-          --changelog_file changelog.md \
-          --version_file be_helpers/version.py \
-          --version_file_type py \
-          --additional_version_info="-rc${{ github.run_number }}.dev${{ github.event.number }}" \
-          --debug
-        python setup.py sdist
-    - name: Test built package
-      # sdist call creates non twine conform "*.orig" files, remove them
-      run: |
-        rm dist/*.orig
-        twine check dist/*.tar.gz
-    - name: Archive build package artifact
-      uses: actions/upload-artifact@v3
-      with:
-        # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
-        # ${{ github.repository }} and ${{ github.ref_name }} can't be used for artifact name due to unallowed '/'
-        name: dist_repo.${{ github.event.repository.name }}_sha.${{ github.sha }}_build.${{ github.run_number }}
-        path: dist/*.tar.gz
-        retention-days: 14
-    - name: Publish package
-      uses: pypa/gh-action-pypi-publish@release/v1.5
-      with:
-        repository_url: https://test.pypi.org/legacy/
-        password: ${{ secrets.TEST_PYPI_API_TOKEN }}
-        skip_existing: true
-        verbose: true
-        print_hash: true
-    - name: 'Create changelog based prerelease'
-      uses: brainelectronics/changelog-based-release@v1
-      with:
-        # note you'll typically need to create a personal access token
-        # with permissions to create releases in the other repo
-        # or you set the "contents" permissions to "write" as in this example
-        changelog-path: changelog.md
-        tag-name-prefix: ''
-        tag-name-extension: '-rc${{ github.run_number }}.dev${{ github.event.number }}'
-        release-name-prefix: ''
-        release-name-extension: '-rc${{ github.run_number }}.dev${{ github.event.number }}'
-        draft-release: true
-        prerelease: true
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Set up Python
+        uses: actions/setup-python@v3
+        with:
+          python-version: '3.9'
+      - name: Install build dependencies
+        run: |
+          if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi
+      - name: Build package
+        run: |
+          changelog2version \
+            --changelog_file changelog.md \
+            --version_file be_helpers/version.py \
+            --version_file_type py \
+            --additional_version_info="-rc${{ github.run_number }}.dev${{ github.event.number }}" \
+            --debug
+          python setup.py sdist
+      - name: Test built package
+        # sdist call creates non twine conform "*.orig" files, remove them
+        run: |
+          rm dist/*.orig
+          twine check dist/*.tar.gz
+      - name: Archive build package artifact
+        uses: actions/upload-artifact@v3
+        with:
+          # https://docs.github.com/en/actions/learn-github-actions/contexts#github-context
+          # ${{ github.repository }} and ${{ github.ref_name }} can't be used
+          # for artifact name due to unallowed '/'
+          name: dist_repo.${{ github.event.repository.name }}_sha.${{ github.sha }}_build.${{ github.run_number }}
+          path: dist/*.tar.gz
+          retention-days: 14
+      - name: Publish package
+        uses: pypa/gh-action-pypi-publish@release/v1.5
+        with:
+          repository_url: https://test.pypi.org/legacy/
+          password: ${{ secrets.TEST_PYPI_API_TOKEN }}
+          skip_existing: true
+          verbose: true
+          print_hash: true
+      - name: 'Create changelog based prerelease'
+        uses: brainelectronics/changelog-based-release@v1
+        with:
+          # note you'll typically need to create a personal access token
+          # with permissions to create releases in the other repo
+          # or you set the "contents" permissions to "write" as in this example
+          changelog-path: changelog.md
+          tag-name-prefix: ''
+          tag-name-extension: '-rc${{ github.run_number }}.dev${{ github.event.number }}'
+          release-name-prefix: ''
+          release-name-extension: '-rc${{ github.run_number }}.dev${{ github.event.number }}'
+          draft-release: true
+          prerelease: true
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index 76883b7..98585e2 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -1,5 +1,9 @@
-# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
-# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
+---
+
+# This workflow will install Python dependencies, run tests and lint with a
+# specific Python version
+# For more information see:
+# https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
 
 name: Test Python package
 
@@ -17,31 +21,39 @@ jobs:
   build:
     runs-on: ubuntu-latest
     steps:
-    - name: Checkout
-      uses: actions/checkout@v3
-    - name: Set up Python
-      uses: actions/setup-python@v3
-      with:
-        python-version: '3.9'
-    - name: Install test dependencies
-      run: |
-        pip install -r requirements-test.txt
-    - name: Lint with flake8
-      run: |
-        flake8 .
-    - name: Install deploy dependencies
-      run: |
-        python -m pip install --upgrade pip
-        if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi
-    - name: Build package
-      run: |
-        changelog2version \
-          --changelog_file changelog.md \
-          --version_file be_helpers/version.py \
-          --version_file_type py \
-          --debug
-        python setup.py sdist
-        rm dist/*.orig
-    - name: Test built package
-      run: |
-        twine check dist/*
+      - name: Checkout
+        uses: actions/checkout@v3
+      - name: Set up Python
+        uses: actions/setup-python@v3
+        with:
+          python-version: '3.9'
+      - name: Install test dependencies
+        run: |
+          pip install -r requirements-test.txt
+      - name: Lint with flake8
+        run: |
+          flake8 .
+      - name: Install deploy dependencies
+        run: |
+          python -m pip install --upgrade pip
+          if [ -f requirements-deploy.txt ]; then pip install -r requirements-deploy.txt; fi
+      - name: Build package
+        run: |
+          changelog2version \
+            --changelog_file changelog.md \
+            --version_file be_helpers/version.py \
+            --version_file_type py \
+            --debug
+          python setup.py sdist
+          rm dist/*.orig
+      - name: Test built package
+        run: |
+          twine check dist/*
+      - name: Validate mip package file
+        run: |
+          upy-package \
+            --setup_file setup.py \
+            --package_changelog_file changelog.md \
+            --package_file package.json \
+            --validate \
+            --ignore-version
diff --git a/requirements-test.txt b/requirements-test.txt
index 3cfb187..7d15d18 100644
--- a/requirements-test.txt
+++ b/requirements-test.txt
@@ -3,3 +3,4 @@
 flake8>=5.0.0,<6
 coverage>=6.4.2,<7
 nose2>=0.12.0,<1
+setup2upypackage>=0.2.0,<1