Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add publish cli command & fix build problems #12

Merged
merged 3 commits into from
Nov 9, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ repos:
rev: v0.931
hooks:
- id: mypy
additional_dependencies:
- types-requests==2.28.11.2
- repo: https://github.com/PyCQA/flake8
rev: 3.9.1
hooks:
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),

## Unreleased

- Feat: Add publish command to cli
- Fix: Support build without actually importing the code
- Fix: Preserve case for key names in metadata file

## [0.4.0] - 2022-11-01

- Feat: Add an option to get version from distribution metadata
Expand Down
6 changes: 6 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,12 @@ Run `qgis-plugin-dev-tools build` (short `qpdt b`) to package the plugin and any

By default config is read from `pyproject.toml`, changelog notes from `CHANGELOG.md`, version from changelog, and package is created in a `dist` directory in the current working directory. Changelog contents and version number are inserted to the `metadata.txt` file, so the version and changelog sections do not need manual updates.

## Plugin publishing

Run `qgis-plugin-dev-tools publish <file>` (short `qpdt publish <file>`) to publish a previously built plugin zip file to QGIS plugin repository.

By default username and password are read from `QPDT_PUBLISH_USERNAME` and `QPDT_PUBLISH_PASSWORD` environment variables.

## Plugin development mode

Run `qgis-plugin-dev-tools start` (short `qpdt s`) to launch QGIS with the plugin installed and ready for development.
Expand Down
3 changes: 3 additions & 0 deletions requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,8 @@ flake8-pytest-style==1.5.0
flake8-pie==0.14.0
flake8-no-pep420==1.1.1

# requests types
types-requests==2.28.11.2

# repo itself
-e file:.
14 changes: 14 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,12 @@ attrs==21.4.0
# pytest
black==22.3.0
# via -r requirements.in
certifi==2022.9.24
# via requests
cfgv==3.3.1
# via pre-commit
charset-normalizer==2.1.1
# via requests
click==8.1.3
# via black
colorama==0.4.4
Expand Down Expand Up @@ -74,6 +78,8 @@ flake8-tidy-imports==4.4.1
# via -r requirements.in
identify==2.5.0
# via pre-commit
idna==3.4
# via requests
importlib-metadata==4.11.3
# via qgis-plugin-dev-tools
iniconfig==1.1.1
Expand Down Expand Up @@ -129,6 +135,8 @@ python-dotenv==0.19.2
# via qgis-plugin-dev-tools
pyyaml==6.0
# via pre-commit
requests==2.28.1
# via qgis-plugin-dev-tools
six==1.16.0
# via
# flake8-print
Expand All @@ -143,11 +151,17 @@ tomli==2.0.1
# coverage
# mypy
# qgis-plugin-dev-tools
types-requests==2.28.11.2
# via -r requirements.in
types-urllib3==1.26.25.1
# via types-requests
typing-extensions==4.2.0
# via
# black
# flake8-pie
# mypy
urllib3==1.26.12
# via requests
virtualenv==20.14.1
# via pre-commit
zipp==3.8.0
Expand Down
1 change: 1 addition & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ install_requires =
tomli>=2.0.0
python-dotenv>=0.19.0
importlib-metadata>=4.9.0
requests>=2.27.0

[options.packages.find]
where = src
Expand Down
7 changes: 6 additions & 1 deletion src/qgis_plugin_dev_tools/build/metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,15 @@
from pathlib import Path


class PreserveKeyCaseConfigParser(ConfigParser):
def optionxform(self, optionstr: str) -> str:
return optionstr


def update_metadata_file(
metadata_file_path: Path, version: str, changelog_contents: str
) -> None:
parser = ConfigParser()
parser = PreserveKeyCaseConfigParser()
parser.read(metadata_file_path, encoding="utf-8")
parser.set("general", "version", version)
parser.set("general", "changelog", changelog_contents)
Expand Down
22 changes: 22 additions & 0 deletions src/qgis_plugin_dev_tools/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
from qgis_plugin_dev_tools.build import make_plugin_zip
from qgis_plugin_dev_tools.config import DevToolsConfig
from qgis_plugin_dev_tools.config.dotenv import read_dotenv_configs
from qgis_plugin_dev_tools.publish import publish_plugin_zip_file
from qgis_plugin_dev_tools.start import launch_development_qgis
from qgis_plugin_dev_tools.start.config import DevelopmentModeConfig
from qgis_plugin_dev_tools.utils.distributions import (
Expand Down Expand Up @@ -90,6 +91,11 @@ def build(override_plugin_version: Optional[str]) -> None:
)


