Skip to content
This repository has been archived by the owner on May 16, 2022. It is now read-only.

Commit

Permalink
Merge pull request #206 from rpitonak/fix-170
Browse files Browse the repository at this point in the history
repository name != pypi name follow-up
  • Loading branch information
TomasTomecek authored Apr 9, 2019
2 parents 1b54c23 + 768e5c5 commit 5157870
Show file tree
Hide file tree
Showing 10 changed files with 104 additions and 25 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ Here are possible options:
| `changelog` | List of changelog entries. If empty, changelog defaults to `$version release` | No |
| `author_name` | Author name for changelog. If not set, author of the merge commit is used | No |
| `author_email`| Author email for changelog. If not set, author of the merge commit is used | No |
| `pypi` | Whether to release on pypi. True by default | No |
| `pypi` | Whether to release on pypi. True by default | No |
| `pypi_project'| Name of your PyPI repository | No |
| `trigger_on_issue`| Whether to allow bot to make PRs based on issues. False by default. | No |
| `labels` | List of labels that bot will put on issues and PRs | No |
| `labels` | List of labels that bot will put on issues and PRs | No |

Sample config named [release-conf-example.yaml](release-conf-example.yaml) can be found in this repository.

Expand Down
16 changes: 14 additions & 2 deletions release_bot/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import yaml
import sys

from release_bot.utils import get_pypi_project_from_setup_cfg
from release_bot.version import __version__


Expand All @@ -39,6 +40,8 @@ def __init__(self):
self.logger = None
self.set_logging()
self.dry_run = False
# used when PyPI project name != repository name
self.pypi_project = ''
# configuration when bot is deployed as github app
self.github_app_installation_id = ''
self.github_app_id = ''
Expand Down Expand Up @@ -121,8 +124,8 @@ def load_release_conf(self, conf):
parsed_conf = yaml.safe_load(conf) or {}
# If pypi option is not specified in release-conf.yaml,
# it defaults to true.
if parsed_conf.get('pypi') is None:
parsed_conf['pypi'] = True
parsed_conf.setdefault('pypi', True)

parsed_conf = {k: v for (k, v) in parsed_conf.items() if v}
for item in self.REQUIRED_ITEMS['release-conf']:
if item not in parsed_conf:
Expand All @@ -135,6 +138,15 @@ def load_release_conf(self, conf):
self.logger.warning(msg)
parsed_conf['trigger_on_issue'] = False

# HEADS UP: pypi_project is set as self's attribute, not returned in parsed_conf
# Try to get name from release-conf.yaml first, if it fails try to parse setup.cfg
self.pypi_project = parsed_conf.get('pypi_project') or get_pypi_project_from_setup_cfg()
if self.pypi_project is None:
msg = "pypi_project is not set, falling back to repository_name"
self.logger.warning(msg)
# Set pypi_project to repository name by default
self.pypi_project = self.repository_name

return parsed_conf


Expand Down
3 changes: 2 additions & 1 deletion release_bot/pypi.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,10 @@ def __init__(self, configuration, git):
self.logger = configuration.logger
self.git = git


def latest_version(self):
"""Get latest version of the package from PyPi or 0.0.0"""
response = requests.get(url=f"{self.PYPI_URL}{self.conf.repository_name}/json")
response = requests.get(url=f"{self.PYPI_URL}{self.conf.pypi_project}/json")
if response.status_code == 200:
return response.json()['info']['version']
elif response.status_code == 404:
Expand Down
41 changes: 31 additions & 10 deletions release_bot/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
import re
import subprocess
import locale
import configparser
from semantic_version import validate

from release_bot.configuration import configuration
from release_bot.exceptions import ReleaseException


Expand Down Expand Up @@ -148,9 +148,9 @@ def run_command(work_directory, cmd, error_message, fail=True):
cwd=work_directory,
universal_newlines=True)

configuration.logger.debug(f"{shell.args}\n{shell.stdout}")
logger.debug(f"{shell.args}\n{shell.stdout}")
if shell.returncode != 0:
configuration.logger.error(f"{error_message}\n{shell.stderr}")
logger.error(f"{error_message}\n{shell.stderr}")
if fail:
raise ReleaseException(f"{shell.args!r} failed with {error_message!r}")
return False
Expand Down Expand Up @@ -194,7 +194,7 @@ def insert_in_changelog(changelog, version, log):
file.write(content + original)
return True
except FileNotFoundError as exc:
configuration.logger.warning(f"No CHANGELOG.md present in repository\n{exc}")
logger.warning(f"No CHANGELOG.md present in repository\n{exc}")
return False


Expand All @@ -219,9 +219,9 @@ def look_for_version_files(repo_directory, new_version):
if success:
changed.append(filename.replace(repo_directory + '/', '', 1))
if len(changed) > 1:
configuration.logger.error('Multiple version files found. Aborting version update.')
logger.error('Multiple version files found. Aborting version update.')
elif not changed:
configuration.logger.error('No version files found. Aborting version update.')
logger.error('No version files found. Aborting version update.')

return changed

Expand All @@ -243,18 +243,39 @@ def update_version(file, new_version, prefix):
if line.startswith(prefix):
pieces = line.split('=', maxsplit=1)
if len(pieces) == 2:
configuration.logger.info(f"Editing line with new version:\n{line}")
logger.info(f"Editing line with new version:\n{line}")
old_version = (pieces[1].strip())[1:-1] # strip whitespace and ' or "
if validate(old_version):
configuration.logger.info(f"Replacing version {old_version} with {new_version}")
logger.info(f"Replacing version {old_version} with {new_version}")
content[index] = f"{pieces[0].strip()} = '{new_version}'"
changed = True if content != content_original else False
break
else:
configuration.logger.warning(f"Failed to validate version, aborting")
logger.warning(f"Failed to validate version, aborting")
return False
if changed:
with open(file, 'w') as output:
output.write('\n'.join(content) + '\n')
configuration.logger.info('Version replaced.')
logger.info('Version replaced.')
return changed


def get_pypi_project_from_setup_cfg(path=None):
"""
Get the name of PyPI project from the metadata section of setup.cfg
:param path: str, path to setup.cfg
:return str or None, PyPI project name
"""
path = path or "setup.cfg"

pypi_config = configparser.ConfigParser()
pypi_config.read(path)

if len(pypi_config) > 0:
try:
metadata = pypi_config["metadata"]
return metadata.get("name", None)
except KeyError:
return None

return None
10 changes: 10 additions & 0 deletions tests/src/different-pypi-name.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
changelog:
- Example changelog entry
- Another changelog entry
author_name: John Smith
author_email: [email protected]
labels:
- bot
- release-bot
- user-cont
pypi_project: release-botos
2 changes: 2 additions & 0 deletions tests/src/test-setup.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
[metadata]
name = release-botos
9 changes: 7 additions & 2 deletions tests/test_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
""" Tests parts of bot workflow"""
import os
import warnings
from pathlib import Path
import pytest
import yaml
Expand Down Expand Up @@ -55,7 +56,11 @@ def teardown_method(self):
call.
"""
if self.g_utils.repo:
self.g_utils.delete_repo()
try:
self.g_utils.delete_repo()
except Exception as ex:
# no need to fail the test, just warn
warnings.warn(f"Could not delete repository {self.g_utils.repo}: {ex!r}")

@pytest.fixture()
def open_issue(self):
Expand Down Expand Up @@ -113,7 +118,7 @@ def test_load_release_conf(self):
if conf.get('pypi') is None:
conf['pypi'] = True
for key, value in conf.items():
assert getattr(self.release_bot.new_release, key) == value
assert getattr(self.release_bot.new_release, key) == value

def test_find_open_rls_issue(self, open_issue):
"""Tests if bot can find opened release issue"""
Expand Down
8 changes: 7 additions & 1 deletion tests/test_github.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Tests bot communication with Github"""
import os
import warnings

import pytest

from release_bot.git import Git
Expand Down Expand Up @@ -52,7 +54,11 @@ def teardown_method(self):
call.
"""
if self.g_utils.repo:
self.g_utils.delete_repo()
try:
self.g_utils.delete_repo()
except Exception as ex:
# no need to fail the test, just warn
warnings.warn(f"Could not delete repository {self.g_utils.repo}: {ex!r}")
self.g_utils.repo = None

@pytest.fixture()
Expand Down
15 changes: 14 additions & 1 deletion tests/test_load_release_conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from pathlib import Path
import pytest

from release_bot.configuration import configuration
from release_bot.configuration import configuration, Configuration


class TestLoadReleaseConf:
Expand Down Expand Up @@ -87,6 +87,14 @@ def valid_conf(self):
"""
return (Path(__file__).parent / "src/release-conf.yaml").read_text()

@pytest.fixture
def different_pypi_name_conf(self):
"""
Emulates configuration with different pypi project name
:return:
"""
return (Path(__file__).parent / "src/different-pypi-name.yaml").read_text()

def test_empty_conf(self, empty_conf):
# if there are any required items, this test must fail
if configuration.REQUIRED_ITEMS['release-conf']:
Expand Down Expand Up @@ -132,3 +140,8 @@ def test_normal_use_case(self, valid_conf, valid_new_release):
assert valid_new_release['author_name'] == 'John Smith'
assert valid_new_release['author_email'] == '[email protected]'
assert valid_new_release['labels'] == ['bot', 'release-bot', 'user-cont']

def test_different_pypi_name(self, different_pypi_name_conf):
c = Configuration()
c.load_release_conf(different_pypi_name_conf)
assert c.pypi_project == "release-botos"
20 changes: 14 additions & 6 deletions tests/test_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,13 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
"""Tests utility functions"""
from os import chdir
from pathlib import Path

from semantic_version import Version
from release_bot.utils import (process_version_from_title,
look_for_version_files)
from release_bot.utils import get_pypi_project_from_setup_cfg


def test_process_version_from_title():
Expand Down Expand Up @@ -45,20 +48,25 @@ def test_process_version_from_title():
assert match is False


def test_look_for_version_files(tmp_path):
def test_look_for_version_files(tmpdir):
"""Test finding the correct files with all possible version variables"""
dir1 = tmp_path / "subdir"
dir1 = tmpdir / "subdir"
dir1.mkdir()

file1 = dir1 / "__init__.py"
file1.write_text('__version__="1.2.3"')
file1.write_text('__version__="1.2.3"', "utf-8")

assert look_for_version_files(str(dir1), "1.2.4") == ["__init__.py"]

file2 = dir1 / "setup.py"
file2.write_text('version="1.2.3"')
file2.write_text('version="1.2.3"', "utf-8")

assert look_for_version_files(str(dir1), "1.2.4") == ["setup.py"]

assert set(look_for_version_files(str(dir1), "1.2.5")) == {"setup.py",
"__init__.py"}
assert set(look_for_version_files(str(dir1), "1.2.5")) == \
{"setup.py", "__init__.py"}


def test_get_pypi_project_name():
assert get_pypi_project_from_setup_cfg(
Path(__file__).parent / "src/test-setup.cfg") == "release-botos"

0 comments on commit 5157870

Please sign in to comment.