Work in Progress

This commit is contained in:
Sylvain LE GAL 2022-03-25 21:36:17 +01:00
parent d64140487f
commit e81e22e91b
16 changed files with 1080 additions and 559 deletions

BIN
.coverage Normal file

Binary file not shown.

7
.gitignore vendored
View File

@ -1,2 +1,7 @@
env
__pycache__
__pycache__
.tox
.coverage
.pytest_cache
/tests/output/*
log/

61
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,61 @@
---
image: python
cache:
key: one-key-to-rule-them-all
paths:
- .venv
stages:
- prepare
- linting
- tests
install_tools:
stage: prepare
script:
- python -m venv .venv
- source .venv/bin/activate
- pip install poetry
- poetry --version
- poetry install -v
- echo $PATH
- echo $PYTHONPATH
black:
stage: linting
script:
- source .venv/bin/activate
- pip install black
- black --version
- black --check .
pylint:
stage: linting
script:
- source .venv/bin/activate
- pylint --version
# - pylint --disable fixme ociedoo
# - pylint --disable fixme tests
pytest:
stage: tests
image: python
cache: {}
script:
- pip install poetry
- poetry --version
- poetry install -v
- poetry run pytest --version
- poetry run pytest --cov odoo_openupgrade_wizard -v
tox:
stage: tests
image: themattrix/tox
cache: {}
script:
- pip install poetry tox
- tox --version
- poetry --version
- tox

36
.pre-commit-config.yaml Normal file
View File

@ -0,0 +1,36 @@
---
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.4.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: debug-statements
- id: mixed-line-ending
- id: name-tests-test
- id: check-yaml
- id: check-json
- id: check-toml
- id: check-added-large-files
args: ['--maxkb=2048']
- id: check-docstring-first
- id: check-merge-conflict
- id: check-symlinks
- repo: https://github.com/pre-commit/mirrors-isort
rev: v5.7.0
hooks:
- id: isort
- repo: https://github.com/psf/black
rev: 20.8b1
hooks:
- id: black
- repo: https://github.com/PyCQA/flake8
rev: "3.9.2"
hooks:
- id: flake8
# - repo: https://gitlab.com/smop/pre-commit-hooks
# rev: v1.0.0
# hooks:
# - id: check-gitlab-ci

View File

@ -1,9 +1,82 @@
# odoo-openupgrade-wizard
Odoo Openupgrade Wizard is a tool that helps developpers to make major
upgrade of Odoo Community Edition. (formely OpenERP).
It works with Openupgrade OCA tools. (https://github.com/oca/openupgrade)
this tool is useful for complex migrations:
- skip several versions
- complex custom code
It will create a migration environment (with all the code available)
and provides helpers to run (and replay) migrations until it works.
## Commands
### ``odoo-openupgrade-wizard init``
```
odoo-openupgrade-wizard init\
--initial-version=10.0\
--final-version=12.0\
--extra-repository=OCA/web,OCA/server-tools
```
Initialize a folder to make a migration from a 10.0 and a 12.0 database.
This will generate the following structure :
```
config.yml
log/
2022_03_25__23_12_41__init.log
...
repos/
10.0.yml
11.0.yml
12.0.yml
requirements/
10.0_requirements.txt
11.0_requirements.txt
12.0_requirements.txt
scripts/
step_1__update__10.0/
pre-migration.sql
post-migration.py
step_2__upgrade__11.0/
pre-migration.sql
post-migration.py
step_2__upgrade__12.0/
pre-migration.sql
post-migration.py
step_4__update__12.0/
pre-migration.sql
post-migration.py
src/
```
* ``log/`` will contains all the log of the ``odoo-openupgrade-wizard``
and the logs of the odoo instance that will be executed.
* ``repos/`` contains a file per version of odoo, that enumerates the
list of the repositories to use to run each odoo instance.
The syntax should respect the ``gitaggregate`` command.
(See : https://pypi.org/project/git-aggregator/)
Repo files are pre-generated. You can update them with your custom settings.
(custom branches, extra PRs, git shallow options, etc...)
* ``requirements/`` contains a file per version of odoo, that enumerates the
list of extra python librairies required to run each odoo instance.
The syntax should respect the ``pip install -r`` command.
(See : https://pip.pypa.io/en/stable/reference/requirements-file-format/)
* ``scripts`` contains a folder per migration step. In each step folder:
- ``pre-migration.sql`` can contains extra SQL queries you want to execute
before beginning the step.
- ``post-migration.py`` can contains extra python command to execute
after the execution of the step. (the orm will be available)
# TODO
* restore ``# @click.version_option(version=ociedoo.__version__)`` when we know how
to avoid to duplicate information in ``pyproject.toml`` file and ``__init__.py`` file.
* move jinja2 templates files into dedicated files.
* with coop it easy :
- short_help of group decorator ? seems useless...

View File

@ -0,0 +1,5 @@
from pathlib import Path
from single_source import get_version
__version__ = get_version(__name__, Path(__file__).parent.parent)

View File

@ -1,47 +1,45 @@
import datetime
# import os
# import signal
# import subprocess
# import time
from pathlib import Path
from plumbum.cmd import mkdir
from jinja2 import Template
import time
import click
import yaml
from loguru import logger
from plumbum.cmd import mkdir
import odoo_openupgrade_wizard
from odoo_openupgrade_wizard.configuration_version_dependant import (
_get_odoo_version_str_list,
_get_odoo_versions,
)
from odoo_openupgrade_wizard.templates import (
_CONFIG_YML_TEMPLATE,
_REPO_YML_TEMPLATE,
_REQUIREMENTS_TXT_TEMPLATE,
_PRE_MIGRATION_SQL_TEMPLATE,
_POST_MIGRATION_PY_TEMPLATE,
)
from odoo_openupgrade_wizard.cli_build import build
from odoo_openupgrade_wizard.cli_init import init
# ############################################################################
# main()
# ############################################################################
@click.group(
short_help="Provide a wizard to simplify the use of OpenUpgrade.",
@click.group()
@click.version_option(version=odoo_openupgrade_wizard.__version__)
@click.option(
"-ef",
"--env-folder",
default="./",
type=click.Path(
exists=True,
dir_okay=True,
file_okay=False,
writable=True,
resolve_path=True,
),
help="Folder that will contains all the configuration of the wizard"
" and all the Odoo code required to make the migrations. Let empty to"
" use current folder (./).",
)
@click.option(
"-fs",
"--filestore-folder",
type=click.Path(dir_okay=True, file_okay=False, resolve_path=True),
help="Folder that contains the Odoo filestore of the database(s)"
" to migrate. Let empty to use the subfolder 'filestore' of the"
" environment folder.",
)
@click.option("-ef", "--env-folder", required=True,
type=click.Path(exists=True, dir_okay=True, file_okay=False, writable=True,
resolve_path=True))
@click.option("-fs", "--filestore-folder",
type=click.Path(dir_okay=True, file_okay=False,
resolve_path=True))
@click.pass_context
def main(ctx, env_folder, filestore_folder):
"""
TODO
Provides a command set to perform odoo Community Edition migrations.
"""
date_begin = datetime.datetime.now()
logger.debug("Beginning script '%s' ..." % (ctx.invoked_subcommand))
@ -72,8 +70,11 @@ def main(ctx, env_folder, filestore_folder):
mkdir(["--mode", "777", log_folder_path])
# Create log file
log_file_path = log_folder_path / Path("{}__{}.log".format(
date_begin.strftime("%Y_%m_%d__%H_%M_%S"), ctx.invoked_subcommand))
log_file_path = log_folder_path / Path(
"{}__{}.log".format(
date_begin.strftime("%Y_%m_%d__%H_%M_%S"), ctx.invoked_subcommand
)
)
logger.add(log_file_path)
ctx.obj["env_folder_path"] = env_folder_path
ctx.obj["src_folder_path"] = src_folder_path
@ -82,133 +83,18 @@ def main(ctx, env_folder, filestore_folder):
ctx.obj["filestore_folder_path"] = filestore_folder_path
ctx.obj["config_file_path"] = config_file_path
ctx.obj["requirement_folder_path"] = requirement_folder_path
# logger.info(ctx.obj)
if config_file_path.exists():
with open(config_file_path) as file:
config = yaml.safe_load(file)
for step in config["migration_steps"]:
step["local_path"] = src_folder_path / Path(
"env_%s" % step["version"]
)
ctx.obj["config"] = config
elif ctx.invoked_subcommand != "init":
raise
# ############################################################################
# init()
# ############################################################################
@main.command(
short_help="Initialize OpenUpgrade Wizard Environment.",
)
@click.option("-iv", "--initial-version", required=True, prompt=True,
type=click.Choice(_get_odoo_version_str_list("initial")))
@click.option("-fv", "--final-version", required=True, prompt=True,
type=click.Choice(_get_odoo_version_str_list("final")))
@click.option("-er", "--extra-repository", "extra_repository_list",
# TODO, add a callback to check the quality of the argument
help="Coma separated extra repositories to use in the odoo environment."
"Ex: 'OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator'")
@click.pass_context
def init(ctx, initial_version, final_version, extra_repository_list):
"""
TODO
"""
# 1. create src directory if not exists
if not ctx.obj["src_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["src_folder_path"]))
mkdir(["--mode", "777", ctx.obj["src_folder_path"]])
# 2. create filestore directory if not exists
if not ctx.obj["filestore_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["filestore_folder_path"]))
mkdir(["--mode", "777", ctx.obj["filestore_folder_path"]])
# 3. Create main config file
series = _get_odoo_versions(float(initial_version), float(final_version))
# Create initial first step
steps = [series[0].copy()]
steps[0].update({
"name": "step_1",
"action": "update",
"complete_name": "step_1__update__%s" % (steps[0]["version"]),
})
# Add all upgrade steps
count = 1
for serie in series[1:]:
steps.append(serie.copy())
steps[count].update({
"name": "step_%d" % (count+1),
"action": "upgrade",
"complete_name": "step_%d__upgrade__%s" % (count+1, serie["version"]),
})
count += 1
# add final update step
steps.append(series[-1].copy())
steps[-1].update({
"name": "step_%d" % (count + 1),
"action": "update",
"complete_name": "step_%d__update__%s" % (count+1, steps[-1]["version"]),
})
template = Template(_CONFIG_YML_TEMPLATE)
output = template.render(steps=steps)
with open(ctx.obj["config_file_path"], "w") as f:
logger.info("Creating configuration file '%s' ..." % (ctx.obj["config_file_path"]))
f.write(output)
f.close()
distinct_versions = list(set(x["version"] for x in series))
# 4. Create Repo folder and files
if not ctx.obj["repo_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["repo_folder_path"]))
mkdir([ctx.obj["repo_folder_path"]])
extra_repositories = extra_repository_list.split(",")
orgs = {x: [] for x in set([x.split("/")[0] for x in extra_repositories])}
for extra_repository in extra_repositories:
org, repo = extra_repository.split("/")
orgs[org].append(repo)
for version in distinct_versions:
template = Template(_REPO_YML_TEMPLATE)
output = template.render(version=version, orgs=orgs)
file_name =ctx.obj["repo_folder_path"] / Path("%s.yml" % (version))
with open(file_name, "w") as f:
logger.info("Creating Repo file '%s' ..." % (file_name))
f.write(output)
f.close()
# 5. Create Requirements folder and files
if not ctx.obj["requirement_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["requirement_folder_path"]))
mkdir([ctx.obj["requirement_folder_path"]])
for serie in series:
template = Template(_REQUIREMENTS_TXT_TEMPLATE)
output = template.render(python_libraries=serie["python_libraries"])
file_name =ctx.obj["requirement_folder_path"] / Path("%s_requirements.txt" % (serie["version"]))
with open(file_name, "w") as f:
logger.info("Creating Requirements file '%s' ..." % (file_name))
f.write(output)
f.close()
# 6. Create Scripts folder and files
if not ctx.obj["script_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["script_folder_path"]))
mkdir([ctx.obj["script_folder_path"]])
for step in steps:
step_path = ctx.obj["script_folder_path"] / step["complete_name"]
if not step_path.exists():
logger.info("Creating folder '%s' ..." % (step_path))
mkdir([step_path])
template = Template(_PRE_MIGRATION_SQL_TEMPLATE)
output = template.render()
file_name =step_path / Path("pre-migration.sql")
with open(file_name, "w") as f:
logger.info("Creating pre-migration.sql file '%s' ..." % (file_name))
f.write(output)
f.close()
template = Template(_POST_MIGRATION_PY_TEMPLATE)
output = template.render()
file_name =step_path / Path("post-migration.py")
with open(file_name, "w") as f:
logger.info("Creating post-migration.py file '%s' ..." % (file_name))
f.write(output)
f.close()
main.add_command(init)
main.add_command(build)

View File

@ -0,0 +1,28 @@
# from pathlib import Path
import click
from loguru import logger
from plumbum.cmd import mkdir
@click.command()
@click.pass_context
def build(ctx):
"""
Build OpenUpgrade Wizard Environment:
- gitaggregate all the repositories
- build virtualenv (TODO)
"""
# distinct_versions = list(set(x["version"] for x in series))
for step in ctx.obj["config"]["migration_steps"]:
# 1. Create main folder for the odoo version
if not step["local_path"].exists():
logger.info("Creating folder '%s' ..." % (step["local_path"]))
mkdir(["--mode", "777", step["local_path"]])
# # 2. gitaggregate source code
# repo_file = ctx.obj["repo_folder_path"] / Path(
# "%s.yml" % (step["version"])
# )

View File

@ -0,0 +1,180 @@
from pathlib import Path
import click
from jinja2 import Template
from loguru import logger
from plumbum.cmd import mkdir
from odoo_openupgrade_wizard.configuration_version_dependant import (
_get_odoo_version_str_list,
_get_odoo_versions,
)
from odoo_openupgrade_wizard.templates import (
_CONFIG_YML_TEMPLATE,
_POST_MIGRATION_PY_TEMPLATE,
_PRE_MIGRATION_SQL_TEMPLATE,
_REPO_YML_TEMPLATE,
_REQUIREMENTS_TXT_TEMPLATE,
)
@click.command()
@click.option(
"-iv",
"--initial-version",
required=True,
prompt=True,
type=click.Choice(_get_odoo_version_str_list("initial")),
)
@click.option(
"-fv",
"--final-version",
required=True,
prompt=True,
type=click.Choice(_get_odoo_version_str_list("final")),
)
@click.option(
"-er",
"--extra-repository",
"extra_repository_list",
# TODO, add a callback to check the quality of the argument
help="Coma separated extra repositories to use in the odoo environment."
"Ex: 'OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator'",
)
@click.pass_context
def init(ctx, initial_version, final_version, extra_repository_list):
"""
Initialize OpenUpgrade Wizard Environment based on the initial and
the final version of Odoo you want to migrate.
"""
# 1. create src directory if not exists
if not ctx.obj["src_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["src_folder_path"]))
mkdir(["--mode", "777", ctx.obj["src_folder_path"]])
# 2. create filestore directory if not exists
if not ctx.obj["filestore_folder_path"].exists():
logger.info(
"Creating folder '%s' ..." % (ctx.obj["filestore_folder_path"])
)
mkdir(["--mode", "777", ctx.obj["filestore_folder_path"]])
# 3. Create main config file
series = _get_odoo_versions(float(initial_version), float(final_version))
# Create initial first step
steps = [series[0].copy()]
steps[0].update(
{
"name": "step_1",
"action": "update",
"complete_name": "step_1__update__%s" % (steps[0]["version"]),
}
)
# Add all upgrade steps
count = 1
for serie in series[1:]:
steps.append(serie.copy())
steps[count].update(
{
"name": "step_%d" % (count + 1),
"action": "upgrade",
"complete_name": "step_%d__upgrade__%s"
% (count + 1, serie["version"]),
}
)
count += 1
# add final update step
steps.append(series[-1].copy())
steps[-1].update(
{
"name": "step_%d" % (count + 1),
"action": "update",
"complete_name": "step_%d__update__%s"
% (count + 1, steps[-1]["version"]),
}
)
template = Template(_CONFIG_YML_TEMPLATE)
output = template.render(steps=steps)
with open(ctx.obj["config_file_path"], "w") as f:
logger.info(
"Creating configuration file '%s' ..."
% (ctx.obj["config_file_path"])
)
f.write(output)
f.close()
distinct_versions = list(set(x["version"] for x in series))
# 4. Create Repo folder and files
if not ctx.obj["repo_folder_path"].exists():
logger.info("Creating folder '%s' ..." % (ctx.obj["repo_folder_path"]))
mkdir([ctx.obj["repo_folder_path"]])
extra_repositories = extra_repository_list.split(",")
orgs = {x: [] for x in set([x.split("/")[0] for x in extra_repositories])}
for extra_repository in extra_repositories:
org, repo = extra_repository.split("/")
orgs[org].append(repo)
for version in distinct_versions:
template = Template(_REPO_YML_TEMPLATE)
output = template.render(version=version, orgs=orgs)
file_name = ctx.obj["repo_folder_path"] / Path("%s.yml" % (version))
with open(file_name, "w") as f:
logger.info("Creating Repo file '%s' ..." % (file_name))
f.write(output)
f.close()
# 5. Create Requirements folder and files
if not ctx.obj["requirement_folder_path"].exists():
logger.info(
"Creating folder '%s' ..." % (ctx.obj["requirement_folder_path"])
)
mkdir([ctx.obj["requirement_folder_path"]])
for serie in series:
template = Template(_REQUIREMENTS_TXT_TEMPLATE)
output = template.render(python_libraries=serie["python_libraries"])
file_name = ctx.obj["requirement_folder_path"] / Path(
"%s_requirements.txt" % (serie["version"])
)
with open(file_name, "w") as f:
logger.info("Creating Requirements file '%s' ..." % (file_name))
f.write(output)
f.close()
# 6. Create Scripts folder and files
if not ctx.obj["script_folder_path"].exists():
logger.info(
"Creating folder '%s' ..." % (ctx.obj["script_folder_path"])
)
mkdir([ctx.obj["script_folder_path"]])
for step in steps:
step_path = ctx.obj["script_folder_path"] / step["complete_name"]
if not step_path.exists():
logger.info("Creating folder '%s' ..." % (step_path))
mkdir([step_path])
template = Template(_PRE_MIGRATION_SQL_TEMPLATE)
output = template.render()
file_name = step_path / Path("pre-migration.sql")
with open(file_name, "w") as f:
logger.info(
"Creating pre-migration.sql file '%s' ..." % (file_name)
)
f.write(output)
f.close()
template = Template(_POST_MIGRATION_PY_TEMPLATE)
output = template.render()
file_name = step_path / Path("post-migration.py")
with open(file_name, "w") as f:
logger.info(
"Creating post-migration.py file '%s' ..." % (file_name)
)
f.write(output)
f.close()

View File

@ -2,19 +2,64 @@
# python version is defined, based on the OCA CI.
# https://github.com/OCA/oca-addons-repo-template/blob/master/src/.github/workflows/%7B%25%20if%20ci%20%3D%3D%20'GitHub'%20%25%7Dtest.yml%7B%25%20endif%20%25%7D.jinja
_ODOO_SERIES = [
{"version": 6.0, "python": "python2.7", "python_libraries": ["openupgradelib"]},
{"version": 6.1, "python": "python2.7", "python_libraries": ["openupgradelib"]},
{"version": 7.0, "python": "python2.7", "python_libraries": ["openupgradelib"]},
{"version": 8.0, "python": "python2.7", "python_libraries": ["openupgradelib"]},
{"version": 9.0, "python": "python2.7", "python_libraries": ["openupgradelib"]},
{"version": 10.0, "python": "python2.7", "python_libraries": ["openupgradelib"]},
{"version": 11.0, "python": "python3.5", "python_libraries": ["openupgradelib"]},
{"version": 12.0, "python": "python3.6", "python_libraries": ["openupgradelib"]},
{"version": 13.0, "python": "python3.6", "python_libraries": ["openupgradelib"]},
{"version": 14.0, "python": "python3.6", "python_libraries": ["openupgradelib"]},
{"version": 15.0, "python": "python3.8", "python_libraries": ["openupgradelib"]},
{
"version": 6.0,
"python": "python2.7",
"python_libraries": ["openupgradelib"],
},
{
"version": 6.1,
"python": "python2.7",
"python_libraries": ["openupgradelib"],
},
{
"version": 7.0,
"python": "python2.7",
"python_libraries": ["openupgradelib"],
},
{
"version": 8.0,
"python": "python2.7",
"python_libraries": ["openupgradelib"],
},
{
"version": 9.0,
"python": "python2.7",
"python_libraries": ["openupgradelib"],
},
{
"version": 10.0,
"python": "python2.7",
"python_libraries": ["openupgradelib"],
},
{
"version": 11.0,
"python": "python3.5",
"python_libraries": ["openupgradelib"],
},
{
"version": 12.0,
"python": "python3.6",
"python_libraries": ["openupgradelib"],
},
{
"version": 13.0,
"python": "python3.6",
"python_libraries": ["openupgradelib"],
},
{
"version": 14.0,
"python": "python3.6",
"python_libraries": ["openupgradelib"],
},
{
"version": 15.0,
"python": "python3.8",
"python_libraries": ["openupgradelib"],
},
]
def _get_odoo_version_str_list(mode):
serie_list = [x["version"] for x in _ODOO_SERIES]
if mode == "initial":
@ -23,9 +68,10 @@ def _get_odoo_version_str_list(mode):
serie_list = serie_list[1:]
return [str(x) for x in serie_list]
def _get_odoo_versions(initial, final):
result = []
for serie in _ODOO_SERIES:
if serie["version"] >= initial and serie["version"] <= final:
result.append(serie)
return result
return result

View File

@ -1,8 +1,7 @@
_CONFIG_YML_TEMPLATE = """
migration_steps:
_CONFIG_YML_TEMPLATE = """migration_steps:
{% for step in steps %}
- name: {{ step['name'] }}
- complete_name: {{ step['complete_name'] }}
complete_name: {{ step['complete_name'] }}
version: {{ step['version'] }}
action: {{ step['action'] }}
python: {{ step['python'] }}
@ -59,4 +58,4 @@ _PRE_MIGRATION_SQL_TEMPLATE = ""
_POST_MIGRATION_PY_TEMPLATE = """
def main(self, step):
pass
"""
"""

877
poetry.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -25,10 +25,11 @@ classifiers = [
odoo-openupgrade-wizard = "odoo_openupgrade_wizard.cli:main"
[tool.poetry.dependencies]
python = "^3.5"
python = "^3.6"
click = "^7.0"
loguru = "^0.6"
plumbum = "^1.7"
single-source = "^0.3"
[tool.poetry.dev-dependencies]
pytest = [
@ -59,4 +60,4 @@ line-length = 79
[tool.isort]
profile = "black"
line_length = 79
line_length = 79

30
tests/cli_init_test.py Normal file
View File

@ -0,0 +1,30 @@
import filecmp
from pathlib import Path
from click.testing import CliRunner
from plumbum.cmd import mkdir
from odoo_openupgrade_wizard.cli import main
def test_cli_init():
output_folder_path = Path("./tests/output")
expected_folder_path = Path("./tests/output_expected")
mkdir([output_folder_path, "--parents"])
result = CliRunner().invoke(
main,
[
"--env-folder=%s" % output_folder_path,
"init",
"--initial-version=9.0",
"--final-version=12.0",
"--extra-repository="
"OCA/web,OCA/server-tools,GRAP/grap-odoo-incubator",
],
)
assert result.exit_code == 0
assert filecmp.cmp(
output_folder_path / Path("config.yml"),
expected_folder_path / Path("config.yml"),
)

View File

@ -0,0 +1,31 @@
migration_steps:
- name: step_1
complete_name: step_1__update__9.0
version: 9.0
action: update
python: python2.7
- name: step_2
complete_name: step_2__upgrade__10.0
version: 10.0
action: upgrade
python: python2.7
- name: step_3
complete_name: step_3__upgrade__11.0
version: 11.0
action: upgrade
python: python3.5
- name: step_4
complete_name: step_4__upgrade__12.0
version: 12.0
action: upgrade
python: python3.6
- name: step_5
complete_name: step_5__update__12.0
version: 12.0
action: update
python: python3.6

11
tox.ini Normal file
View File

@ -0,0 +1,11 @@
[tox]
isolated_build = true
skipsdist = True
; envlist = py35, py36, py37, py38
envlist = py36
[testenv]
whitelist_externals = poetry
commands =
poetry install -v
poetry run pytest -vv