def publish(plugin_zip_file_path: Path) -> None:
LOGGER.info("publishing plugin zip file %s", plugin_zip_file_path)
publish_plugin_zip_file(plugin_zip_file_path)


parser = argparse.ArgumentParser(description="QGIS plugin dev tools cli")

common_parser = argparse.ArgumentParser(add_help=False)
Expand Down Expand Up @@ -134,6 +140,18 @@ def build(override_plugin_version: Optional[str]) -> None:
" (by default infer build version from source files)",
)

publish_parser = commands.add_parser(
"publish",
help="publish a built plugin zip file to QGIS plugin repository",
parents=[common_parser],
)
publish_parser.add_argument(
metavar="<file>",
dest="file",
type=Path,
help="zip file to publish",
)


def run() -> None:
result = vars(parser.parse_args())
Expand All @@ -152,5 +170,9 @@ def run() -> None:
override_plugin_version = result.get("plugin_version", None)
build(override_plugin_version)

elif result.get("subcommand") in ["publish"]:
plugin_zip_file_path = result["file"]
publish(plugin_zip_file_path)

else:
parser.print_usage()
11 changes: 8 additions & 3 deletions src/qgis_plugin_dev_tools/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

from collections import ChainMap
from enum import Enum, auto
from importlib.util import find_spec
from pathlib import Path
from typing import List

Expand Down Expand Up @@ -58,10 +59,14 @@ def __init__(
auto_add_recursive_runtime_dependencies: bool,
version_number_source: VersionNumberSource = VersionNumberSource.CHANGELOG,
) -> None:
plugin_package_spec = find_spec(plugin_package_name)
if plugin_package_spec is None or plugin_package_spec.origin is None:
raise ValueError(
f"could not find {plugin_package_name=} in the current environment"
)

self.plugin_package_path = Path(plugin_package_spec.origin).parent
self.plugin_package_name = plugin_package_name
self.plugin_package_path = Path(
__import__(self.plugin_package_name).__file__ # type: ignore
).parent
# TODO: check versions are satisfied?
self.runtime_distributions = [
distribution(Requirement(spec).name) for spec in runtime_requires
Expand Down
69 changes: 69 additions & 0 deletions src/qgis_plugin_dev_tools/publish/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# json rpc api spec in https://plugins.qgis.org/plugins/RPC2/

import logging
import os
from base64 import b64encode
from pathlib import Path
from typing import Dict, Tuple, cast
from uuid import uuid4

import requests

LOGGER = logging.getLogger(__name__)


def publish_plugin_zip_file(plugin_zip_file_path: Path) -> None:
username = os.environ.get("QPDT_PUBLISH_USERNAME")
password = os.environ.get("QPDT_PUBLISH_PASSWORD")

if not username or not password:
raise ValueError(
"credentials in QPDT_PUBLISH_* variables are not configured properly"
)

if not plugin_zip_file_path.exists():
raise FileNotFoundError(
f"could not find plugin zip file in {plugin_zip_file_path.resolve()}"
)

zip_binary_contents = plugin_zip_file_path.read_bytes()
request_identifier = f"qgis-plugin-dev-tools-{uuid4()}"
body = {
"jsonrpc": "2.0",
"method": "plugin.upload",
"params": [b64encode(zip_binary_contents).decode("utf-8")],
"id": request_identifier,
}

LOGGER.debug(
"sending POST request to plugin RPC api with body %s",
(body | {"params": ["<base64 zip contents>"]}),
)

response = requests.post(
url="https://plugins.qgis.org/plugins/RPC2/",
json=body,
auth=(username, password),
)

LOGGER.debug(
"got response from plugin RPC api with body %s",
response.text,
)

if response.status_code != 200:
raise Exception(
"QGIS plugin repository plugin upload "
f"HTTP request failed with status {response.status_code}"
)
if "error" in response.json():
raise Exception(
"QGIS plugin repository plugin upload "
f"request returned error response {response.json().get('error')}"
)

plugin_id, version_id = cast(Dict[str, Tuple[int, int]], response.json()).get(
"result", (None, None)
)

LOGGER.info("uploaded plugin id %s & version id %s", plugin_id, version_id)
2 changes: 2 additions & 0 deletions whitelist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,5 @@ dest
metavar
util
pyd
base64
optionstr