launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #28873
[Merge] ~cjwatson/launchpad:black-snappy into launchpad:master
Colin Watson has proposed merging ~cjwatson/launchpad:black-snappy into launchpad:master.
Commit message:
lp.snappy: Apply black
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/427276
--
The attached diff has been truncated due to its size.
Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:black-snappy into launchpad:master.
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index 19c4d14..a4e636a 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -84,3 +84,5 @@ ed7d7b97b8fb4ebe92799f922b0fa9c4bd1714e8
66301b8ad409deeb9092dfe62c8e4ef6f3093302
# apply black to lp.services
4719b7aa672a2674c7fdbbde58772871b77c3301
+# apply black to lp.snappy
+cf7c6a08bd010dd260bff4690d64479fadf37e67
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d56d052..a665559 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -56,6 +56,7 @@ repos:
|registry
|scripts
|services
+ |snappy
)/
- repo: https://github.com/PyCQA/isort
rev: 5.9.2
@@ -88,6 +89,7 @@ repos:
|registry
|scripts
|services
+ |snappy
)/
- id: isort
alias: isort-black
@@ -110,6 +112,7 @@ repos:
|registry
|scripts
|services
+ |snappy
)/
- repo: https://github.com/PyCQA/flake8
rev: 3.9.2
diff --git a/lib/lp/snappy/adapters/buildarch.py b/lib/lp/snappy/adapters/buildarch.py
index 3e1ea75..e4e92f7 100644
--- a/lib/lp/snappy/adapters/buildarch.py
+++ b/lib/lp/snappy/adapters/buildarch.py
@@ -2,17 +2,11 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'determine_architectures_to_build',
- ]
+ "determine_architectures_to_build",
+]
from collections import Counter
-from typing import (
- Any,
- Dict,
- List,
- Optional,
- Union,
- )
+from typing import Any, Dict, List, Optional, Union
from lp.services.helpers import english_list
@@ -27,7 +21,9 @@ class MissingPropertyError(SnapArchitecturesParserError):
def __init__(self, prop):
super().__init__(
"Architecture specification is missing the {!r} property".format(
- prop))
+ prop
+ )
+ )
self.property = prop
@@ -37,7 +33,8 @@ class IncompatibleArchitecturesStyleError(SnapArchitecturesParserError):
def __init__(self):
super().__init__(
"'architectures' must either be a list of strings or dicts, not "
- "both")
+ "both"
+ )
class DuplicateBuildOnError(SnapArchitecturesParserError):
@@ -47,7 +44,9 @@ class DuplicateBuildOnError(SnapArchitecturesParserError):
super().__init__(
"{} {} present in the 'build-on' of multiple items".format(
english_list(duplicates),
- "is" if len(duplicates) == 1 else "are"))
+ "is" if len(duplicates) == 1 else "are",
+ )
+ )
class UnsupportedBuildOnError(SnapArchitecturesParserError):
@@ -56,7 +55,9 @@ class UnsupportedBuildOnError(SnapArchitecturesParserError):
def __init__(self, build_on):
super().__init__(
"build-on specifies no supported architectures: {!r}".format(
- build_on))
+ build_on
+ )
+ )
self.build_on = build_on
@@ -67,7 +68,7 @@ class SnapArchitecture:
self,
build_on: Union[str, List[str]],
build_for: Optional[Union[str, List[str]]] = None,
- build_error: Optional[str] = None
+ build_error: Optional[str] = None,
):
"""Create a new architecture entry.
@@ -133,8 +134,10 @@ class SnapBuildInstance:
"""
try:
self.architecture = next(
- arch for arch in supported_architectures
- if arch in architecture.build_on)
+ arch
+ for arch in supported_architectures
+ if arch in architecture.build_on
+ )
except StopIteration:
raise UnsupportedBuildOnError(architecture.build_on)
@@ -153,8 +156,8 @@ def determine_architectures_to_build(
we can create builds for.
:return: a list of `SnapBuildInstance`s.
"""
- architectures_list = (
- snapcraft_data.get("architectures")
+ architectures_list = snapcraft_data.get(
+ "architectures"
) # type: Optional[List]
if architectures_list:
@@ -167,7 +170,8 @@ def determine_architectures_to_build(
elif all(isinstance(arch, dict) for arch in architectures_list):
# If a list of dicts (new style), then that's multiple items.
architectures = [
- SnapArchitecture.from_dict(a) for a in architectures_list]
+ SnapArchitecture.from_dict(a) for a in architectures_list
+ ]
else:
# If a mix of both, bail. We can't reasonably handle it.
raise IncompatibleArchitecturesStyleError()
@@ -175,7 +179,8 @@ def determine_architectures_to_build(
# If no architectures are specified, build one for each supported
# architecture.
architectures = [
- SnapArchitecture(build_on=a) for a in supported_arches]
+ SnapArchitecture(build_on=a) for a in supported_arches
+ ]
# Ensure that multiple `build-on` items don't include the same
# architecture; this is ambiguous and forbidden by snapcraft. Checking
@@ -192,7 +197,8 @@ def determine_architectures_to_build(
for arch in architectures:
try:
architectures_to_build.append(
- SnapBuildInstance(arch, supported_arches))
+ SnapBuildInstance(arch, supported_arches)
+ )
except UnsupportedBuildOnError:
# Snaps are allowed to declare that they build on architectures
# that Launchpad doesn't currently support (perhaps they're
diff --git a/lib/lp/snappy/adapters/tests/test_buildarch.py b/lib/lp/snappy/adapters/tests/test_buildarch.py
index 2f1610f..81f3c87 100644
--- a/lib/lp/snappy/adapters/tests/test_buildarch.py
+++ b/lib/lp/snappy/adapters/tests/test_buildarch.py
@@ -1,64 +1,83 @@
# Copyright 2018 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-from testscenarios import (
- load_tests_apply_scenarios,
- WithScenarios,
- )
+from testscenarios import WithScenarios, load_tests_apply_scenarios
from testtools.matchers import HasLength
from lp.snappy.adapters.buildarch import (
- determine_architectures_to_build,
SnapArchitecture,
SnapBuildInstance,
UnsupportedBuildOnError,
- )
+ determine_architectures_to_build,
+)
from lp.testing import TestCase
class TestSnapArchitecture(WithScenarios, TestCase):
scenarios = [
- ("lists", {
- "architectures": {"build-on": ["amd64"], "build-for": ["amd64"]},
- "expected_build_on": ["amd64"],
- "expected_build_for": ["amd64"],
- "expected_build_error": None,
- }),
- ("strings", {
- "architectures": {"build-on": "amd64", "build-for": "amd64"},
- "expected_build_on": ["amd64"],
- "expected_build_for": ["amd64"],
- "expected_build_error": None,
- }),
- ("no build-for", {
- "architectures": {"build-on": ["amd64"]},
- "expected_build_on": ["amd64"],
- "expected_build_for": ["amd64"],
- "expected_build_error": None,
- }),
- ("not required", {
- "architectures": {
- "build-on": ["amd64"],
- "build-for": "amd64",
- "build-error": "ignore"},
- "expected_build_on": ["amd64"],
- "expected_build_for": ["amd64"],
- "expected_build_error": "ignore",
- }),
- ("build-for", {
- "architectures": {"build-on": ["amd64"], "build-for": "all"},
- "expected_build_on": ["amd64"],
- "expected_build_for": ["all"],
- "expected_build_error": None,
- }),
- ("run-on", {
- "architectures": {"build-on": ["amd64"], "run-on": "all"},
- "expected_build_on": ["amd64"],
- "expected_build_for": ["all"],
- "expected_build_error": None,
- }),
- ]
+ (
+ "lists",
+ {
+ "architectures": {
+ "build-on": ["amd64"],
+ "build-for": ["amd64"],
+ },
+ "expected_build_on": ["amd64"],
+ "expected_build_for": ["amd64"],
+ "expected_build_error": None,
+ },
+ ),
+ (
+ "strings",
+ {
+ "architectures": {"build-on": "amd64", "build-for": "amd64"},
+ "expected_build_on": ["amd64"],
+ "expected_build_for": ["amd64"],
+ "expected_build_error": None,
+ },
+ ),
+ (
+ "no build-for",
+ {
+ "architectures": {"build-on": ["amd64"]},
+ "expected_build_on": ["amd64"],
+ "expected_build_for": ["amd64"],
+ "expected_build_error": None,
+ },
+ ),
+ (
+ "not required",
+ {
+ "architectures": {
+ "build-on": ["amd64"],
+ "build-for": "amd64",
+ "build-error": "ignore",
+ },
+ "expected_build_on": ["amd64"],
+ "expected_build_for": ["amd64"],
+ "expected_build_error": "ignore",
+ },
+ ),
+ (
+ "build-for",
+ {
+ "architectures": {"build-on": ["amd64"], "build-for": "all"},
+ "expected_build_on": ["amd64"],
+ "expected_build_for": ["all"],
+ "expected_build_error": None,
+ },
+ ),
+ (
+ "run-on",
+ {
+ "architectures": {"build-on": ["amd64"], "run-on": "all"},
+ "expected_build_on": ["amd64"],
+ "expected_build_for": ["all"],
+ "expected_build_error": None,
+ },
+ ),
+ ]
def test_architecture(self):
architecture = SnapArchitecture.from_dict(self.architectures)
@@ -72,64 +91,85 @@ class TestSnapBuildInstance(WithScenarios, TestCase):
# Single-item scenarios taken from the architectures document:
# https://forum.snapcraft.io/t/architectures/4972
scenarios = [
- ("i386", {
- "architecture": SnapArchitecture(
- build_on="i386", build_for=["amd64", "i386"]),
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected_architecture": "i386",
- "expected_target_architectures": ["amd64", "i386"],
- "expected_required": True,
- }),
- ("amd64", {
- "architecture": SnapArchitecture(
- build_on="amd64", build_for="all"
- ),
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected_architecture": "amd64",
- "expected_target_architectures": ["all"],
- "expected_required": True,
- }),
- ("amd64 priority", {
- "architecture": SnapArchitecture(
- build_on=["amd64", "i386"], build_for="all"),
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected_architecture": "amd64",
- "expected_target_architectures": ["all"],
- "expected_required": True,
- }),
- ("i386 priority", {
- "architecture": SnapArchitecture(
- build_on=["amd64", "i386"], build_for="all"),
- "supported_architectures": ["i386", "amd64", "armhf"],
- "expected_architecture": "i386",
- "expected_target_architectures": ["all"],
- "expected_required": True,
- }),
- ("optional", {
- "architecture": SnapArchitecture(
- build_on="amd64", build_error="ignore"),
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected_architecture": "amd64",
- "expected_target_architectures": ["amd64"],
- "expected_required": False,
- }),
- ]
+ (
+ "i386",
+ {
+ "architecture": SnapArchitecture(
+ build_on="i386", build_for=["amd64", "i386"]
+ ),
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected_architecture": "i386",
+ "expected_target_architectures": ["amd64", "i386"],
+ "expected_required": True,
+ },
+ ),
+ (
+ "amd64",
+ {
+ "architecture": SnapArchitecture(
+ build_on="amd64", build_for="all"
+ ),
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected_architecture": "amd64",
+ "expected_target_architectures": ["all"],
+ "expected_required": True,
+ },
+ ),
+ (
+ "amd64 priority",
+ {
+ "architecture": SnapArchitecture(
+ build_on=["amd64", "i386"], build_for="all"
+ ),
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected_architecture": "amd64",
+ "expected_target_architectures": ["all"],
+ "expected_required": True,
+ },
+ ),
+ (
+ "i386 priority",
+ {
+ "architecture": SnapArchitecture(
+ build_on=["amd64", "i386"], build_for="all"
+ ),
+ "supported_architectures": ["i386", "amd64", "armhf"],
+ "expected_architecture": "i386",
+ "expected_target_architectures": ["all"],
+ "expected_required": True,
+ },
+ ),
+ (
+ "optional",
+ {
+ "architecture": SnapArchitecture(
+ build_on="amd64", build_error="ignore"
+ ),
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected_architecture": "amd64",
+ "expected_target_architectures": ["amd64"],
+ "expected_required": False,
+ },
+ ),
+ ]
def test_build_instance(self):
instance = SnapBuildInstance(
- self.architecture, self.supported_architectures)
+ self.architecture, self.supported_architectures
+ )
self.assertEqual(self.expected_architecture, instance.architecture)
- self.assertEqual(self.expected_target_architectures,
- instance.target_architectures)
+ self.assertEqual(
+ self.expected_target_architectures, instance.target_architectures
+ )
self.assertEqual(self.expected_required, instance.required)
class TestSnapBuildInstanceError(TestCase):
-
def test_no_matching_arch_raises(self):
architecture = SnapArchitecture(build_on="amd64", build_for="amd64")
raised = self.assertRaises(
- UnsupportedBuildOnError, SnapBuildInstance, architecture, ["i386"])
+ UnsupportedBuildOnError, SnapBuildInstance, architecture, ["i386"]
+ )
self.assertEqual(["amd64"], raised.build_on)
@@ -139,192 +179,226 @@ class TestDetermineArchitecturesToBuild(WithScenarios, TestCase):
# https://forum.snapcraft.io/t/architectures/4972
scenarios = [
- ("none", {
- "architectures": None,
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["amd64"],
- "required": True,
- },
- {
- "architecture": "i386",
- "target_architectures": ["i386"],
- "required": True
- },
- {
- "architecture": "armhf",
- "target_architectures": ["armhf"],
- "required": True
- },
- ],
- }),
- ("i386", {
- "architectures": [
- {"build-on": "i386", "build-for": ["amd64", "i386"]},
- ],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "i386",
- "target_architectures": ["amd64", "i386"],
- "required": True,
- }
- ],
- }),
- ("amd64", {
- "architectures": [{"build-on": "amd64", "build-for": "all"}],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["all"],
- "required": True
- }
- ],
- }),
- ("amd64 and i386", {
- "architectures": [
- {"build-on": "amd64", "build-for": "amd64"},
- {"build-on": "i386", "build-for": "i386"},
- ],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["amd64"],
- "required": True,
- },
- {
- "architecture": "i386",
- "target_architectures": ["i386"],
- "required": True,
- },
- ],
- }),
- ("amd64 and i386 shorthand", {
- "architectures": [
- {"build-on": "amd64"},
- {"build-on": "i386"},
- ],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["amd64"],
- "required": True,
- },
- {
- "architecture": "i386",
- "target_architectures": ["i386"],
- "required": True,
- },
- ],
- }),
- ("amd64, i386, and armhf", {
- "architectures": [
- {"build-on": "amd64", "build-for": "amd64"},
- {"build-on": "i386", "build-for": "i386"},
- {
- "build-on": "armhf",
- "build-for": "armhf",
- "build-error": "ignore",
- },
- ],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["amd64"],
- "required": True,
- },
- {
- "architecture": "i386",
- "target_architectures": ["i386"],
- "required": True,
- },
- {
- "architecture": "armhf",
- "target_architectures": ["armhf"],
- "required": False,
- },
- ],
- }),
- ("amd64 priority", {
- "architectures": [
- {"build-on": ["amd64", "i386"], "build-for": "all"},
- ],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["all"],
- "required": True,
- }
- ],
- }),
- ("i386 priority", {
- "architectures": [
- {"build-on": ["amd64", "i386"], "build-for": "all"},
- ],
- "supported_architectures": ["i386", "amd64", "armhf"],
- "expected": [
- {
- "architecture": "i386",
- "target_architectures": ["all"],
- "required": True,
- }
- ],
- }),
- ("old style i386 priority", {
- "architectures": ["amd64", "i386"],
- "supported_architectures": ["i386", "amd64", "armhf"],
- "expected": [
- {
- "architecture": "i386",
- "target_architectures": ["amd64", "i386"],
- "required": True
- }
- ],
- }),
- ("old style amd64 priority", {
- "architectures": ["amd64", "i386"],
- "supported_architectures": ["amd64", "i386", "armhf"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["amd64", "i386"],
- "required": True,
- }
- ],
- }),
- ("more architectures listed than are supported", {
- "architectures": [
- {"build-on": "amd64"},
- {"build-on": "i386"},
- {"build-on": "armhf"},
- ],
- "supported_architectures": ["amd64", "i386"],
- "expected": [
- {
- "architecture": "amd64",
- "target_architectures": ["amd64"],
- "required": True,
- },
- {
- "architecture": "i386",
- "target_architectures": ["i386"],
- "required": True,
- },
- ],
- })
+ (
+ "none",
+ {
+ "architectures": None,
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["amd64"],
+ "required": True,
+ },
+ {
+ "architecture": "i386",
+ "target_architectures": ["i386"],
+ "required": True,
+ },
+ {
+ "architecture": "armhf",
+ "target_architectures": ["armhf"],
+ "required": True,
+ },
+ ],
+ },
+ ),
+ (
+ "i386",
+ {
+ "architectures": [
+ {"build-on": "i386", "build-for": ["amd64", "i386"]},
+ ],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "i386",
+ "target_architectures": ["amd64", "i386"],
+ "required": True,
+ }
+ ],
+ },
+ ),
+ (
+ "amd64",
+ {
+ "architectures": [{"build-on": "amd64", "build-for": "all"}],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["all"],
+ "required": True,
+ }
+ ],
+ },
+ ),
+ (
+ "amd64 and i386",
+ {
+ "architectures": [
+ {"build-on": "amd64", "build-for": "amd64"},
+ {"build-on": "i386", "build-for": "i386"},
+ ],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["amd64"],
+ "required": True,
+ },
+ {
+ "architecture": "i386",
+ "target_architectures": ["i386"],
+ "required": True,
+ },
+ ],
+ },
+ ),
+ (
+ "amd64 and i386 shorthand",
+ {
+ "architectures": [
+ {"build-on": "amd64"},
+ {"build-on": "i386"},
+ ],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["amd64"],
+ "required": True,
+ },
+ {
+ "architecture": "i386",
+ "target_architectures": ["i386"],
+ "required": True,
+ },
+ ],
+ },
+ ),
+ (
+ "amd64, i386, and armhf",
+ {
+ "architectures": [
+ {"build-on": "amd64", "build-for": "amd64"},
+ {"build-on": "i386", "build-for": "i386"},
+ {
+ "build-on": "armhf",
+ "build-for": "armhf",
+ "build-error": "ignore",
+ },
+ ],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["amd64"],
+ "required": True,
+ },
+ {
+ "architecture": "i386",
+ "target_architectures": ["i386"],
+ "required": True,
+ },
+ {
+ "architecture": "armhf",
+ "target_architectures": ["armhf"],
+ "required": False,
+ },
+ ],
+ },
+ ),
+ (
+ "amd64 priority",
+ {
+ "architectures": [
+ {"build-on": ["amd64", "i386"], "build-for": "all"},
+ ],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["all"],
+ "required": True,
+ }
+ ],
+ },
+ ),
+ (
+ "i386 priority",
+ {
+ "architectures": [
+ {"build-on": ["amd64", "i386"], "build-for": "all"},
+ ],
+ "supported_architectures": ["i386", "amd64", "armhf"],
+ "expected": [
+ {
+ "architecture": "i386",
+ "target_architectures": ["all"],
+ "required": True,
+ }
+ ],
+ },
+ ),
+ (
+ "old style i386 priority",
+ {
+ "architectures": ["amd64", "i386"],
+ "supported_architectures": ["i386", "amd64", "armhf"],
+ "expected": [
+ {
+ "architecture": "i386",
+ "target_architectures": ["amd64", "i386"],
+ "required": True,
+ }
+ ],
+ },
+ ),
+ (
+ "old style amd64 priority",
+ {
+ "architectures": ["amd64", "i386"],
+ "supported_architectures": ["amd64", "i386", "armhf"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["amd64", "i386"],
+ "required": True,
+ }
+ ],
+ },
+ ),
+ (
+ "more architectures listed than are supported",
+ {
+ "architectures": [
+ {"build-on": "amd64"},
+ {"build-on": "i386"},
+ {"build-on": "armhf"},
+ ],
+ "supported_architectures": ["amd64", "i386"],
+ "expected": [
+ {
+ "architecture": "amd64",
+ "target_architectures": ["amd64"],
+ "required": True,
+ },
+ {
+ "architecture": "i386",
+ "target_architectures": ["i386"],
+ "required": True,
+ },
+ ],
+ },
+ ),
]
def test_parser(self):
snapcraft_data = {"architectures": self.architectures}
build_instances = determine_architectures_to_build(
- snapcraft_data, self.supported_architectures)
+ snapcraft_data, self.supported_architectures
+ )
self.assertThat(build_instances, HasLength(len(self.expected)))
for instance in build_instances:
self.assertIn(instance.__dict__, self.expected)
diff --git a/lib/lp/snappy/browser/hassnaps.py b/lib/lp/snappy/browser/hassnaps.py
index f266a1f..7a2ee77 100644
--- a/lib/lp/snappy/browser/hassnaps.py
+++ b/lib/lp/snappy/browser/hassnaps.py
@@ -4,47 +4,44 @@
"""Mixins for browser classes for objects that implement IHasSnaps."""
__all__ = [
- 'HasSnapsMenuMixin',
- 'HasSnapsViewMixin',
- ]
+ "HasSnapsMenuMixin",
+ "HasSnapsViewMixin",
+]
from zope.component import getUtility
from lp.code.browser.decorations import DecoratedBranch
from lp.code.interfaces.gitrepository import IGitRepository
from lp.services.features import getFeatureFlag
-from lp.services.webapp import (
- canonical_url,
- Link,
- )
+from lp.services.webapp import Link, canonical_url
from lp.services.webapp.escaping import structured
-from lp.snappy.interfaces.snap import (
- ISnapSet,
- SNAP_PRIVATE_FEATURE_FLAG,
- )
+from lp.snappy.interfaces.snap import SNAP_PRIVATE_FEATURE_FLAG, ISnapSet
class HasSnapsMenuMixin:
"""A mixin for context menus for objects that implement IHasSnaps."""
def view_snaps(self):
- text = 'View snap packages'
+ text = "View snap packages"
context = self.context
if isinstance(context, DecoratedBranch):
context = context.branch
- enabled = not getUtility(ISnapSet).findByContext(
- context, visible_by_user=self.user).is_empty()
- return Link('+snaps', text, icon='info', enabled=enabled)
+ enabled = (
+ not getUtility(ISnapSet)
+ .findByContext(context, visible_by_user=self.user)
+ .is_empty()
+ )
+ return Link("+snaps", text, icon="info", enabled=enabled)
def create_snap(self):
# Only enabled if the snap_private flag is enabled for
# private contexts.
- enabled = (
- not self.context.private or
- bool(getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG)))
+ enabled = not self.context.private or bool(
+ getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG)
+ )
- text = 'Create snap package'
- return Link('+new-snap', text, enabled=enabled, icon='add')
+ text = "Create snap package"
+ return Link("+new-snap", text, enabled=enabled, icon="add")
class HasSnapsViewMixin:
@@ -56,26 +53,31 @@ class HasSnapsViewMixin:
if isinstance(context, DecoratedBranch):
context = context.branch
return getUtility(ISnapSet).findByContext(
- context, visible_by_user=self.user)
+ context, visible_by_user=self.user
+ )
@property
def snaps_link(self):
"""A link to snap packages for this object."""
count = self.snaps.count()
if IGitRepository.providedBy(self.context):
- context_type = 'repository'
+ context_type = "repository"
else:
- context_type = 'branch'
+ context_type = "branch"
if count == 0:
# Nothing to link to.
- return 'No snap packages using this %s.' % context_type
+ return "No snap packages using this %s." % context_type
elif count == 1:
# Link to the single snap package.
return structured(
'<a href="%s">1 snap package</a> using this %s.',
- canonical_url(self.snaps.one()), context_type).escapedtext
+ canonical_url(self.snaps.one()),
+ context_type,
+ ).escapedtext
else:
# Link to a snap package listing.
return structured(
'<a href="+snaps">%s snap packages</a> using this %s.',
- count, context_type).escapedtext
+ count,
+ context_type,
+ ).escapedtext
diff --git a/lib/lp/snappy/browser/snap.py b/lib/lp/snappy/browser/snap.py
index eccbcd8..13fbe82 100644
--- a/lib/lp/snappy/browser/snap.py
+++ b/lib/lp/snappy/browser/snap.py
@@ -4,46 +4,35 @@
"""Snap views."""
__all__ = [
- 'SnapAddView',
- 'SnapAuthorizeView',
- 'SnapContextMenu',
- 'SnapDeleteView',
- 'SnapEditView',
- 'SnapNavigation',
- 'SnapNavigationMenu',
- 'SnapRequestBuildsView',
- 'SnapView',
- ]
+ "SnapAddView",
+ "SnapAuthorizeView",
+ "SnapContextMenu",
+ "SnapDeleteView",
+ "SnapEditView",
+ "SnapNavigation",
+ "SnapNavigationMenu",
+ "SnapRequestBuildsView",
+ "SnapView",
+]
from urllib.parse import urlencode
from lazr.restful.fields import Reference
-from lazr.restful.interface import (
- copy_field,
- use_template,
- )
+from lazr.restful.interface import copy_field, use_template
from zope.component import getUtility
from zope.error.interfaces import IErrorReportingUtility
from zope.formlib.widget import CustomWidgetFactory
-from zope.interface import (
- implementer,
- Interface,
- )
-from zope.schema import (
- Choice,
- Dict,
- List,
- TextLine,
- )
+from zope.interface import Interface, implementer
+from zope.schema import Choice, Dict, List, TextLine
from zope.security.interfaces import Unauthorized
from lp import _
from lp.app.browser.launchpadform import (
- action,
LaunchpadEditFormView,
LaunchpadFormView,
+ action,
render_radio_widget_part,
- )
+)
from lp.app.browser.lazrjs import InlinePersonEditPickerWidget
from lp.app.browser.tales import format_link
from lp.app.enums import PRIVATE_INFORMATION_TYPES
@@ -55,7 +44,7 @@ from lp.app.widgets.itemswidgets import (
LaunchpadDropdownWidget,
LaunchpadRadioWidget,
LaunchpadRadioWidgetWithDescription,
- )
+)
from lp.buildmaster.interfaces.processor import IProcessorSet
from lp.code.browser.widgets.gitref import GitRefWidget
from lp.code.interfaces.branch import IBranch
@@ -70,29 +59,25 @@ from lp.services.propertycache import cachedproperty
from lp.services.scripts import log
from lp.services.utils import seconds_since_epoch
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
LaunchpadView,
Link,
Navigation,
NavigationMenu,
+ canonical_url,
+ enabled_with_permission,
stepthrough,
structured,
- )
-from lp.services.webapp.breadcrumb import (
- Breadcrumb,
- NameBreadcrumb,
- )
+)
+from lp.services.webapp.breadcrumb import Breadcrumb, NameBreadcrumb
from lp.services.webapp.interfaces import ICanonicalUrlData
from lp.services.webapp.url import urlappend
from lp.services.webhooks.browser import WebhookTargetNavigationMixin
from lp.snappy.browser.widgets.snaparchive import SnapArchiveWidget
-from lp.snappy.browser.widgets.snapbuildchannels import (
- SnapBuildChannelsWidget,
- )
+from lp.snappy.browser.widgets.snapbuildchannels import SnapBuildChannelsWidget
from lp.snappy.browser.widgets.storechannels import StoreChannelsWidget
from lp.snappy.interfaces.snap import (
+ SNAP_PRIVATE_FEATURE_FLAG,
CannotAuthorizeStoreUploads,
CannotFetchSnapcraftYaml,
CannotParseSnapcraftYaml,
@@ -100,20 +85,16 @@ from lp.snappy.interfaces.snap import (
ISnapSet,
MissingSnapcraftYaml,
NoSuchSnap,
- SNAP_PRIVATE_FEATURE_FLAG,
SnapPrivateFeatureDisabled,
- )
-from lp.snappy.interfaces.snapbuild import (
- ISnapBuild,
- ISnapBuildSet,
- )
+)
+from lp.snappy.interfaces.snapbuild import ISnapBuild, ISnapBuildSet
from lp.snappy.interfaces.snappyseries import (
ISnappyDistroSeriesSet,
ISnappySeriesSet,
- )
+)
from lp.snappy.interfaces.snapstoreclient import (
BadRequestPackageUploadResponse,
- )
+)
from lp.soyuz.browser.archive import EnableProcessorsMixin
from lp.soyuz.browser.build import get_build_by_id_str
from lp.soyuz.interfaces.archive import IArchive
@@ -122,7 +103,8 @@ from lp.soyuz.interfaces.archive import IArchive
@implementer(ICanonicalUrlData)
class SnapURL:
"""Snap URL creation rules."""
- rootsite = 'mainsite'
+
+ rootsite = "mainsite"
def __init__(self, snap):
self.snap = snap
@@ -143,7 +125,7 @@ class SnapURL:
class SnapNavigation(WebhookTargetNavigationMixin, Navigation):
usedfor = ISnap
- @stepthrough('+build-request')
+ @stepthrough("+build-request")
def traverse_build_request(self, name):
try:
job_id = int(name)
@@ -151,7 +133,7 @@ class SnapNavigation(WebhookTargetNavigationMixin, Navigation):
return None
return self.context.getBuildRequest(job_id)
- @stepthrough('+build')
+ @stepthrough("+build")
def traverse_build(self, name):
build = get_build_by_id_str(ISnapBuildSet, name)
if build is None or build.snap != self.context:
@@ -170,23 +152,24 @@ class SnapFormMixin:
def validateVCSWidgets(self, cls, data):
"""Validates if VCS sub-widgets."""
# Set widgets as required or optional depending on the vcs field.
- vcs = data.get('vcs')
+ vcs = data.get("vcs")
if vcs == VCSType.BZR:
- self.widgets['branch'].context.required = True
- self.widgets['git_ref'].context.required = False
+ self.widgets["branch"].context.required = True
+ self.widgets["git_ref"].context.required = False
elif vcs == VCSType.GIT:
- self.widgets['branch'].context.required = False
- self.widgets['git_ref'].context.required = True
+ self.widgets["branch"].context.required = False
+ self.widgets["git_ref"].context.required = True
else:
raise AssertionError("Unknown branch type %s" % vcs)
def setUpVCSWidgets(self):
- widget = self.widgets.get('vcs')
+ widget = self.widgets.get("vcs")
if widget is not None:
current_value = widget._getFormValue()
self.vcs_bzr_radio, self.vcs_git_radio = (
render_radio_widget_part(widget, value, current_value)
- for value in (VCSType.BZR, VCSType.GIT))
+ for value in (VCSType.BZR, VCSType.GIT)
+ )
class SnapInformationTypeMixin:
@@ -207,42 +190,50 @@ class SnapInformationTypeMixin:
When creating a new snap, `snap` should be None and the possible
information types will be calculated based on the project.
"""
- info_type = data.get('information_type')
+ info_type = data.get("information_type")
if IProduct.providedBy(self.context):
project = self.context
else:
- project = data.get('project')
+ project = data.get("project")
if info_type is None and project is None:
# Nothing to validate here. Move on.
return
if project is None and info_type in PRIVATE_INFORMATION_TYPES:
self.setFieldError(
- 'information_type',
- 'Private snap recipes must be associated with a project.')
+ "information_type",
+ "Private snap recipes must be associated with a project.",
+ )
elif project is not None:
if snap is None:
snap_set = getUtility(ISnapSet)
possible_types = snap_set.getPossibleSnapInformationTypes(
- project)
+ project
+ )
else:
possible_types = self.getPossibleInformationTypes(
- snap, self.user)
+ snap, self.user
+ )
if info_type not in possible_types:
- msg = ('Project %s only accepts the following information '
- 'types: %s.')
- msg %= (project.name,
- ", ".join(i.title for i in possible_types))
- self.setFieldError('information_type', msg)
+ msg = (
+ "Project %s only accepts the following information "
+ "types: %s."
+ )
+ msg %= (
+ project.name,
+ ", ".join(i.title for i in possible_types),
+ )
+ self.setFieldError("information_type", msg)
class SnapBreadcrumb(NameBreadcrumb):
-
@property
def inside(self):
return Breadcrumb(
self.context.owner,
url=canonical_url(self.context.owner, view_name="+snaps"),
- text="Snap packages", inside=self.context.owner)
+ text="Snap packages",
+ inside=self.context.owner,
+ )
class SnapNavigationMenu(NavigationMenu):
@@ -250,35 +241,38 @@ class SnapNavigationMenu(NavigationMenu):
usedfor = ISnap
- facet = 'overview'
+ facet = "overview"
- links = ('admin', 'edit', 'webhooks', 'authorize', 'delete')
+ links = ("admin", "edit", "webhooks", "authorize", "delete")
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def admin(self):
- return Link('+admin', 'Administer snap package', icon='edit')
+ return Link("+admin", "Administer snap package", icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def edit(self):
- return Link('+edit', 'Edit snap package', icon='edit')
+ return Link("+edit", "Edit snap package", icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def webhooks(self):
return Link(
- '+webhooks', 'Manage webhooks', icon='edit',
- enabled=bool(getFeatureFlag('webhooks.new.enabled')))
+ "+webhooks",
+ "Manage webhooks",
+ icon="edit",
+ enabled=bool(getFeatureFlag("webhooks.new.enabled")),
+ )
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def authorize(self):
if self.context.store_secrets:
- text = 'Reauthorize store uploads'
+ text = "Reauthorize store uploads"
else:
- text = 'Authorize store uploads'
- return Link('+authorize', text, icon='edit')
+ text = "Authorize store uploads"
+ return Link("+authorize", text, icon="edit")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def delete(self):
- return Link('+delete', 'Delete snap package', icon='trash-icon')
+ return Link("+delete", "Delete snap package", icon="trash-icon")
class SnapContextMenu(ContextMenu):
@@ -286,13 +280,13 @@ class SnapContextMenu(ContextMenu):
usedfor = ISnap
- facet = 'overview'
+ facet = "overview"
- links = ('request_builds', 'add_subscriber', 'subscription')
+ links = ("request_builds", "add_subscriber", "subscription")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def request_builds(self):
- return Link('+request-builds', 'Request builds', icon='add')
+ return Link("+request-builds", "Request builds", icon="add")
@enabled_with_permission("launchpad.AnyPerson")
def subscription(self):
@@ -322,18 +316,23 @@ class SnapView(LaunchpadView):
@property
def person_picker(self):
field = copy_field(
- ISnap['owner'],
- vocabularyName='AllUserTeamsParticipationPlusSelfSimpleDisplay')
+ ISnap["owner"],
+ vocabularyName="AllUserTeamsParticipationPlusSelfSimpleDisplay",
+ )
return InlinePersonEditPickerWidget(
- self.context, field, format_link(self.context.owner),
- header='Change owner', step_title='Select a new owner')
+ self.context,
+ field,
+ format_link(self.context.owner),
+ header="Change owner",
+ step_title="Select a new owner",
+ )
@property
def build_frequency(self):
if self.context.auto_build:
- return 'Built automatically'
+ return "Built automatically"
else:
- return 'Built on request'
+ return "Built on request"
@property
def sorted_auto_build_channels_items(self):
@@ -343,7 +342,7 @@ class SnapView(LaunchpadView):
@property
def store_channels(self):
- return ', '.join(self.context.store_channels)
+ return ", ".join(self.context.store_channels)
@property
def user_can_see_source(self):
@@ -380,19 +379,24 @@ def builds_and_requests_for_snap(snap):
items = sorted(
list(snap.pending_builds) + list(snap.pending_build_requests),
- key=make_sort_key("date_created", "date_requested"))
+ key=make_sort_key("date_created", "date_requested"),
+ )
if len(items) < 10:
# We need to interleave two unbounded result sets, but we only need
# enough items from them to make the total count up to 10. It's
# simplest to just fetch the upper bound from each set and do our
# own sorting.
recent_items = sorted(
- list(snap.completed_builds[:10 - len(items)]) +
- list(snap.failed_build_requests[:10 - len(items)]),
+ list(snap.completed_builds[: 10 - len(items)])
+ + list(snap.failed_build_requests[: 10 - len(items)]),
key=make_sort_key(
- "date_finished", "date_started",
- "date_created", "date_requested"))
- items.extend(recent_items[:10 - len(items)])
+ "date_finished",
+ "date_started",
+ "date_created",
+ "date_requested",
+ ),
+ )
+ items.extend(recent_items[: 10 - len(items)])
return items
@@ -401,26 +405,33 @@ class SnapRequestBuildsView(LaunchpadFormView):
@property
def label(self):
- return 'Request builds for %s' % self.context.name
+ return "Request builds for %s" % self.context.name
- page_title = 'Request builds'
+ page_title = "Request builds"
class schema(Interface):
"""Schema for requesting a build."""
- archive = Reference(IArchive, title='Source archive', required=True)
+ archive = Reference(IArchive, title="Source archive", required=True)
distro_arch_series = List(
- Choice(vocabulary='SnapDistroArchSeries'),
- title='Architectures', required=True,
+ Choice(vocabulary="SnapDistroArchSeries"),
+ title="Architectures",
+ required=True,
description=(
- 'If you do not explicitly select any architectures, then the '
- 'snap package will be built for all architectures allowed by '
- 'its configuration.'))
+ "If you do not explicitly select any architectures, then the "
+ "snap package will be built for all architectures allowed by "
+ "its configuration."
+ ),
+ )
pocket = copy_field(
- ISnapBuild['pocket'], title='Pocket', readonly=False)
+ ISnapBuild["pocket"], title="Pocket", readonly=False
+ )
channels = Dict(
- title='Source snap channels', key_type=TextLine(), required=True,
- description=ISnap['auto_build_channels'].description)
+ title="Source snap channels",
+ key_type=TextLine(),
+ required=True,
+ description=ISnap["auto_build_channels"].description,
+ )
custom_widget_archive = SnapArchiveWidget
custom_widget_distro_arch_series = LabeledMultiCheckBoxWidget
@@ -429,7 +440,7 @@ class SnapRequestBuildsView(LaunchpadFormView):
help_links = {
"pocket": "/+help-snappy/snap-build-pocket.html",
- }
+ }
@property
def cancel_url(self):
@@ -439,68 +450,78 @@ class SnapRequestBuildsView(LaunchpadFormView):
def initial_values(self):
"""See `LaunchpadFormView`."""
return {
- 'archive': (
+ "archive": (
# XXX cjwatson 2019-02-04: In order to support non-Ubuntu
# bases, we'd need to store this as None and infer it based
# on the inferred distro series; but this will do for now.
getUtility(ILaunchpadCelebrities).ubuntu.main_archive
if self.context.distro_series is None
- else self.context.distro_series.main_archive),
- 'distro_arch_series': [],
- 'pocket': PackagePublishingPocket.UPDATES,
- 'channels': self.context.auto_build_channels,
- }
+ else self.context.distro_series.main_archive
+ ),
+ "distro_arch_series": [],
+ "pocket": PackagePublishingPocket.UPDATES,
+ "channels": self.context.auto_build_channels,
+ }
- @action('Request builds', name='request')
+ @action("Request builds", name="request")
def request_action(self, action, data):
- if data.get('distro_arch_series', []):
+ if data.get("distro_arch_series", []):
architectures = [
- arch.architecturetag for arch in data['distro_arch_series']]
+ arch.architecturetag for arch in data["distro_arch_series"]
+ ]
else:
architectures = None
self.context.requestBuilds(
- self.user, data['archive'], data['pocket'],
- architectures=architectures, channels=data['channels'])
+ self.user,
+ data["archive"],
+ data["pocket"],
+ architectures=architectures,
+ channels=data["channels"],
+ )
self.request.response.addNotification(
- _('Builds will be dispatched soon.'))
+ _("Builds will be dispatched soon.")
+ )
self.next_url = self.cancel_url
class ISnapEditSchema(Interface):
"""Schema for adding or editing a snap package."""
- use_template(ISnap, include=[
- 'owner',
- 'name',
- 'information_type',
- 'project',
- 'require_virtualized',
- 'allow_internet',
- 'build_source_tarball',
- 'auto_build',
- 'auto_build_channels',
- 'store_upload',
- ])
+ use_template(
+ ISnap,
+ include=[
+ "owner",
+ "name",
+ "information_type",
+ "project",
+ "require_virtualized",
+ "allow_internet",
+ "build_source_tarball",
+ "auto_build",
+ "auto_build_channels",
+ "store_upload",
+ ],
+ )
store_distro_series = Choice(
- vocabulary='SnappyDistroSeries', required=True,
- title='Series')
- vcs = Choice(vocabulary=VCSType, required=True, title='VCS')
+ vocabulary="SnappyDistroSeries", required=True, title="Series"
+ )
+ vcs = Choice(vocabulary=VCSType, required=True, title="VCS")
# Each of these is only required if vcs has an appropriate value. Later
# validation takes care of adjusting the required attribute.
- branch = copy_field(ISnap['branch'], required=True)
- git_ref = copy_field(ISnap['git_ref'], required=True)
+ branch = copy_field(ISnap["branch"], required=True)
+ git_ref = copy_field(ISnap["git_ref"], required=True)
# These are only required if auto_build is True. Later validation takes
# care of adjusting the required attribute.
- auto_build_archive = copy_field(ISnap['auto_build_archive'], required=True)
- auto_build_pocket = copy_field(ISnap['auto_build_pocket'], required=True)
+ auto_build_archive = copy_field(ISnap["auto_build_archive"], required=True)
+ auto_build_pocket = copy_field(ISnap["auto_build_pocket"], required=True)
# This is only required if store_upload is True. Later validation takes
# care of adjusting the required attribute.
- store_name = copy_field(ISnap['store_name'], required=True)
- store_channels = copy_field(ISnap['store_channels'], required=True)
+ store_name = copy_field(ISnap["store_name"], required=True)
+ store_channels = copy_field(ISnap["store_channels"], required=True)
def log_oops(error, request):
@@ -510,29 +531,36 @@ def log_oops(error, request):
class SnapAuthorizeMixin:
-
def requestAuthorization(self, snap):
try:
self.next_url = SnapAuthorizeView.requestAuthorization(
- snap, self.request)
+ snap, self.request
+ )
except BadRequestPackageUploadResponse as e:
self.setFieldError(
- 'store_upload',
- 'Cannot get permission from the store to upload this package.')
+ "store_upload",
+ "Cannot get permission from the store to upload this package.",
+ )
log_oops(e, self.request)
-class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
- SnapInformationTypeMixin, SnapFormMixin, LaunchpadFormView):
+class SnapAddView(
+ SnapAuthorizeMixin,
+ EnableProcessorsMixin,
+ SnapInformationTypeMixin,
+ SnapFormMixin,
+ LaunchpadFormView,
+):
"""View for creating snap packages."""
- page_title = label = 'Create a new snap package'
+ page_title = label = "Create a new snap package"
schema = ISnapEditSchema
custom_widget_vcs = LaunchpadRadioWidget
custom_widget_git_ref = CustomWidgetFactory(
- GitRefWidget, allow_external=True)
+ GitRefWidget, allow_external=True
+ )
custom_widget_store_distro_series = LaunchpadRadioWidget
custom_widget_auto_build_archive = SnapArchiveWidget
custom_widget_auto_build_pocket = LaunchpadDropdownWidget
@@ -541,27 +569,27 @@ class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
help_links = {
"auto_build_pocket": "/+help-snappy/snap-build-pocket.html",
- }
+ }
@property
def field_names(self):
- fields = ['owner', 'name']
+ fields = ["owner", "name"]
if self.is_project_context:
- fields += ['vcs', 'branch', 'git_ref']
+ fields += ["vcs", "branch", "git_ref"]
else:
- fields += ['project']
+ fields += ["project"]
return fields + [
- 'information_type',
- 'store_distro_series',
- 'build_source_tarball',
- 'auto_build',
- 'auto_build_archive',
- 'auto_build_pocket',
- 'auto_build_channels',
- 'store_upload',
- 'store_name',
- 'store_channels',
- ]
+ "information_type",
+ "store_distro_series",
+ "build_source_tarball",
+ "auto_build",
+ "auto_build_archive",
+ "auto_build_pocket",
+ "auto_build_channels",
+ "store_upload",
+ "store_name",
+ "store_channels",
+ ]
def initialize(self):
"""See `LaunchpadView`."""
@@ -570,8 +598,10 @@ class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
# Once initialized, if the private_snap flag is disabled, it
# prevents snap creation for private contexts.
if not getFeatureFlag(SNAP_PRIVATE_FEATURE_FLAG):
- if (IInformationType.providedBy(self.context) and
- self.context.information_type in PRIVATE_INFORMATION_TYPES):
+ if (
+ IInformationType.providedBy(self.context)
+ and self.context.information_type in PRIVATE_INFORMATION_TYPES
+ ):
raise SnapPrivateFeatureDisabled
@property
@@ -585,19 +615,21 @@ class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
getUtility(IProcessorSet).getAll(),
"The architectures that this snap package builds for. Some "
"architectures are restricted and may only be enabled or "
- "disabled by administrators.")
+ "disabled by administrators.",
+ )
def setUpWidgets(self):
"""See `LaunchpadFormView`."""
super().setUpWidgets()
- self.widgets['processors'].widget_class = 'processors'
+ self.widgets["processors"].widget_class = "processors"
if self.is_project_context:
# If we are on Project:+new-snap page, we know which information
# types the project supports. Let's filter out the ones that are
# not supported.
types = getUtility(ISnapSet).getPossibleSnapInformationTypes(
- self.context)
- info_type_widget = self.widgets['information_type']
+ self.context
+ )
+ info_type_widget = self.widgets["information_type"]
info_type_widget.vocabulary = InformationTypeVocabulary(types)
self.setUpVCSWidgets()
@@ -612,12 +644,16 @@ class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
# Try to extract Snap store name from snapcraft.yaml file.
try:
snapcraft_data = getUtility(ISnapSet).getSnapcraftYaml(
- self.context, logger=log)
- except (MissingSnapcraftYaml, CannotFetchSnapcraftYaml,
- CannotParseSnapcraftYaml):
+ self.context, logger=log
+ )
+ except (
+ MissingSnapcraftYaml,
+ CannotFetchSnapcraftYaml,
+ CannotParseSnapcraftYaml,
+ ):
pass
else:
- store_name = snapcraft_data.get('name')
+ store_name = snapcraft_data.get("name")
store_series = getUtility(ISnappySeriesSet).getAll().first()
if store_series.can_infer_distro_series:
@@ -628,24 +664,28 @@ class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
distro_series = store_series.usable_distro_series.first()
sds_set = getUtility(ISnappyDistroSeriesSet)
store_distro_series = sds_set.getByBothSeries(
- store_series, distro_series)
+ store_series, distro_series
+ )
return {
- 'store_name': store_name,
- 'owner': self.user,
- 'store_distro_series': store_distro_series,
- 'processors': [
- p for p in getUtility(IProcessorSet).getAll()
- if p.build_by_default],
- 'auto_build_archive': (
+ "store_name": store_name,
+ "owner": self.user,
+ "store_distro_series": store_distro_series,
+ "processors": [
+ p
+ for p in getUtility(IProcessorSet).getAll()
+ if p.build_by_default
+ ],
+ "auto_build_archive": (
# XXX cjwatson 2019-02-04: In order to support non-Ubuntu
# bases, we'd need to store this as None and infer it based
# on the inferred distro series; but this will do for now.
getUtility(ILaunchpadCelebrities).ubuntu.main_archive
if distro_series is None
- else distro_series.main_archive),
- 'auto_build_pocket': PackagePublishingPocket.UPDATES,
- }
+ else distro_series.main_archive
+ ),
+ "auto_build_pocket": PackagePublishingPocket.UPDATES,
+ }
@property
def has_snappy_distro_series(self):
@@ -653,76 +693,85 @@ class SnapAddView(SnapAuthorizeMixin, EnableProcessorsMixin,
def validate_widgets(self, data, names=None):
"""See `LaunchpadFormView`."""
- if self.widgets.get('vcs') is not None:
- super().validate_widgets(data, ['vcs'])
+ if self.widgets.get("vcs") is not None:
+ super().validate_widgets(data, ["vcs"])
self.validateVCSWidgets(SnapAddView, data)
- if self.widgets.get('auto_build') is not None:
+ if self.widgets.get("auto_build") is not None:
# Set widgets as required or optional depending on the
# auto_build field.
- super().validate_widgets(data, ['auto_build'])
- auto_build = data.get('auto_build', False)
- self.widgets['auto_build_archive'].context.required = auto_build
- self.widgets['auto_build_pocket'].context.required = auto_build
- if self.widgets.get('store_upload') is not None:
+ super().validate_widgets(data, ["auto_build"])
+ auto_build = data.get("auto_build", False)
+ self.widgets["auto_build_archive"].context.required = auto_build
+ self.widgets["auto_build_pocket"].context.required = auto_build
+ if self.widgets.get("store_upload") is not None:
# Set widgets as required or optional depending on the
# store_upload field.
- super().validate_widgets(data, ['store_upload'])
- store_upload = data.get('store_upload', False)
- self.widgets['store_name'].context.required = store_upload
- self.widgets['store_channels'].context.required = store_upload
+ super().validate_widgets(data, ["store_upload"])
+ store_upload = data.get("store_upload", False)
+ self.widgets["store_name"].context.required = store_upload
+ self.widgets["store_channels"].context.required = store_upload
super().validate_widgets(data, names=names)
- @action('Create snap package', name='create')
+ @action("Create snap package", name="create")
def create_action(self, action, data):
if IGitRef.providedBy(self.context):
- kwargs = {'git_ref': self.context, 'project': data['project']}
+ kwargs = {"git_ref": self.context, "project": data["project"]}
elif IBranch.providedBy(self.context):
- kwargs = {'branch': self.context, 'project': data['project']}
+ kwargs = {"branch": self.context, "project": data["project"]}
elif self.is_project_context:
- if data['vcs'] == VCSType.GIT:
- kwargs = {'git_ref': data['git_ref']}
+ if data["vcs"] == VCSType.GIT:
+ kwargs = {"git_ref": data["git_ref"]}
else:
- kwargs = {'branch': data['branch']}
- kwargs['project'] = self.context
+ kwargs = {"branch": data["branch"]}
+ kwargs["project"] = self.context
else:
raise NotImplementedError("Unknown context for snap creation.")
- if not data.get('auto_build', False):
- data['auto_build_archive'] = None
- data['auto_build_pocket'] = None
+ if not data.get("auto_build", False):
+ data["auto_build_archive"] = None
+ data["auto_build_pocket"] = None
snap = getUtility(ISnapSet).new(
- self.user, data['owner'],
- data['store_distro_series'].distro_series, data['name'],
- auto_build=data['auto_build'],
- auto_build_archive=data['auto_build_archive'],
- auto_build_pocket=data['auto_build_pocket'],
- auto_build_channels=data['auto_build_channels'],
- information_type=data['information_type'],
- processors=data['processors'],
- build_source_tarball=data['build_source_tarball'],
- store_upload=data['store_upload'],
- store_series=data['store_distro_series'].snappy_series,
- store_name=data['store_name'],
- store_channels=data.get('store_channels'), **kwargs)
- if data['store_upload']:
+ self.user,
+ data["owner"],
+ data["store_distro_series"].distro_series,
+ data["name"],
+ auto_build=data["auto_build"],
+ auto_build_archive=data["auto_build_archive"],
+ auto_build_pocket=data["auto_build_pocket"],
+ auto_build_channels=data["auto_build_channels"],
+ information_type=data["information_type"],
+ processors=data["processors"],
+ build_source_tarball=data["build_source_tarball"],
+ store_upload=data["store_upload"],
+ store_series=data["store_distro_series"].snappy_series,
+ store_name=data["store_name"],
+ store_channels=data.get("store_channels"),
+ **kwargs,
+ )
+ if data["store_upload"]:
self.requestAuthorization(snap)
else:
self.next_url = canonical_url(snap)
def validate(self, data):
super().validate(data)
- owner = data.get('owner', None)
- name = data.get('name', None)
+ owner = data.get("owner", None)
+ name = data.get("name", None)
if owner and name:
if getUtility(ISnapSet).exists(owner, name):
self.setFieldError(
- 'name',
- 'There is already a snap package owned by %s with this '
- 'name.' % owner.displayname)
+ "name",
+ "There is already a snap package owned by %s with this "
+ "name." % owner.displayname,
+ )
self.validateInformationType(data)
-class BaseSnapEditView(SnapAuthorizeMixin, SnapInformationTypeMixin,
- SnapFormMixin, LaunchpadEditFormView):
+class BaseSnapEditView(
+ SnapAuthorizeMixin,
+ SnapInformationTypeMixin,
+ SnapFormMixin,
+ LaunchpadEditFormView,
+):
schema = ISnapEditSchema
@@ -741,59 +790,65 @@ class BaseSnapEditView(SnapAuthorizeMixin, SnapInformationTypeMixin,
def validate_widgets(self, data, names=None):
"""See `LaunchpadFormView`."""
- if self.widgets.get('vcs') is not None:
- super().validate_widgets(data, ['vcs'])
+ if self.widgets.get("vcs") is not None:
+ super().validate_widgets(data, ["vcs"])
self.validateVCSWidgets(BaseSnapEditView, data)
- if self.widgets.get('auto_build') is not None:
+ if self.widgets.get("auto_build") is not None:
# Set widgets as required or optional depending on the
# auto_build field.
- super().validate_widgets(data, ['auto_build'])
- auto_build = data.get('auto_build', False)
- self.widgets['auto_build_archive'].context.required = auto_build
- self.widgets['auto_build_pocket'].context.required = auto_build
- if self.widgets.get('store_upload') is not None:
+ super().validate_widgets(data, ["auto_build"])
+ auto_build = data.get("auto_build", False)
+ self.widgets["auto_build_archive"].context.required = auto_build
+ self.widgets["auto_build_pocket"].context.required = auto_build
+ if self.widgets.get("store_upload") is not None:
# Set widgets as required or optional depending on the
# store_upload field.
- super().validate_widgets(data, ['store_upload'])
- store_upload = data.get('store_upload', False)
- self.widgets['store_name'].context.required = store_upload
- self.widgets['store_channels'].context.required = store_upload
+ super().validate_widgets(data, ["store_upload"])
+ store_upload = data.get("store_upload", False)
+ self.widgets["store_name"].context.required = store_upload
+ self.widgets["store_channels"].context.required = store_upload
super().validate_widgets(data, names=names)
def validate(self, data):
super().validate(data)
- info_type = data.get('information_type', self.context.information_type)
- editing_info_type = 'information_type' in data
+ info_type = data.get("information_type", self.context.information_type)
+ editing_info_type = "information_type" in data
private = info_type in PRIVATE_INFORMATION_TYPES
if private is False:
# These are the requirements for public snaps.
- if 'information_type' in data or 'owner' in data:
- owner = data.get('owner', self.context.owner)
+ if "information_type" in data or "owner" in data:
+ owner = data.get("owner", self.context.owner)
if owner is not None and owner.private:
self.setFieldError(
- 'information_type' if editing_info_type else 'owner',
- 'A public snap cannot have a private owner.')
- if 'information_type' in data or 'branch' in data:
- branch = data.get('branch', self.context.branch)
+ "information_type" if editing_info_type else "owner",
+ "A public snap cannot have a private owner.",
+ )
+ if "information_type" in data or "branch" in data:
+ branch = data.get("branch", self.context.branch)
if branch is not None and branch.private:
self.setFieldError(
- 'information_type' if editing_info_type else 'branch',
- 'A public snap cannot have a private branch.')
- if 'information_type' in data or 'git_ref' in data:
- ref = data.get('git_ref', self.context.git_ref)
+ "information_type" if editing_info_type else "branch",
+ "A public snap cannot have a private branch.",
+ )
+ if "information_type" in data or "git_ref" in data:
+ ref = data.get("git_ref", self.context.git_ref)
if ref is not None and ref.private:
self.setFieldError(
- 'information_type' if editing_info_type else 'git_ref',
- 'A public snap cannot have a private repository.')
+ "information_type" if editing_info_type else "git_ref",
+ "A public snap cannot have a private repository.",
+ )
self.validateInformationType(data, snap=self.context)
def _needStoreReauth(self, data):
"""Does this change require reauthorizing to the store?"""
- store_upload = data.get('store_upload', False)
- store_distro_series = data.get('store_distro_series')
- store_name = data.get('store_name')
- if (not store_upload or
- store_distro_series is None or store_name is None):
+ store_upload = data.get("store_upload", False)
+ store_distro_series = data.get("store_distro_series")
+ store_name = data.get("store_name")
+ if (
+ not store_upload
+ or store_distro_series is None
+ or store_name is None
+ ):
return False
if not self.context.store_upload:
return True
@@ -803,37 +858,38 @@ class BaseSnapEditView(SnapAuthorizeMixin, SnapInformationTypeMixin,
return True
return False
- @action('Update snap package', name='update')
+ @action("Update snap package", name="update")
def request_action(self, action, data):
- vcs = data.pop('vcs', None)
+ vcs = data.pop("vcs", None)
if vcs == VCSType.BZR:
- data['git_ref'] = None
+ data["git_ref"] = None
elif vcs == VCSType.GIT:
- data['branch'] = None
- new_processors = data.get('processors')
+ data["branch"] = None
+ new_processors = data.get("processors")
if new_processors is not None:
if set(self.context.processors) != set(new_processors):
self.context.setProcessors(
- new_processors, check_permissions=True, user=self.user)
- del data['processors']
- if not data.get('auto_build', False):
- if 'auto_build_archive' in data:
- del data['auto_build_archive']
- if 'auto_build_pocket' in data:
- del data['auto_build_pocket']
- if 'auto_build_channels' in data:
- del data['auto_build_channels']
- store_upload = data.get('store_upload', False)
+ new_processors, check_permissions=True, user=self.user
+ )
+ del data["processors"]
+ if not data.get("auto_build", False):
+ if "auto_build_archive" in data:
+ del data["auto_build_archive"]
+ if "auto_build_pocket" in data:
+ del data["auto_build_pocket"]
+ if "auto_build_channels" in data:
+ del data["auto_build_channels"]
+ store_upload = data.get("store_upload", False)
if not store_upload:
- if 'store_name' in data:
- del data['store_name']
- if 'store_channels' in data:
- del data['store_channels']
+ if "store_name" in data:
+ del data["store_name"]
+ if "store_channels" in data:
+ del data["store_channels"]
need_store_reauth = self._needStoreReauth(data)
- info_type = data.get('information_type')
+ info_type = data.get("information_type")
if info_type and info_type != self.context.information_type:
self.context.information_type = info_type
- del data['information_type']
+ del data["information_type"]
self.updateContextFromData(data)
if need_store_reauth:
self.requestAuthorization(self.context)
@@ -851,15 +907,19 @@ class SnapAdminView(BaseSnapEditView):
@property
def label(self):
- return 'Administer %s snap package' % self.context.name
+ return "Administer %s snap package" % self.context.name
- page_title = 'Administer'
+ page_title = "Administer"
# XXX pappacena 2021-02-19: Once we have the whole privacy work in
# place, we should move "project" and "information_type" from +admin
# page to +edit, to allow common users to edit this.
field_names = [
- 'project', 'information_type', 'require_virtualized', 'allow_internet']
+ "project",
+ "information_type",
+ "require_virtualized",
+ "allow_internet",
+ ]
@property
def initial_values(self):
@@ -868,11 +928,11 @@ class SnapAdminView(BaseSnapEditView):
# database column, it will be NULL, but snap.information_type
# property has a fallback to check "private" property. This should
# be removed once we back fill snap.information_type.
- return {'information_type': self.context.information_type}
+ return {"information_type": self.context.information_type}
def updateContextFromData(self, data, context=None, notify_modified=True):
- if 'project' in data:
- project = data.pop('project')
+ if "project" in data:
+ project = data.pop("project")
self.context.setProject(project)
super().updateContextFromData(data, context, notify_modified)
@@ -882,32 +942,33 @@ class SnapEditView(BaseSnapEditView, EnableProcessorsMixin):
@property
def label(self):
- return 'Edit %s snap package' % self.context.name
+ return "Edit %s snap package" % self.context.name
- page_title = 'Edit'
+ page_title = "Edit"
field_names = [
- 'owner',
- 'name',
- 'project',
- 'information_type',
- 'store_distro_series',
- 'vcs',
- 'branch',
- 'git_ref',
- 'build_source_tarball',
- 'auto_build',
- 'auto_build_archive',
- 'auto_build_pocket',
- 'auto_build_channels',
- 'store_upload',
- 'store_name',
- 'store_channels',
- ]
+ "owner",
+ "name",
+ "project",
+ "information_type",
+ "store_distro_series",
+ "vcs",
+ "branch",
+ "git_ref",
+ "build_source_tarball",
+ "auto_build",
+ "auto_build_archive",
+ "auto_build_pocket",
+ "auto_build_channels",
+ "store_upload",
+ "store_name",
+ "store_channels",
+ ]
custom_widget_store_distro_series = LaunchpadRadioWidget
custom_widget_vcs = LaunchpadRadioWidget
custom_widget_git_ref = CustomWidgetFactory(
- GitRefWidget, allow_external=True)
+ GitRefWidget, allow_external=True
+ )
custom_widget_auto_build_archive = SnapArchiveWidget
custom_widget_auto_build_pocket = LaunchpadDropdownWidget
custom_widget_auto_build_channels = SnapBuildChannelsWidget
@@ -915,11 +976,12 @@ class SnapEditView(BaseSnapEditView, EnableProcessorsMixin):
# See `setUpWidgets` method.
custom_widget_information_type = CustomWidgetFactory(
LaunchpadRadioWidgetWithDescription,
- vocabulary=InformationTypeVocabulary(types=[]))
+ vocabulary=InformationTypeVocabulary(types=[]),
+ )
help_links = {
"auto_build_pocket": "/+help-snappy/snap-build-pocket.html",
- }
+ }
def setUpFields(self):
"""See `LaunchpadFormView`."""
@@ -928,62 +990,66 @@ class SnapEditView(BaseSnapEditView, EnableProcessorsMixin):
self.context.available_processors,
"The architectures that this snap package builds for. Some "
"architectures are restricted and may only be enabled or "
- "disabled by administrators.")
+ "disabled by administrators.",
+ )
def setUpWidgets(self, context=None):
super().setUpWidgets(context)
- info_type_widget = self.widgets['information_type']
+ info_type_widget = self.widgets["information_type"]
info_type_widget.vocabulary = InformationTypeVocabulary(
- types=self.getPossibleInformationTypes(self.context, self.user))
+ types=self.getPossibleInformationTypes(self.context, self.user)
+ )
@property
def initial_values(self):
initial_values = {}
if self.context.git_ref is not None:
- initial_values['vcs'] = VCSType.GIT
+ initial_values["vcs"] = VCSType.GIT
else:
- initial_values['vcs'] = VCSType.BZR
+ initial_values["vcs"] = VCSType.BZR
if self.context.auto_build_pocket is None:
- initial_values['auto_build_pocket'] = (
- PackagePublishingPocket.UPDATES)
+ initial_values[
+ "auto_build_pocket"
+ ] = PackagePublishingPocket.UPDATES
# XXX pappacena 2021-02-12: Until we back fill information_type
# database column, it will be NULL, but snap.information_type
# property has a fallback to check "private" property. This should
# be removed once we back fill snap.information_type.
- initial_values['information_type'] = self.context.information_type
+ initial_values["information_type"] = self.context.information_type
return initial_values
def validate(self, data):
super().validate(data)
- owner = data.get('owner', None)
- name = data.get('name', None)
+ owner = data.get("owner", None)
+ name = data.get("name", None)
if owner and name:
try:
snap = getUtility(ISnapSet).getByName(owner, name)
if snap != self.context:
self.setFieldError(
- 'name',
- 'There is already a snap package owned by %s with '
- 'this name.' % owner.displayname)
+ "name",
+ "There is already a snap package owned by %s with "
+ "this name." % owner.displayname,
+ )
except NoSuchSnap:
pass
- if 'processors' in data:
+ if "processors" in data:
available_processors = set(self.context.available_processors)
- widget = self.widgets['processors']
+ widget = self.widgets["processors"]
for processor in self.context.processors:
- if processor not in data['processors']:
+ if processor not in data["processors"]:
if processor not in available_processors:
# This processor is not currently available for
# selection, but is enabled. Leave it untouched.
- data['processors'].append(processor)
+ data["processors"].append(processor)
elif processor.name in widget.disabled_items:
# This processor is restricted and currently
# enabled. Leave it untouched.
- data['processors'].append(processor)
+ data["processors"].append(processor)
def updateContextFromData(self, data, context=None, notify_modified=True):
- if 'project' in data:
- project = data.pop('project')
+ if "project" in data:
+ project = data.pop("project")
self.context.setProject(project)
super().updateContextFromData(data, context, notify_modified)
@@ -993,15 +1059,16 @@ class SnapAuthorizeView(LaunchpadEditFormView):
@property
def label(self):
- return 'Authorize store uploads of %s' % self.context.name
+ return "Authorize store uploads of %s" % self.context.name
- page_title = 'Authorize store uploads'
+ page_title = "Authorize store uploads"
class schema(Interface):
"""Schema for authorizing snap package uploads to the store."""
discharge_macaroon = TextLine(
- title='Serialized discharge macaroon', required=True)
+ title="Serialized discharge macaroon", required=True
+ )
render_context = False
@@ -1016,37 +1083,46 @@ class SnapAuthorizeView(LaunchpadEditFormView):
"""Begin the process of authorizing uploads of a snap package."""
try:
sso_caveat_id = snap.beginAuthorization()
- base_url = canonical_url(snap, view_name='+authorize')
- login_url = urlappend(base_url, '+login')
- login_url += '?%s' % urlencode([
- ('macaroon_caveat_id', sso_caveat_id),
- ('discharge_macaroon_action', 'field.actions.complete'),
- ('discharge_macaroon_field', 'field.discharge_macaroon'),
- ])
+ base_url = canonical_url(snap, view_name="+authorize")
+ login_url = urlappend(base_url, "+login")
+ login_url += "?%s" % urlencode(
+ [
+ ("macaroon_caveat_id", sso_caveat_id),
+ ("discharge_macaroon_action", "field.actions.complete"),
+ ("discharge_macaroon_field", "field.discharge_macaroon"),
+ ]
+ )
return login_url
except CannotAuthorizeStoreUploads as e:
request.response.addInfoNotification(str(e))
request.response.redirect(canonical_url(snap))
return
- @action('Begin authorization', name='begin')
+ @action("Begin authorization", name="begin")
def begin_action(self, action, data):
login_url = self.requestAuthorization(self.context, self.request)
if login_url is not None:
self.request.response.redirect(login_url)
- @action('Complete authorization', name='complete')
+ @action("Complete authorization", name="complete")
def complete_action(self, action, data):
- if not data.get('discharge_macaroon'):
- self.addError(structured(
- _('Uploads of %(snap)s to the store were not authorized.'),
- snap=self.context.name))
+ if not data.get("discharge_macaroon"):
+ self.addError(
+ structured(
+ _("Uploads of %(snap)s to the store were not authorized."),
+ snap=self.context.name,
+ )
+ )
return
self.context.completeAuthorization(
- discharge_macaroon=data['discharge_macaroon'])
- self.request.response.addInfoNotification(structured(
- _('Uploads of %(snap)s to the store are now authorized.'),
- snap=self.context.name))
+ discharge_macaroon=data["discharge_macaroon"]
+ )
+ self.request.response.addInfoNotification(
+ structured(
+ _("Uploads of %(snap)s to the store are now authorized."),
+ snap=self.context.name,
+ )
+ )
self.request.response.redirect(canonical_url(self.context))
@property
@@ -1060,14 +1136,14 @@ class SnapDeleteView(BaseSnapEditView):
@property
def label(self):
- return 'Delete %s snap package' % self.context.name
+ return "Delete %s snap package" % self.context.name
- page_title = 'Delete'
+ page_title = "Delete"
field_names = []
- @action('Delete snap package', name='delete')
+ @action("Delete snap package", name="delete")
def delete_action(self, action, data):
owner = self.context.owner
self.context.destroySelf()
- self.next_url = canonical_url(owner, view_name='+snaps')
+ self.next_url = canonical_url(owner, view_name="+snaps")
diff --git a/lib/lp/snappy/browser/snapbase.py b/lib/lp/snappy/browser/snapbase.py
index 3bd070a..719bb26 100644
--- a/lib/lp/snappy/browser/snapbase.py
+++ b/lib/lp/snappy/browser/snapbase.py
@@ -6,20 +6,13 @@
__all__ = [
"SnapBaseNavigation",
"SnapBaseSetNavigation",
- ]
+]
from zope.component import getUtility
from lp.services.database.sqlobject import SQLObjectNotFound
-from lp.services.webapp import (
- GetitemNavigation,
- Navigation,
- stepthrough,
- )
-from lp.snappy.interfaces.snapbase import (
- ISnapBase,
- ISnapBaseSet,
- )
+from lp.services.webapp import GetitemNavigation, Navigation, stepthrough
+from lp.snappy.interfaces.snapbase import ISnapBase, ISnapBaseSet
from lp.soyuz.interfaces.archive import IArchiveSet
@@ -52,4 +45,5 @@ class SnapBaseNavigation(Navigation):
class SnapBaseSetNavigation(GetitemNavigation):
"""Navigation methods for `ISnapBaseSet`."""
+
usedfor = ISnapBaseSet
diff --git a/lib/lp/snappy/browser/snapbuild.py b/lib/lp/snappy/browser/snapbuild.py
index ad54692..405000d 100644
--- a/lib/lp/snappy/browser/snapbuild.py
+++ b/lib/lp/snappy/browser/snapbuild.py
@@ -4,33 +4,30 @@
"""SnapBuild views."""
__all__ = [
- 'SnapBuildContextMenu',
- 'SnapBuildNavigation',
- 'SnapBuildView',
- ]
+ "SnapBuildContextMenu",
+ "SnapBuildNavigation",
+ "SnapBuildView",
+]
from zope.interface import Interface
-from lp.app.browser.launchpadform import (
- action,
- LaunchpadFormView,
- )
+from lp.app.browser.launchpadform import LaunchpadFormView, action
from lp.services.librarian.browser import (
FileNavigationMixin,
ProxiedLibraryFileAlias,
- )
+)
from lp.services.propertycache import cachedproperty
from lp.services.webapp import (
- canonical_url,
ContextMenu,
- enabled_with_permission,
Link,
Navigation,
- )
+ canonical_url,
+ enabled_with_permission,
+)
from lp.snappy.interfaces.snapbuild import (
CannotScheduleStoreUpload,
ISnapBuild,
- )
+)
from lp.soyuz.interfaces.binarypackagebuild import IBuildRescoreForm
@@ -43,27 +40,36 @@ class SnapBuildContextMenu(ContextMenu):
usedfor = ISnapBuild
- facet = 'overview'
+ facet = "overview"
- links = ('retry', 'cancel', 'rescore')
+ links = ("retry", "cancel", "rescore")
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def retry(self):
return Link(
- '+retry', 'Retry this build', icon='retry',
- enabled=self.context.can_be_retried)
+ "+retry",
+ "Retry this build",
+ icon="retry",
+ enabled=self.context.can_be_retried,
+ )
- @enabled_with_permission('launchpad.Edit')
+ @enabled_with_permission("launchpad.Edit")
def cancel(self):
return Link(
- '+cancel', 'Cancel build', icon='remove',
- enabled=self.context.can_be_cancelled)
+ "+cancel",
+ "Cancel build",
+ icon="remove",
+ enabled=self.context.can_be_cancelled,
+ )
- @enabled_with_permission('launchpad.Admin')
+ @enabled_with_permission("launchpad.Admin")
def rescore(self):
return Link(
- '+rescore', 'Rescore build', icon='edit',
- enabled=self.context.can_be_rescored)
+ "+rescore",
+ "Rescore build",
+ icon="edit",
+ enabled=self.context.can_be_rescored,
+ )
class SnapBuildView(LaunchpadFormView):
@@ -86,7 +92,9 @@ class SnapBuildView(LaunchpadFormView):
return [
ProxiedLibraryFileAlias(alias, self.context)
- for _, alias, _ in self.context.getFiles() if not alias.deleted]
+ for _, alias, _ in self.context.getFiles()
+ if not alias.deleted
+ ]
@cachedproperty
def has_files(self):
@@ -96,7 +104,7 @@ class SnapBuildView(LaunchpadFormView):
def next_url(self):
return canonical_url(self.context)
- @action('Upload build to store', name='upload')
+ @action("Upload build to store", name="upload")
def upload_action(self, action, data):
"""Schedule an upload of this build to the store."""
try:
@@ -106,7 +114,8 @@ class SnapBuildView(LaunchpadFormView):
else:
self.request.response.addInfoNotification(
"An upload has been scheduled and will run as soon as "
- "possible.")
+ "possible."
+ )
class SnapBuildRetryView(LaunchpadFormView):
@@ -115,22 +124,24 @@ class SnapBuildRetryView(LaunchpadFormView):
class schema(Interface):
"""Schema for retrying a build."""
- page_title = label = 'Retry build'
+ page_title = label = "Retry build"
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
- @action('Retry build', name='retry')
+ @action("Retry build", name="retry")
def request_action(self, action, data):
"""Retry the build."""
if not self.context.can_be_retried:
self.request.response.addErrorNotification(
- 'Build cannot be retried')
+ "Build cannot be retried"
+ )
else:
self.context.retry()
- self.request.response.addInfoNotification('Build has been queued')
+ self.request.response.addInfoNotification("Build has been queued")
self.request.response.redirect(self.next_url)
@@ -141,14 +152,15 @@ class SnapBuildCancelView(LaunchpadFormView):
class schema(Interface):
"""Schema for cancelling a build."""
- page_title = label = 'Cancel build'
+ page_title = label = "Cancel build"
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
- @action('Cancel build', name='cancel')
+ @action("Cancel build", name="cancel")
def request_action(self, action, data):
"""Cancel the build."""
self.context.cancel()
@@ -159,27 +171,29 @@ class SnapBuildRescoreView(LaunchpadFormView):
schema = IBuildRescoreForm
- page_title = label = 'Rescore build'
+ page_title = label = "Rescore build"
def __call__(self):
if self.context.can_be_rescored:
return super().__call__()
self.request.response.addWarningNotification(
- "Cannot rescore this build because it is not queued.")
+ "Cannot rescore this build because it is not queued."
+ )
self.request.response.redirect(canonical_url(self.context))
@property
def cancel_url(self):
return canonical_url(self.context)
+
next_url = cancel_url
- @action('Rescore build', name='rescore')
+ @action("Rescore build", name="rescore")
def request_action(self, action, data):
"""Rescore the build."""
- score = data.get('priority')
+ score = data.get("priority")
self.context.rescore(score)
- self.request.response.addNotification('Build rescored to %s.' % score)
+ self.request.response.addNotification("Build rescored to %s." % score)
@property
def initial_values(self):
- return {'score': str(self.context.buildqueue_record.lastscore)}
+ return {"score": str(self.context.buildqueue_record.lastscore)}
diff --git a/lib/lp/snappy/browser/snaplisting.py b/lib/lp/snappy/browser/snaplisting.py
index 37098b5..410c8ab 100644
--- a/lib/lp/snappy/browser/snaplisting.py
+++ b/lib/lp/snappy/browser/snaplisting.py
@@ -4,10 +4,10 @@
"""Base class view for snap listings."""
__all__ = [
- 'BranchSnapListingView',
- 'GitSnapListingView',
- 'PersonSnapListingView',
- ]
+ "BranchSnapListingView",
+ "GitSnapListingView",
+ "PersonSnapListingView",
+]
from functools import partial
@@ -31,19 +31,22 @@ class SnapListingView(LaunchpadView, FeedsMixin):
@property
def page_title(self):
- return 'Snap packages'
+ return "Snap packages"
@property
def label(self):
- return 'Snap packages for %(displayname)s' % {
- 'displayname': self.context.displayname}
+ return "Snap packages for %(displayname)s" % {
+ "displayname": self.context.displayname
+ }
def initialize(self):
super().initialize()
snaps = getUtility(ISnapSet).findByContext(
- self.context, visible_by_user=self.user)
+ self.context, visible_by_user=self.user
+ )
loader = partial(
- getUtility(ISnapSet).preloadDataForSnaps, user=self.user)
+ getUtility(ISnapSet).preloadDataForSnaps, user=self.user
+ )
self.snaps = DecoratedResultSet(snaps, pre_iter_hook=loader)
@cachedproperty
@@ -69,8 +72,9 @@ class GitSnapListingView(SnapListingView):
@property
def label(self):
- return 'Snap packages for %(display_name)s' % {
- 'display_name': self.context.display_name}
+ return "Snap packages for %(display_name)s" % {
+ "display_name": self.context.display_name
+ }
class PersonSnapListingView(SnapListingView):
diff --git a/lib/lp/snappy/browser/snappyseries.py b/lib/lp/snappy/browser/snappyseries.py
index 7939d33..281f8a0 100644
--- a/lib/lp/snappy/browser/snappyseries.py
+++ b/lib/lp/snappy/browser/snappyseries.py
@@ -4,8 +4,8 @@
"""SnappySeries views."""
__all__ = [
- 'SnappySeriesSetNavigation',
- ]
+ "SnappySeriesSetNavigation",
+]
from lp.services.webapp import GetitemNavigation
from lp.snappy.interfaces.snappyseries import ISnappySeriesSet
@@ -13,4 +13,5 @@ from lp.snappy.interfaces.snappyseries import ISnappySeriesSet
class SnappySeriesSetNavigation(GetitemNavigation):
"""Navigation methods for `ISnappySeriesSet`."""
+
usedfor = ISnappySeriesSet
diff --git a/lib/lp/snappy/browser/snapsubscription.py b/lib/lp/snappy/browser/snapsubscription.py
index dcb6a2c..7bbc80e 100644
--- a/lib/lp/snappy/browser/snapsubscription.py
+++ b/lib/lp/snappy/browser/snapsubscription.py
@@ -3,9 +3,7 @@
"""Snap subscription views."""
-__all__ = [
- 'SnapPortletSubscribersContent'
-]
+__all__ = ["SnapPortletSubscribersContent"]
from zope.component import getUtility
from zope.formlib.form import action
@@ -14,16 +12,13 @@ from zope.security.interfaces import ForbiddenAttribute
from lp.app.browser.launchpadform import (
LaunchpadEditFormView,
LaunchpadFormView,
- )
+)
from lp.registry.interfaces.person import IPersonSet
-from lp.services.webapp import (
- canonical_url,
- LaunchpadView,
- )
+from lp.services.webapp import LaunchpadView, canonical_url
from lp.services.webapp.authorization import (
check_permission,
precache_permission_for_objects,
- )
+)
from lp.snappy.interfaces.snapsubscription import ISnapSubscription
@@ -38,20 +33,28 @@ class SnapPortletSubscribersContent(LaunchpadView):
# need the expense of running several complex SQL queries.
subscriptions = list(self.context.subscriptions)
person_ids = [sub.person.id for sub in subscriptions]
- list(getUtility(IPersonSet).getPrecachedPersonsFromIDs(
- person_ids, need_validity=True))
+ list(
+ getUtility(IPersonSet).getPrecachedPersonsFromIDs(
+ person_ids, need_validity=True
+ )
+ )
if self.user is not None:
subscribers = [
- subscription.person for subscription in subscriptions]
+ subscription.person for subscription in subscriptions
+ ]
precache_permission_for_objects(
- self.request, "launchpad.LimitedView", subscribers)
+ self.request, "launchpad.LimitedView", subscribers
+ )
visible_subscriptions = [
- subscription for subscription in subscriptions
- if check_permission("launchpad.LimitedView", subscription.person)]
+ subscription
+ for subscription in subscriptions
+ if check_permission("launchpad.LimitedView", subscription.person)
+ ]
return sorted(
visible_subscriptions,
- key=lambda subscription: subscription.person.displayname)
+ key=lambda subscription: subscription.person.displayname,
+ )
class RedirectToSnapMixin:
@@ -75,20 +78,19 @@ class RedirectToSnapMixin:
class SnapSubscriptionEditView(RedirectToSnapMixin, LaunchpadEditFormView):
"""The view for editing Snap recipe subscriptions."""
+
schema = ISnapSubscription
field_names = []
@property
def page_title(self):
- return (
- "Edit subscription to snap recipe %s" %
- self.snap.displayname)
+ return "Edit subscription to snap recipe %s" % self.snap.displayname
@property
def label(self):
return (
- "Edit subscription to snap recipe for %s" %
- self.person.displayname)
+ "Edit subscription to snap recipe for %s" % self.person.displayname
+ )
def initialize(self):
self.snap = self.context.snap
@@ -101,7 +103,8 @@ class SnapSubscriptionEditView(RedirectToSnapMixin, LaunchpadEditFormView):
self.snap.unsubscribe(self.person, self.user)
self.request.response.addNotification(
"%s has been unsubscribed from this snap recipe."
- % self.person.displayname)
+ % self.person.displayname
+ )
class _SnapSubscriptionCreationView(RedirectToSnapMixin, LaunchpadFormView):
@@ -125,12 +128,14 @@ class SnapSubscriptionAddView(_SnapSubscriptionCreationView):
# subscribed before continuing.
if self.context.hasSubscription(self.user):
self.request.response.addNotification(
- "You are already subscribed to this snap recipe.")
+ "You are already subscribed to this snap recipe."
+ )
else:
self.context.subscribe(self.user, self.user)
self.request.response.addNotification(
- "You have subscribed to this snap recipe.")
+ "You have subscribed to this snap recipe."
+ )
class SnapSubscriptionAddOtherView(_SnapSubscriptionCreationView):
@@ -149,12 +154,14 @@ class SnapSubscriptionAddOtherView(_SnapSubscriptionCreationView):
if "person" in data:
person = data["person"]
subscription = self.context.getSubscription(person)
- if (subscription is None
- and not self.context.userCanBeSubscribed(person)):
+ if subscription is None and not self.context.userCanBeSubscribed(
+ person
+ ):
self.setFieldError(
"person",
"Open and delegated teams cannot be subscribed to "
- "private snap recipes.")
+ "private snap recipes.",
+ )
@action("Subscribe", name="subscribe_action")
def subscribe_action(self, action, data):
@@ -164,9 +171,11 @@ class SnapSubscriptionAddOtherView(_SnapSubscriptionCreationView):
if subscription is None:
self.context.subscribe(person, self.user)
self.request.response.addNotification(
- "%s has been subscribed to this snap recipe." %
- person.displayname)
+ "%s has been subscribed to this snap recipe."
+ % person.displayname
+ )
else:
self.request.response.addNotification(
- "%s was already subscribed to this snap recipe." %
- person.displayname)
+ "%s was already subscribed to this snap recipe."
+ % person.displayname
+ )
diff --git a/lib/lp/snappy/browser/tests/test_hassnaps.py b/lib/lp/snappy/browser/tests/test_hassnaps.py
index 88608a6..edad008 100644
--- a/lib/lp/snappy/browser/tests/test_hassnaps.py
+++ b/lib/lp/snappy/browser/tests/test_hassnaps.py
@@ -4,10 +4,7 @@
"""Test views for objects that have snap packages."""
import soupmatchers
-from testscenarios import (
- load_tests_apply_scenarios,
- WithScenarios,
- )
+from testscenarios import WithScenarios, load_tests_apply_scenarios
from lp.code.interfaces.branch import IBranch
from lp.code.interfaces.gitrepository import IGitRepository
@@ -35,19 +32,28 @@ class TestHasSnapsView(WithScenarios, TestCaseWithFactory):
layer = DatabaseFunctionalLayer
scenarios = [
- ("Branch", {
- "context_type": "branch",
- "context_factory": make_branch,
- }),
- ("GitRepository", {
- "context_type": "repository",
- "context_factory": make_git_repository,
- }),
- ("GitRef", {
- "context_type": "branch",
- "context_factory": make_git_ref,
- }),
- ]
+ (
+ "Branch",
+ {
+ "context_type": "branch",
+ "context_factory": make_branch,
+ },
+ ),
+ (
+ "GitRepository",
+ {
+ "context_type": "repository",
+ "context_factory": make_git_repository,
+ },
+ ),
+ (
+ "GitRef",
+ {
+ "context_type": "branch",
+ "context_factory": make_git_ref,
+ },
+ ),
+ ]
def makeSnap(self, context):
if IBranch.providedBy(context):
@@ -63,16 +69,18 @@ class TestHasSnapsView(WithScenarios, TestCaseWithFactory):
view = create_initialized_view(context, "+index")
self.assertEqual(
"No snap packages using this %s." % self.context_type,
- view.snaps_link)
+ view.snaps_link,
+ )
def test_snaps_link_one_snap(self):
# An object with one snap package shows a link to that snap package.
context = self.context_factory(self)
snap = self.makeSnap(context)
view = create_initialized_view(context, "+index")
- expected_link = (
- '<a href="%s">1 snap package</a> using this %s.' %
- (canonical_url(snap), self.context_type))
+ expected_link = '<a href="%s">1 snap package</a> using this %s.' % (
+ canonical_url(snap),
+ self.context_type,
+ )
self.assertEqual(expected_link, view.snaps_link)
def test_snaps_link_more_snaps(self):
@@ -82,8 +90,9 @@ class TestHasSnapsView(WithScenarios, TestCaseWithFactory):
self.makeSnap(context)
view = create_initialized_view(context, "+index")
expected_link = (
- '<a href="+snaps">2 snap packages</a> using this %s.' %
- self.context_type)
+ '<a href="+snaps">2 snap packages</a> using this %s.'
+ % self.context_type
+ )
self.assertEqual(expected_link, view.snaps_link)
@@ -94,14 +103,20 @@ class TestHasSnapsMenu(WithScenarios, TestCaseWithFactory):
needs_git_hosting_fixture = False
scenarios = [
- ("Branch", {
- "context_factory": make_branch,
- }),
- ("GitRef", {
- "context_factory": make_git_ref,
- "needs_git_hosting_fixture": True,
- }),
- ]
+ (
+ "Branch",
+ {
+ "context_factory": make_branch,
+ },
+ ),
+ (
+ "GitRef",
+ {
+ "context_factory": make_git_ref,
+ "needs_git_hosting_fixture": True,
+ },
+ ),
+ ]
def setUp(self):
super().setUp()
@@ -119,10 +134,17 @@ class TestHasSnapsMenu(WithScenarios, TestCaseWithFactory):
context = self.context_factory(self)
view = create_initialized_view(context, "+index")
new_snap_url = canonical_url(context, view_name="+new-snap")
- self.assertThat(view(), soupmatchers.HTMLContains(
- soupmatchers.Tag(
- "creation link", "a", attrs={"href": new_snap_url},
- text="Create snap package")))
+ self.assertThat(
+ view(),
+ soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "creation link",
+ "a",
+ attrs={"href": new_snap_url},
+ text="Create snap package",
+ )
+ ),
+ )
def test_creation_link_snaps(self):
# An object with snap packages shows a creation link.
@@ -130,10 +152,17 @@ class TestHasSnapsMenu(WithScenarios, TestCaseWithFactory):
self.makeSnap(context)
view = create_initialized_view(context, "+index")
new_snap_url = canonical_url(context, view_name="+new-snap")
- self.assertThat(view(), soupmatchers.HTMLContains(
- soupmatchers.Tag(
- "creation link", "a", attrs={"href": new_snap_url},
- text="Create snap package")))
+ self.assertThat(
+ view(),
+ soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "creation link",
+ "a",
+ attrs={"href": new_snap_url},
+ text="Create snap package",
+ )
+ ),
+ )
load_tests = load_tests_apply_scenarios
diff --git a/lib/lp/snappy/browser/tests/test_snap.py b/lib/lp/snappy/browser/tests/test_snap.py
index fc683c4..74b502c 100644
--- a/lib/lp/snappy/browser/tests/test_snap.py
+++ b/lib/lp/snappy/browser/tests/test_snap.py
@@ -3,22 +3,17 @@
"""Test snap package views."""
-from datetime import (
- datetime,
- timedelta,
- )
import json
import re
-from urllib.parse import (
- parse_qs,
- urlsplit,
- )
+from datetime import datetime, timedelta
+from urllib.parse import parse_qs, urlsplit
-from fixtures import FakeLogger
-from pymacaroons import Macaroon
import pytz
import responses
import soupmatchers
+import transaction
+from fixtures import FakeLogger
+from pymacaroons import Macaroon
from testtools.matchers import (
AfterPreprocessing,
Equals,
@@ -28,8 +23,7 @@ from testtools.matchers import (
MatchesSetwise,
MatchesStructure,
Not,
- )
-import transaction
+)
from zope.component import getUtility
from zope.publisher.interfaces import NotFound
from zope.security.interfaces import Unauthorized
@@ -40,19 +34,13 @@ from lp.app.enums import InformationType
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.buildmaster.enums import BuildStatus
from lp.buildmaster.interfaces.processor import IProcessorSet
-from lp.code.errors import (
- BranchHostingFault,
- GitRepositoryScanFault,
- )
-from lp.code.tests.helpers import (
- BranchHostingFixture,
- GitHostingFixture,
- )
+from lp.code.errors import BranchHostingFault, GitRepositoryScanFault
+from lp.code.tests.helpers import BranchHostingFixture, GitHostingFixture
from lp.registry.enums import (
BranchSharingPolicy,
PersonVisibility,
TeamMembershipPolicy,
- )
+)
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.series import SeriesStatus
from lp.services.config import config
@@ -63,54 +51,41 @@ from lp.services.job.interfaces.job import JobStatus
from lp.services.propertycache import get_property_cache
from lp.services.webapp import canonical_url
from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.snappy.browser.snap import (
- SnapAdminView,
- SnapEditView,
- SnapView,
- )
+from lp.snappy.browser.snap import SnapAdminView, SnapEditView, SnapView
from lp.snappy.interfaces.snap import (
- CannotModifySnapProcessor,
- ISnapSet,
SNAP_PRIVATE_FEATURE_FLAG,
SNAP_TESTING_FLAGS,
+ CannotModifySnapProcessor,
+ ISnapSet,
SnapBuildRequestStatus,
SnapPrivateFeatureDisabled,
- )
+)
from lp.snappy.interfaces.snappyseries import ISnappyDistroSeriesSet
from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient
from lp.snappy.model.snap import Snap
from lp.testing import (
- admin_logged_in,
BrowserTestCase,
+ TestCaseWithFactory,
+ admin_logged_in,
login,
login_admin,
login_person,
person_logged_in,
- TestCaseWithFactory,
time_counter,
- )
+)
from lp.testing.fakemethod import FakeMethod
from lp.testing.fixture import ZopeUtilityFixture
-from lp.testing.layers import (
- DatabaseFunctionalLayer,
- LaunchpadFunctionalLayer,
- )
-from lp.testing.matchers import (
- MatchesPickerText,
- MatchesTagText,
- )
+from lp.testing.layers import DatabaseFunctionalLayer, LaunchpadFunctionalLayer
+from lp.testing.matchers import MatchesPickerText, MatchesTagText
from lp.testing.pages import (
extract_text,
find_main_content,
find_tag_by_id,
find_tags_by_class,
get_feedback_messages,
- )
+)
from lp.testing.publication import test_traverse
-from lp.testing.views import (
- create_initialized_view,
- create_view,
- )
+from lp.testing.views import create_initialized_view, create_view
class TestSnapNavigation(TestCaseWithFactory):
@@ -124,15 +99,17 @@ class TestSnapNavigation(TestCaseWithFactory):
def test_canonical_url(self):
owner = self.factory.makePerson(name="person")
snap = self.factory.makeSnap(
- registrant=owner, owner=owner, name="snap")
+ registrant=owner, owner=owner, name="snap"
+ )
self.assertEqual(
- "http://launchpad.test/~person/+snap/snap", canonical_url(snap))
+ "http://launchpad.test/~person/+snap/snap", canonical_url(snap)
+ )
def test_snap(self):
snap = self.factory.makeSnap()
obj, _, _ = test_traverse(
- "http://launchpad.test/~%s/+snap/%s" % (
- snap.owner.name, snap.name))
+ "http://launchpad.test/~%s/+snap/%s" % (snap.owner.name, snap.name)
+ )
self.assertEqual(snap, obj)
@@ -146,11 +123,15 @@ class TestSnapViewsFeatureFlag(TestCaseWithFactory):
self.useFixture(BranchHostingFixture())
owner = self.factory.makePerson()
branch = self.factory.makeAnyBranch(
- owner=owner, information_type=InformationType.USERDATA)
+ owner=owner, information_type=InformationType.USERDATA
+ )
with person_logged_in(owner):
self.assertRaises(
- SnapPrivateFeatureDisabled, create_initialized_view,
- branch, "+new-snap")
+ SnapPrivateFeatureDisabled,
+ create_initialized_view,
+ branch,
+ "+new-snap",
+ )
class BaseTestSnapView(BrowserTestCase):
@@ -162,23 +143,27 @@ class BaseTestSnapView(BrowserTestCase):
self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
self.useFixture(FakeLogger())
self.snap_store_client = FakeMethod()
- self.snap_store_client.requestPackageUploadPermission = (
- getUtility(ISnapStoreClient).requestPackageUploadPermission)
+ self.snap_store_client.requestPackageUploadPermission = getUtility(
+ ISnapStoreClient
+ ).requestPackageUploadPermission
self.useFixture(
- ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient))
+ ZopeUtilityFixture(self.snap_store_client, ISnapStoreClient)
+ )
self.person = self.factory.makePerson(
- name="test-person", displayname="Test Person")
+ name="test-person", displayname="Test Person"
+ )
class TestSnapAddView(BaseTestSnapView):
-
def setUp(self):
super().setUp()
self.distroseries = self.factory.makeUbuntuDistroSeries(
- version="13.10")
+ version="13.10"
+ )
with admin_logged_in():
self.snappyseries = self.factory.makeSnappySeries(
- preferred_distro_series=self.distroseries)
+ preferred_distro_series=self.distroseries
+ )
def setUpDistroSeries(self):
"""Set up a distroseries with some available processors."""
@@ -187,8 +172,10 @@ class TestSnapAddView(BaseTestSnapView):
for name in processor_names:
processor = getUtility(IProcessorSet).getByName(name)
self.factory.makeDistroArchSeries(
- distroseries=distroseries, architecturetag=name,
- processor=processor)
+ distroseries=distroseries,
+ architecturetag=name,
+ processor=processor,
+ )
with admin_logged_in():
self.factory.makeSnappySeries(preferred_distro_series=distroseries)
return distroseries
@@ -196,10 +183,14 @@ class TestSnapAddView(BaseTestSnapView):
def assertProcessorControls(self, processors_control, enabled, disabled):
matchers = [
MatchesStructure.byEquality(optionValue=name, disabled=False)
- for name in enabled]
- matchers.extend([
- MatchesStructure.byEquality(optionValue=name, disabled=True)
- for name in disabled])
+ for name in enabled
+ ]
+ matchers.extend(
+ [
+ MatchesStructure.byEquality(optionValue=name, disabled=True)
+ for name in disabled
+ ]
+ )
self.assertThat(processors_control.controls, MatchesSetwise(*matchers))
def test_initial_store_distro_series(self):
@@ -207,121 +198,152 @@ class TestSnapAddView(BaseTestSnapView):
# series for the latest snappy series.
self.useFixture(BranchHostingFixture(blob=b""))
lts = self.factory.makeUbuntuDistroSeries(
- version="16.04", status=SeriesStatus.CURRENT)
+ version="16.04", status=SeriesStatus.CURRENT
+ )
current = self.factory.makeUbuntuDistroSeries(
- version="16.10", status=SeriesStatus.CURRENT)
+ version="16.10", status=SeriesStatus.CURRENT
+ )
with admin_logged_in():
self.factory.makeSnappySeries(usable_distro_series=[lts, current])
newest = self.factory.makeSnappySeries(
preferred_distro_series=lts,
- usable_distro_series=[lts, current])
+ usable_distro_series=[lts, current],
+ )
branch = self.factory.makeAnyBranch()
with person_logged_in(self.person):
view = create_initialized_view(branch, "+new-snap")
self.assertThat(
view.initial_values["store_distro_series"],
MatchesStructure.byEquality(
- snappy_series=newest, distro_series=lts))
+ snappy_series=newest, distro_series=lts
+ ),
+ )
def test_initial_store_distro_series_can_infer_distro_series(self):
# If the latest snappy series supports inferring the distro series
# from snapcraft.yaml, then we default to that.
self.useFixture(BranchHostingFixture(blob=b""))
lts = self.factory.makeUbuntuDistroSeries(
- version="16.04", status=SeriesStatus.CURRENT)
+ version="16.04", status=SeriesStatus.CURRENT
+ )
with admin_logged_in():
self.factory.makeSnappySeries(usable_distro_series=[lts])
newest = self.factory.makeSnappySeries(
- preferred_distro_series=lts, can_infer_distro_series=True)
+ preferred_distro_series=lts, can_infer_distro_series=True
+ )
branch = self.factory.makeAnyBranch()
with person_logged_in(self.person):
view = create_initialized_view(branch, "+new-snap")
self.assertThat(
view.initial_values["store_distro_series"],
MatchesStructure(
- snappy_series=Equals(newest), distro_series=Is(None)))
+ snappy_series=Equals(newest), distro_series=Is(None)
+ ),
+ )
def test_create_new_snap_not_logged_in(self):
branch = self.factory.makeAnyBranch()
self.assertRaises(
- Unauthorized, self.getViewBrowser, branch, view_name="+new-snap",
- no_login=True)
+ Unauthorized,
+ self.getViewBrowser,
+ branch,
+ view_name="+new-snap",
+ no_login=True,
+ )
def test_create_new_snap_bzr(self):
self.useFixture(BranchHostingFixture(blob=b""))
branch = self.factory.makeAnyBranch()
source_display = branch.display_name
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "snap-name"
browser.getControl("Create snap package").click()
content = find_main_content(browser.contents)
self.assertEqual("snap-name", extract_text(content.h1))
self.assertThat(
- "Test Person", MatchesPickerText(content, "edit-owner"))
+ "Test Person", MatchesPickerText(content, "edit-owner")
+ )
self.assertThat(
- "Distribution series:\n%s\nEdit snap package" %
- self.distroseries.fullseriesname,
- MatchesTagText(content, "distro_series"))
+ "Distribution series:\n%s\nEdit snap package"
+ % self.distroseries.fullseriesname,
+ MatchesTagText(content, "distro_series"),
+ )
self.assertThat(
"Source:\n%s\nEdit snap package" % source_display,
- MatchesTagText(content, "source"))
+ MatchesTagText(content, "source"),
+ )
self.assertThat(
"Build source tarball:\nNo\nEdit snap package",
- MatchesTagText(content, "build_source_tarball"))
+ MatchesTagText(content, "build_source_tarball"),
+ )
self.assertThat(
"Build schedule:\n(?)\nBuilt on request\nEdit snap package\n",
- MatchesTagText(content, "auto_build"))
+ MatchesTagText(content, "auto_build"),
+ )
self.assertThat(
"Source archive for automatic builds:\n\nEdit snap package\n",
- MatchesTagText(content, "auto_build_archive"))
+ MatchesTagText(content, "auto_build_archive"),
+ )
self.assertThat(
"Pocket for automatic builds:\n\nEdit snap package",
- MatchesTagText(content, "auto_build_pocket"))
+ MatchesTagText(content, "auto_build_pocket"),
+ )
self.assertIsNone(find_tag_by_id(content, "auto_build_channels"))
self.assertThat(
"Builds of this snap package are not automatically uploaded to "
"the store.\nEdit snap package",
- MatchesTagText(content, "store_upload"))
+ MatchesTagText(content, "store_upload"),
+ )
def test_create_new_snap_git(self):
self.useFixture(GitHostingFixture(blob=b""))
[git_ref] = self.factory.makeGitRefs()
source_display = git_ref.display_name
browser = self.getViewBrowser(
- git_ref, view_name="+new-snap", user=self.person)
+ git_ref, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "snap-name"
browser.getControl("Create snap package").click()
content = find_main_content(browser.contents)
self.assertEqual("snap-name", extract_text(content.h1))
self.assertThat(
- "Test Person", MatchesPickerText(content, "edit-owner"))
+ "Test Person", MatchesPickerText(content, "edit-owner")
+ )
self.assertThat(
- "Distribution series:\n%s\nEdit snap package" %
- self.distroseries.fullseriesname,
- MatchesTagText(content, "distro_series"))
+ "Distribution series:\n%s\nEdit snap package"
+ % self.distroseries.fullseriesname,
+ MatchesTagText(content, "distro_series"),
+ )
self.assertThat(
"Source:\n%s\nEdit snap package" % source_display,
- MatchesTagText(content, "source"))
+ MatchesTagText(content, "source"),
+ )
self.assertThat(
"Build source tarball:\nNo\nEdit snap package",
- MatchesTagText(content, "build_source_tarball"))
+ MatchesTagText(content, "build_source_tarball"),
+ )
self.assertThat(
"Build schedule:\n(?)\nBuilt on request\nEdit snap package\n",
- MatchesTagText(content, "auto_build"))
+ MatchesTagText(content, "auto_build"),
+ )
self.assertThat(
"Source archive for automatic builds:\n\nEdit snap package\n",
- MatchesTagText(content, "auto_build_archive"))
+ MatchesTagText(content, "auto_build_archive"),
+ )
self.assertThat(
"Pocket for automatic builds:\n\nEdit snap package",
- MatchesTagText(content, "auto_build_pocket"))
+ MatchesTagText(content, "auto_build_pocket"),
+ )
self.assertIsNone(find_tag_by_id(content, "auto_build_channels"))
self.assertThat(
"Builds of this snap package are not automatically uploaded to "
"the store.\nEdit snap package",
- MatchesTagText(content, "store_upload"))
+ MatchesTagText(content, "store_upload"),
+ )
def test_create_new_snap_project(self):
self.useFixture(GitHostingFixture(blob=b""))
@@ -329,55 +351,68 @@ class TestSnapAddView(BaseTestSnapView):
[git_ref] = self.factory.makeGitRefs()
source_display = git_ref.display_name
browser = self.getViewBrowser(
- project, view_name="+new-snap", user=self.person)
+ project, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "snap-name"
browser.getControl(name="field.vcs").value = "GIT"
- browser.getControl(name="field.git_ref.repository").value = (
- git_ref.repository.shortened_path)
+ browser.getControl(
+ name="field.git_ref.repository"
+ ).value = git_ref.repository.shortened_path
browser.getControl(name="field.git_ref.path").value = git_ref.path
browser.getControl("Create snap package").click()
content = find_main_content(browser.contents)
self.assertEqual("snap-name", extract_text(content.h1))
self.assertThat(
- "Test Person", MatchesPickerText(content, "edit-owner"))
+ "Test Person", MatchesPickerText(content, "edit-owner")
+ )
self.assertThat(
- "Distribution series:\n%s\nEdit snap package" %
- self.distroseries.fullseriesname,
- MatchesTagText(content, "distro_series"))
+ "Distribution series:\n%s\nEdit snap package"
+ % self.distroseries.fullseriesname,
+ MatchesTagText(content, "distro_series"),
+ )
self.assertThat(
"Source:\n%s\nEdit snap package" % source_display,
- MatchesTagText(content, "source"))
+ MatchesTagText(content, "source"),
+ )
self.assertThat(
"Build source tarball:\nNo\nEdit snap package",
- MatchesTagText(content, "build_source_tarball"))
+ MatchesTagText(content, "build_source_tarball"),
+ )
self.assertThat(
"Build schedule:\n(?)\nBuilt on request\nEdit snap package\n",
- MatchesTagText(content, "auto_build"))
+ MatchesTagText(content, "auto_build"),
+ )
self.assertThat(
"Source archive for automatic builds:\n\nEdit snap package\n",
- MatchesTagText(content, "auto_build_archive"))
+ MatchesTagText(content, "auto_build_archive"),
+ )
self.assertThat(
"Pocket for automatic builds:\n\nEdit snap package",
- MatchesTagText(content, "auto_build_pocket"))
+ MatchesTagText(content, "auto_build_pocket"),
+ )
self.assertIsNone(find_tag_by_id(content, "auto_build_channels"))
self.assertThat(
"Builds of this snap package are not automatically uploaded to "
"the store.\nEdit snap package",
- MatchesTagText(content, "store_upload"))
+ MatchesTagText(content, "store_upload"),
+ )
def test_create_new_snap_users_teams_as_owner_options(self):
# Teams that the user is in are options for the snap package owner.
self.useFixture(BranchHostingFixture(blob=b""))
self.factory.makeTeam(
- name="test-team", displayname="Test Team", members=[self.person])
+ name="test-team", displayname="Test Team", members=[self.person]
+ )
branch = self.factory.makeAnyBranch()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
options = browser.getControl("Owner").displayOptions
self.assertEqual(
["Test Person (test-person)", "Test Team (test-team)"],
- sorted(str(option) for option in options))
+ sorted(str(option) for option in options),
+ )
def test_create_new_snap_public(self):
# Public owner implies public snap.
@@ -385,15 +420,16 @@ class TestSnapAddView(BaseTestSnapView):
branch = self.factory.makeAnyBranch()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "public-snap"
browser.getControl("Create snap package").click()
content = find_main_content(browser.contents)
self.assertEqual("public-snap", extract_text(content.h1))
self.assertEqual(
- 'This snap contains Public information',
- extract_text(find_tag_by_id(browser.contents, "privacy"))
+ "This snap contains Public information",
+ extract_text(find_tag_by_id(browser.contents, "privacy")),
)
def test_create_new_snap_private_link(self):
@@ -401,13 +437,14 @@ class TestSnapAddView(BaseTestSnapView):
# if the 'snap.allow_private' is enabled.
login_person(self.person)
branch = self.factory.makeAnyBranch(
- owner=self.person,
- information_type=InformationType.USERDATA)
+ owner=self.person, information_type=InformationType.USERDATA
+ )
with FeatureFixture({SNAP_PRIVATE_FEATURE_FLAG: ""}):
browser = self.getViewBrowser(branch, user=self.person)
self.assertRaises(
- LinkNotFoundError, browser.getLink, "Create snap package")
+ LinkNotFoundError, browser.getLink, "Create snap package"
+ )
with FeatureFixture(SNAP_TESTING_FLAGS):
browser = self.getViewBrowser(branch, user=self.person)
browser.getLink("Create snap package")
@@ -416,14 +453,17 @@ class TestSnapAddView(BaseTestSnapView):
# Creates a private snap for a private project.
login_person(self.person)
self.factory.makeProduct(
- name='private-project',
- owner=self.person, registrant=self.person,
+ name="private-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
[git_ref] = self.factory.makeGitRefs()
browser = self.getViewBrowser(
- git_ref, view_name="+new-snap", user=self.person)
+ git_ref, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "private-snap"
browser.getControl(name="field.information_type").value = "PROPRIETARY"
browser.getControl(name="field.project").value = "private-project"
@@ -432,12 +472,12 @@ class TestSnapAddView(BaseTestSnapView):
content = find_main_content(browser.contents)
self.assertEqual("private-snap", extract_text(content.h1))
self.assertEqual(
- 'This snap contains Private information',
- extract_text(find_tag_by_id(browser.contents, "privacy")))
+ "This snap contains Private information",
+ extract_text(find_tag_by_id(browser.contents, "privacy")),
+ )
login_admin()
- snap = getUtility(ISnapSet).getByName(self.person, 'private-snap')
- self.assertEqual(
- InformationType.PROPRIETARY, snap.information_type)
+ snap = getUtility(ISnapSet).getByName(self.person, "private-snap")
+ self.assertEqual(InformationType.PROPRIETARY, snap.information_type)
def test_create_new_snap_private_without_project_fails(self):
# It should not not be possible to create a private snap with
@@ -446,7 +486,8 @@ class TestSnapAddView(BaseTestSnapView):
[git_ref] = self.factory.makeGitRefs()
browser = self.getViewBrowser(
- git_ref, view_name="+new-snap", user=self.person)
+ git_ref, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "private-snap"
browser.getControl(name="field.information_type").value = "PROPRIETARY"
browser.getControl("Create snap package").click()
@@ -456,13 +497,13 @@ class TestSnapAddView(BaseTestSnapView):
messages = find_tags_by_class(browser.contents, "message")
self.assertEqual(2, len(messages))
top_msg, field_msg = messages
+ self.assertEqual("There is 1 error.", extract_text(top_msg))
self.assertEqual(
- 'There is 1 error.', extract_text(top_msg))
- self.assertEqual(
- 'Private snap recipes must be associated with a project.',
- extract_text(field_msg))
+ "Private snap recipes must be associated with a project.",
+ extract_text(field_msg),
+ )
login_admin()
- snap = IStore(Snap).find(Snap, Snap.name == 'private-snap').one()
+ snap = IStore(Snap).find(Snap, Snap.name == "private-snap").one()
self.assertIsNone(snap)
def test_create_new_snap_private_with_invalid_information_type_fails(self):
@@ -472,14 +513,17 @@ class TestSnapAddView(BaseTestSnapView):
# The project is proprietary, with branch policy beign proprietary
# too. We can only create proprietary snaps.
self.factory.makeProduct(
- name='private-project',
- owner=self.person, registrant=self.person,
+ name="private-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
[git_ref] = self.factory.makeGitRefs()
browser = self.getViewBrowser(
- git_ref, view_name="+new-snap", user=self.person)
+ git_ref, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "private-snap"
browser.getControl(name="field.information_type").value = "PUBLIC"
browser.getControl(name="field.project").value = "private-project"
@@ -490,14 +534,14 @@ class TestSnapAddView(BaseTestSnapView):
messages = find_tags_by_class(browser.contents, "message")
self.assertEqual(2, len(messages))
top_msg, field_msg = messages
- self.assertEqual(
- 'There is 1 error.', extract_text(top_msg))
+ self.assertEqual("There is 1 error.", extract_text(top_msg))
expected_msg = (
- 'Project private-project only accepts the following information '
- 'types: Proprietary.')
+ "Project private-project only accepts the following information "
+ "types: Proprietary."
+ )
self.assertEqual(expected_msg, extract_text(field_msg))
login_admin()
- snap = IStore(Snap).find(Snap, Snap.name == 'private-snap').one()
+ snap = IStore(Snap).find(Snap, Snap.name == "private-snap").one()
self.assertIsNone(snap)
def test_create_new_snap_build_source_tarball(self):
@@ -505,7 +549,8 @@ class TestSnapAddView(BaseTestSnapView):
self.useFixture(BranchHostingFixture(blob=b""))
branch = self.factory.makeAnyBranch()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "snap-name"
browser.getControl("Build source tarball").selected = True
browser.getControl("Create snap package").click()
@@ -513,7 +558,8 @@ class TestSnapAddView(BaseTestSnapView):
content = find_main_content(browser.contents)
self.assertThat(
"Build source tarball:\nYes\nEdit snap package",
- MatchesTagText(content, "build_source_tarball"))
+ MatchesTagText(content, "build_source_tarball"),
+ )
def test_create_new_snap_auto_build(self):
# Creating a new snap and asking for it to be automatically built
@@ -522,40 +568,51 @@ class TestSnapAddView(BaseTestSnapView):
branch = self.factory.makeAnyBranch()
archive = self.factory.makeArchive()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "snap-name"
browser.getControl(
- "Automatically build when branch changes").selected = True
+ "Automatically build when branch changes"
+ ).selected = True
browser.getControl("PPA").click()
- browser.getControl(name="field.auto_build_archive.ppa").value = (
- archive.reference)
+ browser.getControl(
+ name="field.auto_build_archive.ppa"
+ ).value = archive.reference
browser.getControl("Pocket for automatic builds").value = ["SECURITY"]
browser.getControl(
- name="field.auto_build_channels.core").value = "stable"
+ name="field.auto_build_channels.core"
+ ).value = "stable"
browser.getControl(
- name="field.auto_build_channels.core18").value = "beta"
+ name="field.auto_build_channels.core18"
+ ).value = "beta"
browser.getControl(
- name="field.auto_build_channels.core20").value = "edge/feature"
+ name="field.auto_build_channels.core20"
+ ).value = "edge/feature"
browser.getControl(
- name="field.auto_build_channels.snapcraft").value = "edge"
+ name="field.auto_build_channels.snapcraft"
+ ).value = "edge"
browser.getControl("Create snap package").click()
content = find_main_content(browser.contents)
self.assertThat(
"Build schedule:\n(?)\nBuilt automatically\nEdit snap package\n",
- MatchesTagText(content, "auto_build"))
+ MatchesTagText(content, "auto_build"),
+ )
self.assertThat(
- "Source archive for automatic builds:\n%s\nEdit snap package\n" %
- archive.displayname,
- MatchesTagText(content, "auto_build_archive"))
+ "Source archive for automatic builds:\n%s\nEdit snap package\n"
+ % archive.displayname,
+ MatchesTagText(content, "auto_build_archive"),
+ )
self.assertThat(
"Pocket for automatic builds:\nSecurity\nEdit snap package",
- MatchesTagText(content, "auto_build_pocket"))
+ MatchesTagText(content, "auto_build_pocket"),
+ )
self.assertThat(
"Source snap channels for automatic builds:\nEdit snap package\n"
"core\nstable\ncore18\nbeta\n"
"core20\nedge/feature\nsnapcraft\nedge\n",
- MatchesTagText(content, "auto_build_channels"))
+ MatchesTagText(content, "auto_build_channels"),
+ )
@responses.activate
def test_create_new_snap_store_upload(self):
@@ -568,51 +625,70 @@ class TestSnapAddView(BaseTestSnapView):
browser = self.getNonRedirectingBrowser(url=view_url, user=self.person)
browser.getControl(name="field.name").value = "snap-name"
browser.getControl("Automatically upload to store").selected = True
- browser.getControl("Registered store package name").value = (
- "store-name")
+ browser.getControl(
+ "Registered store package name"
+ ).value = "store-name"
self.assertFalse(browser.getControl("Stable").selected)
browser.getControl(name="field.store_channels.track").value = "track"
browser.getControl("Edge").selected = True
root_macaroon = Macaroon()
root_macaroon.add_third_party_caveat(
- urlsplit(config.launchpad.openid_provider_root).netloc, "",
- "dummy")
+ urlsplit(config.launchpad.openid_provider_root).netloc, "", "dummy"
+ )
root_macaroon_raw = root_macaroon.serialize()
self.pushConfig("snappy", store_url="http://sca.example/")
responses.add(
- "POST", "http://sca.example/dev/api/acl/",
- json={"macaroon": root_macaroon_raw})
+ "POST",
+ "http://sca.example/dev/api/acl/",
+ json={"macaroon": root_macaroon_raw},
+ )
browser.getControl("Create snap package").click()
login_person(self.person)
snap = getUtility(ISnapSet).getByName(self.person, "snap-name")
- self.assertThat(snap, MatchesStructure.byEquality(
- owner=self.person, distro_series=self.distroseries,
- name="snap-name", source=branch, store_upload=True,
- store_series=self.snappyseries, store_name="store-name",
- store_secrets={"root": root_macaroon_raw},
- store_channels=["track/edge"]))
+ self.assertThat(
+ snap,
+ MatchesStructure.byEquality(
+ owner=self.person,
+ distro_series=self.distroseries,
+ name="snap-name",
+ source=branch,
+ store_upload=True,
+ store_series=self.snappyseries,
+ store_name="store-name",
+ store_secrets={"root": root_macaroon_raw},
+ store_channels=["track/edge"],
+ ),
+ )
[call] = responses.calls
- self.assertThat(call.request, MatchesStructure.byEquality(
- url="http://sca.example/dev/api/acl/", method="POST"))
+ self.assertThat(
+ call.request,
+ MatchesStructure.byEquality(
+ url="http://sca.example/dev/api/acl/", method="POST"
+ ),
+ )
expected_body = {
- "packages": [{
- "name": "store-name",
- "series": self.snappyseries.name,
- }],
+ "packages": [
+ {
+ "name": "store-name",
+ "series": self.snappyseries.name,
+ }
+ ],
"permissions": ["package_upload"],
- }
+ }
self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8")))
+ expected_body, json.loads(call.request.body.decode("UTF-8"))
+ )
self.assertEqual(303, int(browser.headers["Status"].split(" ", 1)[0]))
parsed_location = urlsplit(browser.headers["Location"])
self.assertEqual(
urlsplit(canonical_url(snap) + "/+authorize/+login")[:3],
- parsed_location[:3])
+ parsed_location[:3],
+ )
expected_args = {
"discharge_macaroon_action": ["field.actions.complete"],
"discharge_macaroon_field": ["field.discharge_macaroon"],
"macaroon_caveat_id": ["dummy"],
- }
+ }
self.assertEqual(expected_args, parse_qs(parsed_location[3]))
def test_create_new_snap_display_processors(self):
@@ -620,11 +696,13 @@ class TestSnapAddView(BaseTestSnapView):
branch = self.factory.makeAnyBranch()
self.setUpDistroSeries()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
processors = browser.getControl(name="field.processors")
self.assertContentEqual(
["Intel 386 (386)", "AMD 64bit (amd64)", "HPPA Processor (hppa)"],
- [extract_text(option) for option in processors.displayOptions])
+ [extract_text(option) for option in processors.displayOptions],
+ )
self.assertContentEqual(["386", "amd64", "hppa"], processors.options)
self.assertContentEqual(["386", "amd64", "hppa"], processors.value)
@@ -635,22 +713,28 @@ class TestSnapAddView(BaseTestSnapView):
branch = self.factory.makeAnyBranch()
distroseries = self.setUpDistroSeries()
proc_armhf = self.factory.makeProcessor(
- name="armhf", restricted=True, build_by_default=False)
+ name="armhf", restricted=True, build_by_default=False
+ )
self.factory.makeDistroArchSeries(
- distroseries=distroseries, architecturetag="armhf",
- processor=proc_armhf)
+ distroseries=distroseries,
+ architecturetag="armhf",
+ processor=proc_armhf,
+ )
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
processors = browser.getControl(name="field.processors")
self.assertProcessorControls(
- processors, ["386", "amd64", "hppa"], ["armhf"])
+ processors, ["386", "amd64", "hppa"], ["armhf"]
+ )
def test_create_new_snap_processors(self):
self.useFixture(BranchHostingFixture(blob=b""))
branch = self.factory.makeAnyBranch()
self.setUpDistroSeries()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
processors = browser.getControl(name="field.processors")
processors.value = ["386", "amd64"]
browser.getControl(name="field.name").value = "snap-name"
@@ -658,7 +742,8 @@ class TestSnapAddView(BaseTestSnapView):
login_person(self.person)
snap = getUtility(ISnapSet).getByName(self.person, "snap-name")
self.assertContentEqual(
- ["386", "amd64"], [proc.name for proc in snap.processors])
+ ["386", "amd64"], [proc.name for proc in snap.processors]
+ )
def test_create_new_snap_infer_distro_series(self):
self.useFixture(BranchHostingFixture(blob=b""))
@@ -666,14 +751,17 @@ class TestSnapAddView(BaseTestSnapView):
self.snappyseries.can_infer_distro_series = True
branch = self.factory.makeAnyBranch()
browser = self.getViewBrowser(
- branch, view_name="+new-snap", user=self.person)
+ branch, view_name="+new-snap", user=self.person
+ )
browser.getControl(name="field.name").value = "snap-name"
self.assertEqual(
[self.snappyseries.name],
- browser.getControl(name="field.store_distro_series").value)
+ browser.getControl(name="field.store_distro_series").value,
+ )
self.assertEqual(
self.snappyseries.name,
- browser.getControl(name="field.store_distro_series").options[0])
+ browser.getControl(name="field.store_distro_series").options[0],
+ )
browser.getControl("Create snap package").click()
content = find_main_content(browser.contents)
@@ -681,60 +769,68 @@ class TestSnapAddView(BaseTestSnapView):
self.assertIsNone(find_tag_by_id(content, "distro_series"))
def test_initial_name_extraction_bzr_success(self):
- self.useFixture(BranchHostingFixture(
- file_list={"snapcraft.yaml": "file-id"}, blob=b"name: test-snap"))
+ self.useFixture(
+ BranchHostingFixture(
+ file_list={"snapcraft.yaml": "file-id"},
+ blob=b"name: test-snap",
+ )
+ )
branch = self.factory.makeBranch()
view = create_initialized_view(branch, "+new-snap")
initial_values = view.initial_values
- self.assertIn('store_name', initial_values)
- self.assertEqual('test-snap', initial_values['store_name'])
+ self.assertIn("store_name", initial_values)
+ self.assertEqual("test-snap", initial_values["store_name"])
def test_initial_name_extraction_bzr_error(self):
self.useFixture(BranchHostingFixture()).getInventory = FakeMethod(
- failure=BranchHostingFault)
+ failure=BranchHostingFault
+ )
branch = self.factory.makeBranch()
view = create_initialized_view(branch, "+new-snap")
initial_values = view.initial_values
- self.assertIn('store_name', initial_values)
- self.assertIsNone(initial_values['store_name'])
+ self.assertIn("store_name", initial_values)
+ self.assertIsNone(initial_values["store_name"])
def test_initial_name_extraction_bzr_no_name(self):
- self.useFixture(BranchHostingFixture(
- file_list={"snapcraft.yaml": "file-id"}, blob=b"some: nonsense"))
+ self.useFixture(
+ BranchHostingFixture(
+ file_list={"snapcraft.yaml": "file-id"}, blob=b"some: nonsense"
+ )
+ )
branch = self.factory.makeBranch()
view = create_initialized_view(branch, "+new-snap")
initial_values = view.initial_values
- self.assertIn('store_name', initial_values)
- self.assertIsNone(initial_values['store_name'])
+ self.assertIn("store_name", initial_values)
+ self.assertIsNone(initial_values["store_name"])
def test_initial_name_extraction_git_success(self):
self.useFixture(GitHostingFixture(blob=b"name: test-snap"))
[git_ref] = self.factory.makeGitRefs()
view = create_initialized_view(git_ref, "+new-snap")
initial_values = view.initial_values
- self.assertIn('store_name', initial_values)
- self.assertEqual('test-snap', initial_values['store_name'])
+ self.assertIn("store_name", initial_values)
+ self.assertEqual("test-snap", initial_values["store_name"])
def test_initial_name_extraction_git_error(self):
self.useFixture(GitHostingFixture()).getBlob = FakeMethod(
- failure=GitRepositoryScanFault)
+ failure=GitRepositoryScanFault
+ )
[git_ref] = self.factory.makeGitRefs()
view = create_initialized_view(git_ref, "+new-snap")
initial_values = view.initial_values
- self.assertIn('store_name', initial_values)
- self.assertIsNone(initial_values['store_name'])
+ self.assertIn("store_name", initial_values)
+ self.assertIsNone(initial_values["store_name"])
def test_initial_name_extraction_git_no_name(self):
self.useFixture(GitHostingFixture(blob=b"some: nonsense"))
[git_ref] = self.factory.makeGitRefs()
view = create_initialized_view(git_ref, "+new-snap")
initial_values = view.initial_values
- self.assertIn('store_name', initial_values)
- self.assertIsNone(initial_values['store_name'])
+ self.assertIn("store_name", initial_values)
+ self.assertIsNone(initial_values["store_name"])
class TestSnapAdminView(BaseTestSnapView):
-
def test_unauthorized(self):
# A non-admin user cannot administer a snap package.
login_person(self.person)
@@ -742,16 +838,21 @@ class TestSnapAdminView(BaseTestSnapView):
snap_url = canonical_url(snap)
browser = self.getViewBrowser(snap, user=self.person)
self.assertRaises(
- LinkNotFoundError, browser.getLink, "Administer snap package")
+ LinkNotFoundError, browser.getLink, "Administer snap package"
+ )
self.assertRaises(
- Unauthorized, self.getUserBrowser, snap_url + "/+admin",
- user=self.person)
+ Unauthorized,
+ self.getUserBrowser,
+ snap_url + "/+admin",
+ user=self.person,
+ )
def test_admin_snap(self):
# Admins can change require_virtualized, privacy, and allow_internet.
login("admin@xxxxxxxxxxxxx")
admin = self.factory.makePerson(
- member_of=[getUtility(ILaunchpadCelebrities).admin])
+ member_of=[getUtility(ILaunchpadCelebrities).admin]
+ )
login_person(self.person)
project = self.factory.makeProduct(name="my-project")
with person_logged_in(project.owner):
@@ -763,11 +864,12 @@ class TestSnapAdminView(BaseTestSnapView):
self.assertTrue(snap.allow_internet)
self.factory.makeAccessPolicy(
- pillar=project, type=InformationType.PRIVATESECURITY)
+ pillar=project, type=InformationType.PRIVATESECURITY
+ )
private = InformationType.PRIVATESECURITY.name
browser = self.getViewBrowser(snap, user=admin)
browser.getLink("Administer snap package").click()
- browser.getControl(name='field.project').value = "my-project"
+ browser.getControl(name="field.project").value = "my-project"
browser.getControl("Require virtualized builders").selected = False
browser.getControl(name="field.information_type").value = private
browser.getControl("Allow external network access").selected = False
@@ -784,51 +886,63 @@ class TestSnapAdminView(BaseTestSnapView):
login_person(self.person)
snap = self.factory.makeSnap(registrant=self.person)
admin = self.factory.makePerson(
- member_of=[getUtility(ILaunchpadCelebrities).admin])
+ member_of=[getUtility(ILaunchpadCelebrities).admin]
+ )
private = InformationType.PRIVATESECURITY.name
browser = self.getViewBrowser(snap, user=admin)
browser.getLink("Administer snap package").click()
- browser.getControl(name='field.project').value = None
+ browser.getControl(name="field.project").value = None
browser.getControl(name="field.information_type").value = private
browser.getControl("Update snap package").click()
self.assertEqual(
- 'Private snap recipes must be associated with a project.',
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ "Private snap recipes must be associated with a project.",
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_admin_snap_privacy_mismatch(self):
# Cannot make snap public if it still contains private information.
login_person(self.person)
team = self.factory.makeTeam(
membership_policy=TeamMembershipPolicy.MODERATED,
- owner=self.person, visibility=PersonVisibility.PRIVATE)
+ owner=self.person,
+ visibility=PersonVisibility.PRIVATE,
+ )
project = self.factory.makeProduct(
information_type=InformationType.PUBLIC,
- branch_sharing_policy=BranchSharingPolicy.PUBLIC_OR_PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PUBLIC_OR_PROPRIETARY,
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=team, project=project,
- information_type=InformationType.PRIVATESECURITY)
+ registrant=self.person,
+ owner=team,
+ project=project,
+ information_type=InformationType.PRIVATESECURITY,
+ )
# Note that only LP admins or, in this case, commercial_admins
# can reach this snap because it's owned by a private team.
admin = self.factory.makePerson(
- member_of=[getUtility(ILaunchpadCelebrities).admin])
+ member_of=[getUtility(ILaunchpadCelebrities).admin]
+ )
public = InformationType.PUBLIC.name
browser = self.getViewBrowser(snap, user=admin)
browser.getLink("Administer snap package").click()
browser.getControl(name="field.information_type").value = public
browser.getControl("Update snap package").click()
self.assertEqual(
- 'A public snap cannot have a private owner.',
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ "A public snap cannot have a private owner.",
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_admin_snap_sets_date_last_modified(self):
# Administering a snap package sets the date_last_modified property.
login("admin@xxxxxxxxxxxxx")
ppa_admin = self.factory.makePerson(
- member_of=[getUtility(ILaunchpadCelebrities).ppa_admin])
+ member_of=[getUtility(ILaunchpadCelebrities).ppa_admin]
+ )
login_person(self.person)
date_created = datetime(2000, 1, 1, tzinfo=pytz.UTC)
snap = self.factory.makeSnap(
- registrant=self.person, date_created=date_created)
+ registrant=self.person, date_created=date_created
+ )
login_person(ppa_admin)
view = SnapAdminView(snap, LaunchpadTestRequest())
view.initialize()
@@ -837,27 +951,33 @@ class TestSnapAdminView(BaseTestSnapView):
class TestSnapEditView(BaseTestSnapView):
-
def setUp(self):
super().setUp()
self.distroseries = self.factory.makeUbuntuDistroSeries(
- version="13.10")
+ version="13.10"
+ )
with admin_logged_in():
self.snappyseries = self.factory.makeSnappySeries(
- usable_distro_series=[self.distroseries])
+ usable_distro_series=[self.distroseries]
+ )
def test_edit_snap(self):
old_series = self.factory.makeUbuntuDistroSeries()
old_branch = self.factory.makeAnyBranch()
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, distroseries=old_series,
- branch=old_branch)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=old_series,
+ branch=old_branch,
+ )
self.factory.makeTeam(
- name="new-team", displayname="New Team", members=[self.person])
+ name="new-team", displayname="New Team", members=[self.person]
+ )
new_series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
new_snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[new_series])
+ usable_distro_series=[new_series]
+ )
[new_git_ref] = self.factory.makeGitRefs()
archive = self.factory.makeArchive()
@@ -866,160 +986,200 @@ class TestSnapEditView(BaseTestSnapView):
browser.getControl("Owner").value = ["new-team"]
browser.getControl(name="field.name").value = "new-name"
browser.getControl(name="field.store_distro_series").value = [
- "ubuntu/%s/%s" % (new_series.name, new_snappy_series.name)]
+ "ubuntu/%s/%s" % (new_series.name, new_snappy_series.name)
+ ]
browser.getControl("Git", index=0).click()
- browser.getControl(name="field.git_ref.repository").value = (
- new_git_ref.repository.identity)
+ browser.getControl(
+ name="field.git_ref.repository"
+ ).value = new_git_ref.repository.identity
browser.getControl(name="field.git_ref.path").value = new_git_ref.path
browser.getControl("Build source tarball").selected = True
browser.getControl(
- "Automatically build when branch changes").selected = True
+ "Automatically build when branch changes"
+ ).selected = True
browser.getControl("PPA").click()
- browser.getControl(name="field.auto_build_archive.ppa").value = (
- archive.reference)
+ browser.getControl(
+ name="field.auto_build_archive.ppa"
+ ).value = archive.reference
browser.getControl("Pocket for automatic builds").value = ["SECURITY"]
browser.getControl(
- name="field.auto_build_channels.snapcraft").value = "edge"
+ name="field.auto_build_channels.snapcraft"
+ ).value = "edge"
browser.getControl("Update snap package").click()
content = find_main_content(browser.contents)
self.assertEqual("new-name", extract_text(content.h1))
self.assertThat("New Team", MatchesPickerText(content, "edit-owner"))
self.assertThat(
- "Distribution series:\n%s\nEdit snap package" %
- new_series.fullseriesname,
- MatchesTagText(content, "distro_series"))
+ "Distribution series:\n%s\nEdit snap package"
+ % new_series.fullseriesname,
+ MatchesTagText(content, "distro_series"),
+ )
self.assertThat(
"Source:\n%s\nEdit snap package" % new_git_ref.display_name,
- MatchesTagText(content, "source"))
+ MatchesTagText(content, "source"),
+ )
self.assertThat(
"Build source tarball:\nYes\nEdit snap package",
- MatchesTagText(content, "build_source_tarball"))
+ MatchesTagText(content, "build_source_tarball"),
+ )
self.assertThat(
"Build schedule:\n(?)\nBuilt automatically\nEdit snap package\n",
- MatchesTagText(content, "auto_build"))
+ MatchesTagText(content, "auto_build"),
+ )
self.assertThat(
- "Source archive for automatic builds:\n%s\nEdit snap package\n" %
- archive.displayname,
- MatchesTagText(content, "auto_build_archive"))
+ "Source archive for automatic builds:\n%s\nEdit snap package\n"
+ % archive.displayname,
+ MatchesTagText(content, "auto_build_archive"),
+ )
self.assertThat(
"Pocket for automatic builds:\nSecurity\nEdit snap package",
- MatchesTagText(content, "auto_build_pocket"))
+ MatchesTagText(content, "auto_build_pocket"),
+ )
self.assertThat(
"Source snap channels for automatic builds:\nEdit snap package\n"
"snapcraft\nedge",
- MatchesTagText(content, "auto_build_channels"))
+ MatchesTagText(content, "auto_build_channels"),
+ )
self.assertThat(
"Builds of this snap package are not automatically uploaded to "
"the store.\nEdit snap package",
- MatchesTagText(content, "store_upload"))
+ MatchesTagText(content, "store_upload"),
+ )
def test_edit_snap_built_for_older_store_series(self):
distro_series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
usable_distro_series=[distro_series],
- status=SeriesStatus.SUPPORTED)
+ status=SeriesStatus.SUPPORTED,
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
+ registrant=self.person,
+ owner=self.person,
distroseries=distro_series,
store_series=snappy_series,
- branch=self.factory.makeAnyBranch())
+ branch=self.factory.makeAnyBranch(),
+ )
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
- browser.getControl(name="field.store_distro_series").value = (
- "ubuntu/%s/%s" % (distro_series.name, snappy_series.name))
+ browser.getControl(
+ name="field.store_distro_series"
+ ).value = "ubuntu/%s/%s" % (distro_series.name, snappy_series.name)
browser.getControl("Update snap package").click()
self.assertEqual([], find_tags_by_class(browser.contents, "message"))
login_person(self.person)
- self.assertThat(snap, MatchesStructure.byEquality(
- distro_series=distro_series,
- store_series=snappy_series))
+ self.assertThat(
+ snap,
+ MatchesStructure.byEquality(
+ distro_series=distro_series, store_series=snappy_series
+ ),
+ )
def test_edit_snap_built_for_distro_series_None(self):
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- status=SeriesStatus.CURRENT)
+ status=SeriesStatus.CURRENT
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
+ registrant=self.person,
+ owner=self.person,
distroseries=None,
- store_series=snappy_series)
+ store_series=snappy_series,
+ )
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
- browser.getControl(
- name="field.store_distro_series").value = (
- browser.getControl(
- name="field.store_distro_series"
- ).options[0].strip())
+ browser.getControl(name="field.store_distro_series").value = (
+ browser.getControl(name="field.store_distro_series")
+ .options[0]
+ .strip()
+ )
browser.getControl("Update snap package").click()
self.assertEqual([], find_tags_by_class(browser.contents, "message"))
login_person(self.person)
- self.assertThat(snap, MatchesStructure(
- distro_series=Is(None),
- store_series=Equals(snappy_series)))
+ self.assertThat(
+ snap,
+ MatchesStructure(
+ distro_series=Is(None), store_series=Equals(snappy_series)
+ ),
+ )
def test_edit_snap_built_for_snappy_series_None(self):
distro_series = self.factory.makeUbuntuDistroSeries()
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
+ registrant=self.person,
+ owner=self.person,
distroseries=distro_series,
- store_series=None)
+ store_series=None,
+ )
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
self.assertIn(
"ubuntu/%s" % distro_series.name,
- browser.getControl(name="field.store_distro_series").options)
- browser.getControl(
- name="field.store_distro_series").value = (
- "ubuntu/%s" % distro_series.name)
+ browser.getControl(name="field.store_distro_series").options,
+ )
+ browser.getControl(name="field.store_distro_series").value = (
+ "ubuntu/%s" % distro_series.name
+ )
browser.getControl("Update snap package").click()
self.assertEqual([], find_tags_by_class(browser.contents, "message"))
login_person(self.person)
- self.assertThat(snap, MatchesStructure(
- distro_series=Equals(distro_series),
- store_series=Is(None)))
+ self.assertThat(
+ snap,
+ MatchesStructure(
+ distro_series=Equals(distro_series), store_series=Is(None)
+ ),
+ )
def test_edit_snap_built_for_distro_snappy_series_None(self):
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
+ registrant=self.person,
+ owner=self.person,
distroseries=None,
- store_series=None)
+ store_series=None,
+ )
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
self.assertIn(
"(unset)",
- browser.getControl(name="field.store_distro_series").options)
- browser.getControl(
- name="field.store_distro_series").value = '(unset)'
+ browser.getControl(name="field.store_distro_series").options,
+ )
+ browser.getControl(name="field.store_distro_series").value = "(unset)"
browser.getControl("Update snap package").click()
self.assertEqual([], find_tags_by_class(browser.contents, "message"))
login_person(self.person)
- self.assertThat(snap, MatchesStructure(
- distro_series=Is(None),
- store_series=Is(None)))
+ self.assertThat(
+ snap,
+ MatchesStructure(distro_series=Is(None), store_series=Is(None)),
+ )
def test_edit_snap_sets_date_last_modified(self):
# Editing a snap package sets the date_last_modified property.
date_created = datetime(2000, 1, 1, tzinfo=pytz.UTC)
snap = self.factory.makeSnap(
- registrant=self.person, date_created=date_created)
+ registrant=self.person, date_created=date_created
+ )
with person_logged_in(self.person):
view = SnapEditView(snap, LaunchpadTestRequest())
view.initialize()
- view.request_action.success({
- "owner": snap.owner,
- "name": "changed",
- "distro_series": snap.distro_series,
- })
+ view.request_action.success(
+ {
+ "owner": snap.owner,
+ "name": "changed",
+ "distro_series": snap.distro_series,
+ }
+ )
self.assertSqlAttributeEqualsDate(snap, "date_last_modified", UTC_NOW)
def test_edit_snap_already_exists(self):
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, name="one")
+ registrant=self.person, owner=self.person, name="one"
+ )
self.factory.makeSnap(
- registrant=self.person, owner=self.person, name="two")
+ registrant=self.person, owner=self.person, name="two"
+ )
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
browser.getControl(name="field.name").value = "two"
@@ -1027,28 +1187,38 @@ class TestSnapEditView(BaseTestSnapView):
self.assertEqual(
"There is already a snap package owned by Test Person with this "
"name.",
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_edit_snap_project_and_info_type(self):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
initial_project = self.factory.makeProduct(
- name='initial-project',
- owner=self.person, registrant=self.person,
+ name="initial-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PUBLIC,
- branch_sharing_policy=BranchSharingPolicy.PUBLIC_OR_PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PUBLIC_OR_PROPRIETARY,
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, project=initial_project,
- distroseries=series, store_series=snappy_series,
- information_type=InformationType.PUBLIC)
+ registrant=self.person,
+ owner=self.person,
+ project=initial_project,
+ distroseries=series,
+ store_series=snappy_series,
+ information_type=InformationType.PUBLIC,
+ )
final_project = self.factory.makeProduct(
- name='final-project',
- owner=self.person, registrant=self.person,
+ name="final-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
browser.getControl(name="field.project").value = "final-project"
@@ -1064,68 +1234,86 @@ class TestSnapEditView(BaseTestSnapView):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
private_project = self.factory.makeProduct(
- name='private-project',
- owner=self.person, registrant=self.person,
+ name="private-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
snap = self.factory.makeSnap(
- name='foo-snap', registrant=self.person, owner=self.person,
- distroseries=series, store_series=snappy_series,
+ name="foo-snap",
+ registrant=self.person,
+ owner=self.person,
+ distroseries=series,
+ store_series=snappy_series,
information_type=InformationType.PROPRIETARY,
- project=private_project)
+ project=private_project,
+ )
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
- browser.getControl(name="field.project").value = ''
- browser.getControl(name="field.information_type").value = (
- "PROPRIETARY")
+ browser.getControl(name="field.project").value = ""
+ browser.getControl(name="field.information_type").value = "PROPRIETARY"
browser.getControl("Update snap package").click()
messages = find_tags_by_class(browser.contents, "message")
self.assertEqual(2, len(messages))
top_msg, field_msg = messages
+ self.assertEqual("There is 1 error.", extract_text(top_msg))
self.assertEqual(
- 'There is 1 error.', extract_text(top_msg))
- self.assertEqual(
- 'Private snap recipes must be associated with a project.',
- extract_text(field_msg))
+ "Private snap recipes must be associated with a project.",
+ extract_text(field_msg),
+ )
def test_edit_snap_private_information_type_matches_project(self):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
private_project = self.factory.makeProduct(
- name='private-project',
- owner=self.person, registrant=self.person,
+ name="private-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
snap = self.factory.makeSnap(
- name='foo-snap', registrant=self.person, owner=self.person,
- distroseries=series, store_series=snappy_series,
+ name="foo-snap",
+ registrant=self.person,
+ owner=self.person,
+ distroseries=series,
+ store_series=snappy_series,
information_type=InformationType.PROPRIETARY,
- project=private_project)
+ project=private_project,
+ )
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
# Make sure we are only showing valid information type options:
info_type_selector = browser.getControl(name="field.information_type")
- self.assertEqual(['PROPRIETARY'], info_type_selector.options)
+ self.assertEqual(["PROPRIETARY"], info_type_selector.options)
def test_edit_public_snap_private_owner(self):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, distroseries=series,
- store_series=snappy_series)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=series,
+ store_series=snappy_series,
+ )
private_team = self.factory.makeTeam(
- owner=self.person, visibility=PersonVisibility.PRIVATE)
+ owner=self.person, visibility=PersonVisibility.PRIVATE
+ )
private_team_name = private_team.name
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
@@ -1133,32 +1321,40 @@ class TestSnapEditView(BaseTestSnapView):
browser.getControl("Update snap package").click()
self.assertEqual(
"A public snap cannot have a private owner.",
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_edit_public_snap_make_private_in_one_go(self):
# Move a public snap to a private owner and mark it private in one go
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
private_project = self.factory.makeProduct(
- name='private-project',
- owner=self.person, registrant=self.person,
+ name="private-project",
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, distroseries=series,
- store_series=snappy_series, project=private_project)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=series,
+ store_series=snappy_series,
+ project=private_project,
+ )
private_team = self.factory.makeTeam(
- owner=self.person, visibility=PersonVisibility.PRIVATE)
+ owner=self.person, visibility=PersonVisibility.PRIVATE
+ )
private_team_name = private_team.name
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
browser.getControl("Owner").value = [private_team_name]
- browser.getControl(name="field.information_type").value = (
- "PROPRIETARY")
+ browser.getControl(name="field.information_type").value = "PROPRIETARY"
browser.getControl("Update snap package").click()
login_admin()
@@ -1168,14 +1364,19 @@ class TestSnapEditView(BaseTestSnapView):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, distroseries=series,
- branch=self.factory.makeAnyBranch(), store_series=snappy_series)
- private_branch = self.factory.makeAnyBranch(
+ registrant=self.person,
owner=self.person,
- information_type=InformationType.PRIVATESECURITY)
+ distroseries=series,
+ branch=self.factory.makeAnyBranch(),
+ store_series=snappy_series,
+ )
+ private_branch = self.factory.makeAnyBranch(
+ owner=self.person, information_type=InformationType.PRIVATESECURITY
+ )
private_branch_name = private_branch.unique_name
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
@@ -1183,56 +1384,71 @@ class TestSnapEditView(BaseTestSnapView):
browser.getControl("Update snap package").click()
self.assertEqual(
"A public snap cannot have a private branch.",
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_edit_public_snap_private_git_ref(self):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
login_person(self.person)
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, distroseries=series,
- git_ref=self.factory.makeGitRefs()[0], store_series=snappy_series)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=series,
+ git_ref=self.factory.makeGitRefs()[0],
+ store_series=snappy_series,
+ )
login_person(self.person)
[private_ref] = self.factory.makeGitRefs(
- owner=self.person,
- information_type=InformationType.PRIVATESECURITY)
+ owner=self.person, information_type=InformationType.PRIVATESECURITY
+ )
private_ref_identity = private_ref.repository.identity
private_ref_path = private_ref.path
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
- browser.getControl(name="field.git_ref.repository").value = (
- private_ref_identity)
+ browser.getControl(
+ name="field.git_ref.repository"
+ ).value = private_ref_identity
browser.getControl(name="field.git_ref.path").value = private_ref_path
browser.getControl("Update snap package").click()
self.assertEqual(
"A public snap cannot have a private repository.",
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_edit_snap_git_url(self):
series = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
snappy_series = self.factory.makeSnappySeries(
- usable_distro_series=[series])
+ usable_distro_series=[series]
+ )
old_ref = self.factory.makeGitRefRemote()
new_ref = self.factory.makeGitRefRemote()
new_repository_url = new_ref.repository_url
new_path = new_ref.path
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person, distroseries=series,
- git_ref=old_ref, store_series=snappy_series)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=series,
+ git_ref=old_ref,
+ store_series=snappy_series,
+ )
browser = self.getViewBrowser(snap, user=self.person)
browser.getLink("Edit snap package").click()
browser.getControl(
- name="field.git_ref.repository").value = new_repository_url
+ name="field.git_ref.repository"
+ ).value = new_repository_url
browser.getControl(name="field.git_ref.path").value = new_path
browser.getControl("Update snap package").click()
login_person(self.person)
content = find_main_content(browser.contents)
self.assertThat(
"Source:\n%s\nEdit snap package" % new_ref.display_name,
- MatchesTagText(content, "source"))
+ MatchesTagText(content, "source"),
+ )
def setUpSeries(self):
"""Set up {distro,snappy}series with some available processors."""
@@ -1241,43 +1457,58 @@ class TestSnapEditView(BaseTestSnapView):
for name in processor_names:
processor = getUtility(IProcessorSet).getByName(name)
self.factory.makeDistroArchSeries(
- distroseries=distroseries, architecturetag=name,
- processor=processor)
+ distroseries=distroseries,
+ architecturetag=name,
+ processor=processor,
+ )
with admin_logged_in():
snappyseries = self.factory.makeSnappySeries(
- usable_distro_series=[distroseries])
+ usable_distro_series=[distroseries]
+ )
return distroseries, snappyseries
def assertSnapProcessors(self, snap, names):
self.assertContentEqual(
- names, [processor.name for processor in snap.processors])
+ names, [processor.name for processor in snap.processors]
+ )
def assertProcessorControls(self, processors_control, enabled, disabled):
matchers = [
MatchesStructure.byEquality(optionValue=name, disabled=False)
- for name in enabled]
- matchers.extend([
- MatchesStructure.byEquality(optionValue=name, disabled=True)
- for name in disabled])
+ for name in enabled
+ ]
+ matchers.extend(
+ [
+ MatchesStructure.byEquality(optionValue=name, disabled=True)
+ for name in disabled
+ ]
+ )
self.assertThat(processors_control.controls, MatchesSetwise(*matchers))
def test_display_processors(self):
distroseries, snappyseries = self.setUpSeries()
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=distroseries, store_series=snappyseries)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=distroseries,
+ store_series=snappyseries,
+ )
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
processors = browser.getControl(name="field.processors")
self.assertContentEqual(
["Intel 386 (386)", "AMD 64bit (amd64)", "HPPA Processor (hppa)"],
- [extract_text(option) for option in processors.displayOptions])
+ [extract_text(option) for option in processors.displayOptions],
+ )
self.assertContentEqual(["386", "amd64", "hppa"], processors.options)
def test_edit_processors(self):
distroseries, snappyseries = self.setUpSeries()
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=distroseries, store_series=snappyseries)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=distroseries,
+ store_series=snappyseries,
+ )
self.assertSnapProcessors(snap, ["386", "amd64", "hppa"])
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
processors = browser.getControl(name="field.processors")
@@ -1297,11 +1528,15 @@ class TestSnapEditView(BaseTestSnapView):
proc_386 = getUtility(IProcessorSet).getByName("386")
proc_amd64 = getUtility(IProcessorSet).getByName("amd64")
proc_armel = self.factory.makeProcessor(
- name="armel", restricted=True, build_by_default=False)
+ name="armel", restricted=True, build_by_default=False
+ )
distroseries, snappyseries = self.setUpSeries()
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=distroseries, store_series=snappyseries)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=distroseries,
+ store_series=snappyseries,
+ )
snap.setProcessors([proc_386, proc_amd64, proc_armel])
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
processors = browser.getControl(name="field.processors")
@@ -1316,19 +1551,26 @@ class TestSnapEditView(BaseTestSnapView):
# checkbox in the UI, and the processor cannot be enabled.
distroseries, snappyseries = self.setUpSeries()
proc_armhf = self.factory.makeProcessor(
- name="armhf", restricted=True, build_by_default=False)
+ name="armhf", restricted=True, build_by_default=False
+ )
self.factory.makeDistroArchSeries(
- distroseries=distroseries, architecturetag="armhf",
- processor=proc_armhf)
+ distroseries=distroseries,
+ architecturetag="armhf",
+ processor=proc_armhf,
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=distroseries, store_series=snappyseries)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=distroseries,
+ store_series=snappyseries,
+ )
self.assertSnapProcessors(snap, ["386", "amd64", "hppa"])
browser = self.getViewBrowser(snap, view_name="+edit", user=snap.owner)
processors = browser.getControl(name="field.processors")
self.assertContentEqual(["386", "amd64", "hppa"], processors.value)
self.assertProcessorControls(
- processors, ["386", "amd64", "hppa"], ["armhf"])
+ processors, ["386", "amd64", "hppa"], ["armhf"]
+ )
# Even if the user works around the disabled checkbox and forcibly
# enables it, they can't enable the restricted processor.
for control in processors.controls:
@@ -1337,7 +1579,8 @@ class TestSnapEditView(BaseTestSnapView):
processors.value = ["386", "amd64", "armhf"]
self.assertRaises(
CannotModifySnapProcessor,
- browser.getControl("Update snap package").click)
+ browser.getControl("Update snap package").click,
+ )
def test_edit_processors_restricted_already_enabled(self):
# A restricted processor that is already enabled is shown with a
@@ -1347,23 +1590,31 @@ class TestSnapEditView(BaseTestSnapView):
proc_386 = getUtility(IProcessorSet).getByName("386")
proc_amd64 = getUtility(IProcessorSet).getByName("amd64")
proc_armhf = self.factory.makeProcessor(
- name="armhf", restricted=True, build_by_default=False)
+ name="armhf", restricted=True, build_by_default=False
+ )
distroseries, snappyseries = self.setUpSeries()
self.factory.makeDistroArchSeries(
- distroseries=distroseries, architecturetag="armhf",
- processor=proc_armhf)
+ distroseries=distroseries,
+ architecturetag="armhf",
+ processor=proc_armhf,
+ )
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=distroseries, store_series=snappyseries)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=distroseries,
+ store_series=snappyseries,
+ )
snap.setProcessors([proc_386, proc_amd64, proc_armhf])
self.assertSnapProcessors(snap, ["386", "amd64", "armhf"])
browser = self.getUserBrowser(
- canonical_url(snap) + "/+edit", user=snap.owner)
+ canonical_url(snap) + "/+edit", user=snap.owner
+ )
processors = browser.getControl(name="field.processors")
# armhf is checked but disabled.
self.assertContentEqual(["386", "amd64", "armhf"], processors.value)
self.assertProcessorControls(
- processors, ["386", "amd64", "hppa"], ["armhf"])
+ processors, ["386", "amd64", "hppa"], ["armhf"]
+ )
processors.value = ["386"]
browser.getControl("Update snap package").click()
login_person(self.person)
@@ -1374,8 +1625,11 @@ class TestSnapEditView(BaseTestSnapView):
initial_kwargs.setdefault("store_series", self.snappyseries)
initial_kwargs.setdefault("store_name", "one")
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=self.distroseries, **initial_kwargs)
+ registrant=self.person,
+ owner=self.person,
+ distroseries=self.distroseries,
+ **initial_kwargs,
+ )
view = create_initialized_view(snap, "+edit", principal=self.person)
data.setdefault("store_upload", snap.store_upload)
data.setdefault("store_distro_series", snap.store_distro_series)
@@ -1391,9 +1645,11 @@ class TestSnapEditView(BaseTestSnapView):
# Changing the store series requires reauthorization.
with admin_logged_in():
new_snappyseries = self.factory.makeSnappySeries(
- usable_distro_series=[self.distroseries])
+ usable_distro_series=[self.distroseries]
+ )
sds = getUtility(ISnappyDistroSeriesSet).getByBothSeries(
- new_snappyseries, self.distroseries)
+ new_snappyseries, self.distroseries
+ )
self.assertNeedStoreReauth(True, {}, {"store_distro_series": sds})
def test__needStoreReauth_different_name(self):
@@ -1408,17 +1664,22 @@ class TestSnapEditView(BaseTestSnapView):
# case, we can't tell if store_series or store_name were also
# changed in between, so reauthorizing is the conservative course.)
self.assertNeedStoreReauth(
- True, {"store_upload": False}, {"store_upload": True})
+ True, {"store_upload": False}, {"store_upload": True}
+ )
@responses.activate
def test_edit_store_upload(self):
# Changing store upload settings on a snap sets all the appropriate
# fields and redirects to SSO for reauthorization.
snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=self.distroseries, store_upload=True,
- store_series=self.snappyseries, store_name="one",
- store_channels=["track/edge"])
+ registrant=self.person,
+ owner=self.person,
+ distroseries=self.distroseries,
+ store_upload=True,
+ store_series=self.snappyseries,
+ store_name="one",
+ store_channels=["track/edge"],
+ )
view_url = canonical_url(snap, view_name="+edit")
browser = self.getNonRedirectingBrowser(url=view_url, user=self.person)
browser.getControl("Registered store package name").value = "two"
@@ -1428,60 +1689,79 @@ class TestSnapEditView(BaseTestSnapView):
browser.getControl("Stable").selected = True
root_macaroon = Macaroon()
root_macaroon.add_third_party_caveat(
- urlsplit(config.launchpad.openid_provider_root).netloc, "",
- "dummy")
+ urlsplit(config.launchpad.openid_provider_root).netloc, "", "dummy"
+ )
root_macaroon_raw = root_macaroon.serialize()
self.pushConfig("snappy", store_url="http://sca.example/")
responses.add(
- "POST", "http://sca.example/dev/api/acl/",
- json={"macaroon": root_macaroon_raw})
+ "POST",
+ "http://sca.example/dev/api/acl/",
+ json={"macaroon": root_macaroon_raw},
+ )
browser.getControl("Update snap package").click()
login_person(self.person)
- self.assertThat(snap, MatchesStructure.byEquality(
- store_name="two", store_secrets={"root": root_macaroon_raw},
- store_channels=["stable", "edge"]))
+ self.assertThat(
+ snap,
+ MatchesStructure.byEquality(
+ store_name="two",
+ store_secrets={"root": root_macaroon_raw},
+ store_channels=["stable", "edge"],
+ ),
+ )
[call] = responses.calls
- self.assertThat(call.request, MatchesStructure.byEquality(
- url="http://sca.example/dev/api/acl/", method="POST"))
+ self.assertThat(
+ call.request,
+ MatchesStructure.byEquality(
+ url="http://sca.example/dev/api/acl/", method="POST"
+ ),
+ )
expected_body = {
"packages": [{"name": "two", "series": self.snappyseries.name}],
"permissions": ["package_upload"],
- }
+ }
self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8")))
+ expected_body, json.loads(call.request.body.decode("UTF-8"))
+ )
self.assertEqual(303, int(browser.headers["Status"].split(" ", 1)[0]))
parsed_location = urlsplit(browser.headers["Location"])
self.assertEqual(
urlsplit(canonical_url(snap) + "/+authorize/+login")[:3],
- parsed_location[:3])
+ parsed_location[:3],
+ )
expected_args = {
"discharge_macaroon_action": ["field.actions.complete"],
"discharge_macaroon_field": ["field.discharge_macaroon"],
"macaroon_caveat_id": ["dummy"],
- }
+ }
self.assertEqual(expected_args, parse_qs(parsed_location[3]))
class TestSnapAuthorizeView(BaseTestSnapView):
-
def setUp(self):
super().setUp()
self.distroseries = self.factory.makeUbuntuDistroSeries()
with admin_logged_in():
self.snappyseries = self.factory.makeSnappySeries(
- usable_distro_series=[self.distroseries])
+ usable_distro_series=[self.distroseries]
+ )
self.snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=self.distroseries, store_upload=True,
+ registrant=self.person,
+ owner=self.person,
+ distroseries=self.distroseries,
+ store_upload=True,
store_series=self.snappyseries,
- store_name=self.factory.getUniqueUnicode())
+ store_name=self.factory.getUniqueUnicode(),
+ )
def test_unauthorized(self):
# A user without edit access cannot authorize snap package uploads.
other_person = self.factory.makePerson()
self.assertRaises(
- Unauthorized, self.getUserBrowser,
- canonical_url(self.snap) + "/+authorize", user=other_person)
+ Unauthorized,
+ self.getUserBrowser,
+ canonical_url(self.snap) + "/+authorize",
+ user=other_person,
+ )
@responses.activate
def test_begin_authorization(self):
@@ -1492,37 +1772,49 @@ class TestSnapAuthorizeView(BaseTestSnapView):
owner = self.snap.owner
root_macaroon = Macaroon()
root_macaroon.add_third_party_caveat(
- urlsplit(config.launchpad.openid_provider_root).netloc, '',
- 'dummy')
+ urlsplit(config.launchpad.openid_provider_root).netloc, "", "dummy"
+ )
root_macaroon_raw = root_macaroon.serialize()
self.pushConfig("snappy", store_url="http://sca.example/")
responses.add(
- "POST", "http://sca.example/dev/api/acl/",
- json={"macaroon": root_macaroon_raw})
+ "POST",
+ "http://sca.example/dev/api/acl/",
+ json={"macaroon": root_macaroon_raw},
+ )
browser = self.getNonRedirectingBrowser(
- url=snap_url + "/+authorize", user=self.snap.owner)
+ url=snap_url + "/+authorize", user=self.snap.owner
+ )
browser.getControl("Begin authorization").click()
[call] = responses.calls
- self.assertThat(call.request, MatchesStructure.byEquality(
- url="http://sca.example/dev/api/acl/", method="POST"))
+ self.assertThat(
+ call.request,
+ MatchesStructure.byEquality(
+ url="http://sca.example/dev/api/acl/", method="POST"
+ ),
+ )
with person_logged_in(owner):
expected_body = {
- "packages": [{
- "name": self.snap.store_name,
- "series": self.snap.store_series.name,
- }],
+ "packages": [
+ {
+ "name": self.snap.store_name,
+ "series": self.snap.store_series.name,
+ }
+ ],
"permissions": ["package_upload"],
- }
+ }
self.assertEqual(
- expected_body, json.loads(call.request.body.decode("UTF-8")))
+ expected_body, json.loads(call.request.body.decode("UTF-8"))
+ )
self.assertEqual(
- {"root": root_macaroon_raw}, self.snap.store_secrets)
+ {"root": root_macaroon_raw}, self.snap.store_secrets
+ )
self.assertEqual(303, int(browser.headers["Status"].split(" ", 1)[0]))
self.assertEqual(
snap_url + "/+authorize/+login?macaroon_caveat_id=dummy&"
"discharge_macaroon_action=field.actions.complete&"
"discharge_macaroon_field=field.discharge_macaroon",
- browser.headers["Location"])
+ browser.headers["Location"],
+ )
def test_complete_authorization_missing_discharge_macaroon(self):
# If the form does not include a discharge macaroon, the "complete"
@@ -1532,13 +1824,18 @@ class TestSnapAuthorizeView(BaseTestSnapView):
transaction.commit()
form = {"field.actions.complete": "1"}
view = create_initialized_view(
- self.snap, "+authorize", form=form, method="POST",
- principal=self.snap.owner)
+ self.snap,
+ "+authorize",
+ form=form,
+ method="POST",
+ principal=self.snap.owner,
+ )
html = view()
self.assertEqual(
- "Uploads of %s to the store were not authorized." %
- self.snap.name,
- get_feedback_messages(html)[1])
+ "Uploads of %s to the store were not authorized."
+ % self.snap.name,
+ get_feedback_messages(html)[1],
+ )
self.assertNotIn("discharge", self.snap.store_secrets)
def test_complete_authorization(self):
@@ -1552,27 +1849,35 @@ class TestSnapAuthorizeView(BaseTestSnapView):
form = {
"field.actions.complete": "1",
"field.discharge_macaroon": discharge_macaroon.serialize(),
- }
+ }
view = create_initialized_view(
- self.snap, "+authorize", form=form, method="POST",
- principal=self.snap.owner)
+ self.snap,
+ "+authorize",
+ form=form,
+ method="POST",
+ principal=self.snap.owner,
+ )
self.assertEqual("", view())
self.assertEqual(302, view.request.response.getStatus())
self.assertEqual(
canonical_url(self.snap),
- view.request.response.getHeader("Location"))
+ view.request.response.getHeader("Location"),
+ )
self.assertEqual(
- "Uploads of %s to the store are now authorized." %
- self.snap.name,
- view.request.response.notifications[0].message)
+ "Uploads of %s to the store are now authorized."
+ % self.snap.name,
+ view.request.response.notifications[0].message,
+ )
self.assertEqual(
- {"root": root_macaroon.serialize(),
- "discharge": discharge_macaroon.serialize()},
- self.snap.store_secrets)
+ {
+ "root": root_macaroon.serialize(),
+ "discharge": discharge_macaroon.serialize(),
+ },
+ self.snap.store_secrets,
+ )
class TestSnapDeleteView(BaseTestSnapView):
-
def test_unauthorized(self):
# A user without edit access cannot delete a snap package.
snap = self.factory.makeSnap(registrant=self.person, owner=self.person)
@@ -1580,10 +1885,14 @@ class TestSnapDeleteView(BaseTestSnapView):
other_person = self.factory.makePerson()
browser = self.getViewBrowser(snap, user=other_person)
self.assertRaises(
- LinkNotFoundError, browser.getLink, "Delete snap package")
+ LinkNotFoundError, browser.getLink, "Delete snap package"
+ )
self.assertRaises(
- Unauthorized, self.getUserBrowser, snap_url + "/+delete",
- user=other_person)
+ Unauthorized,
+ self.getUserBrowser,
+ snap_url + "/+delete",
+ user=other_person,
+ )
def test_delete_snap_without_builds(self):
# A snap package without builds can be deleted.
@@ -1611,16 +1920,18 @@ class TestSnapDeleteView(BaseTestSnapView):
class TestSnapView(BaseTestSnapView):
-
def setUp(self):
super().setUp()
self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
self.distroseries = self.factory.makeDistroSeries(
- distribution=self.ubuntu, name="shiny", displayname="Shiny")
+ distribution=self.ubuntu, name="shiny", displayname="Shiny"
+ )
processor = getUtility(IProcessorSet).getByName("386")
self.distroarchseries = self.factory.makeDistroArchSeries(
- distroseries=self.distroseries, architecturetag="i386",
- processor=processor)
+ distroseries=self.distroseries,
+ architecturetag="i386",
+ processor=processor,
+ )
self.factory.makeBuilder(virtualized=True)
def makeSnap(self, **kwargs):
@@ -1629,8 +1940,11 @@ class TestSnapView(BaseTestSnapView):
if kwargs.get("branch") is None and kwargs.get("git_ref") is None:
kwargs["branch"] = self.factory.makeAnyBranch()
return self.factory.makeSnap(
- registrant=self.person, owner=self.person, name="snap-name",
- **kwargs)
+ registrant=self.person,
+ owner=self.person,
+ name="snap-name",
+ **kwargs,
+ )
def makeBuild(self, snap=None, archive=None, date_created=None, **kwargs):
if snap is None:
@@ -1640,9 +1954,13 @@ class TestSnapView(BaseTestSnapView):
if date_created is None:
date_created = datetime.now(pytz.UTC) - timedelta(hours=1)
return self.factory.makeSnapBuild(
- requester=self.person, snap=snap, archive=archive,
- distroarchseries=self.distroarchseries, date_created=date_created,
- **kwargs)
+ requester=self.person,
+ snap=snap,
+ archive=archive,
+ distroarchseries=self.distroarchseries,
+ date_created=date_created,
+ **kwargs,
+ )
def test_breadcrumb(self):
snap = self.makeSnap()
@@ -1651,41 +1969,55 @@ class TestSnapView(BaseTestSnapView):
view.request.traversed_objects = [self.person, snap, view]
view.initialize()
breadcrumbs_tag = soupmatchers.Tag(
- "breadcrumbs", "ol", attrs={"class": "breadcrumbs"})
+ "breadcrumbs", "ol", attrs={"class": "breadcrumbs"}
+ )
self.assertThat(
view(),
soupmatchers.HTMLContains(
soupmatchers.Within(
breadcrumbs_tag,
soupmatchers.Tag(
- "snap collection breadcrumb", "a",
+ "snap collection breadcrumb",
+ "a",
text="Snap packages",
attrs={
"href": re.compile(r"/~test-person/\+snaps$"),
- })),
+ },
+ ),
+ ),
soupmatchers.Within(
breadcrumbs_tag,
soupmatchers.Tag(
- "snap breadcrumb", "li",
- text=re.compile(r"\ssnap-name\s")))))
+ "snap breadcrumb",
+ "li",
+ text=re.compile(r"\ssnap-name\s"),
+ ),
+ ),
+ ),
+ )
def test_snap_with_project_pillar_url(self):
project = self.factory.makeProduct()
snap = self.factory.makeSnap(project=project)
browser = self.getViewBrowser(snap)
with admin_logged_in():
- expected_url = 'http://launchpad.test/~{}/{}/+snap/{}'.format(
- snap.owner.name, project.name, snap.name)
+ expected_url = "http://launchpad.test/~{}/{}/+snap/{}".format(
+ snap.owner.name, project.name, snap.name
+ )
self.assertEqual(expected_url, browser.url)
def test_index_bzr(self):
branch = self.factory.makePersonalBranch(
- owner=self.person, name="snap-branch")
+ owner=self.person, name="snap-branch"
+ )
snap = self.makeSnap(branch=branch)
build = self.makeBuild(
- snap=snap, status=BuildStatus.FULLYBUILT,
- duration=timedelta(minutes=30))
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ snap=snap,
+ status=BuildStatus.FULLYBUILT,
+ duration=timedelta(minutes=30),
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Snap packages snap-name
.*
Snap package information
@@ -1703,17 +2035,25 @@ class TestSnapView(BaseTestSnapView):
Status When complete Architecture Archive
Successfully built 30 minutes ago i386
Primary Archive for Ubuntu Linux
- """, self.getMainText(build.snap))
+ """,
+ self.getMainText(build.snap),
+ )
def test_index_git(self):
[ref] = self.factory.makeGitRefs(
- owner=self.person, target=self.person, name="snap-repository",
- paths=["refs/heads/master"])
+ owner=self.person,
+ target=self.person,
+ name="snap-repository",
+ paths=["refs/heads/master"],
+ )
snap = self.makeSnap(git_ref=ref)
build = self.makeBuild(
- snap=snap, status=BuildStatus.FULLYBUILT,
- duration=timedelta(minutes=30))
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ snap=snap,
+ status=BuildStatus.FULLYBUILT,
+ duration=timedelta(minutes=30),
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Snap packages snap-name
.*
Snap package information
@@ -1731,23 +2071,31 @@ class TestSnapView(BaseTestSnapView):
Status When complete Architecture Archive
Successfully built 30 minutes ago i386
Primary Archive for Ubuntu Linux
- """, self.getMainText(build.snap))
+ """,
+ self.getMainText(build.snap),
+ )
def test_index_for_subscriber_without_git_repo_access(self):
[ref] = self.factory.makeGitRefs(
- owner=self.person, target=self.person, name="snap-repository",
+ owner=self.person,
+ target=self.person,
+ name="snap-repository",
paths=["refs/heads/master"],
- information_type=InformationType.PRIVATESECURITY)
+ information_type=InformationType.PRIVATESECURITY,
+ )
snap = self.makeSnap(git_ref=ref, private=True)
with admin_logged_in():
self.makeBuild(
- snap=snap, status=BuildStatus.FULLYBUILT,
- duration=timedelta(minutes=30))
+ snap=snap,
+ status=BuildStatus.FULLYBUILT,
+ duration=timedelta(minutes=30),
+ )
subscriber = self.factory.makePerson()
with person_logged_in(self.person):
snap.subscribe(subscriber, self.person)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Snap packages snap-name
.*
Snap package information
@@ -1765,24 +2113,33 @@ class TestSnapView(BaseTestSnapView):
Status When complete Architecture Archive
Successfully built 30 minutes ago i386
Primary Archive for Ubuntu Linux
- """, self.getMainText(snap, user=subscriber))
+ """,
+ self.getMainText(snap, user=subscriber),
+ )
def test_index_for_subscriber_without_archive_access(self):
[ref] = self.factory.makeGitRefs(
- owner=self.person, target=self.person, name="snap-repository",
+ owner=self.person,
+ target=self.person,
+ name="snap-repository",
paths=["refs/heads/master"],
- information_type=InformationType.PRIVATESECURITY)
+ information_type=InformationType.PRIVATESECURITY,
+ )
snap = self.makeSnap(git_ref=ref, private=True)
with admin_logged_in():
archive = self.factory.makeArchive(private=True)
self.makeBuild(
- snap=snap, status=BuildStatus.FULLYBUILT, archive=archive,
- duration=timedelta(minutes=30))
+ snap=snap,
+ status=BuildStatus.FULLYBUILT,
+ archive=archive,
+ duration=timedelta(minutes=30),
+ )
subscriber = self.factory.makePerson()
with person_logged_in(self.person):
snap.subscribe(subscriber, self.person)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Snap packages snap-name
.*
Snap package information
@@ -1799,17 +2156,23 @@ class TestSnapView(BaseTestSnapView):
Latest builds
Status When complete Architecture Archive
This snap package has not been built yet.
- """, self.getMainText(snap, user=subscriber))
+ """,
+ self.getMainText(snap, user=subscriber),
+ )
def test_index_git_url(self):
ref = self.factory.makeGitRefRemote(
repository_url="https://git.example.org/foo",
- path="refs/heads/master")
+ path="refs/heads/master",
+ )
snap = self.makeSnap(git_ref=ref)
build = self.makeBuild(
- snap=snap, status=BuildStatus.FULLYBUILT,
- duration=timedelta(minutes=30))
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ snap=snap,
+ status=BuildStatus.FULLYBUILT,
+ duration=timedelta(minutes=30),
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Snap packages snap-name
.*
Snap package information
@@ -1827,7 +2190,9 @@ class TestSnapView(BaseTestSnapView):
Status When complete Architecture Archive
Successfully built 30 minutes ago i386
Primary Archive for Ubuntu Linux
- """, self.getMainText(build.snap))
+ """,
+ self.getMainText(build.snap),
+ )
def test_index_no_distro_series(self):
# If the snap is configured to infer an appropriate distro series
@@ -1841,14 +2206,18 @@ class TestSnapView(BaseTestSnapView):
def test_index_success_with_buildlog(self):
# The build log is shown if it is there.
build = self.makeBuild(
- status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=30))
+ status=BuildStatus.FULLYBUILT, duration=timedelta(minutes=30)
+ )
build.setLog(self.factory.makeLibraryFileAlias())
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Latest builds
Status When complete Architecture Archive
Successfully built 30 minutes ago buildlog \(.*\) i386
Primary Archive for Ubuntu Linux
- """, self.getMainText(build.snap))
+ """,
+ self.getMainText(build.snap),
+ )
def test_index_hides_builds_into_private_archive(self):
# The index page hides builds into archives the user can't view.
@@ -1856,81 +2225,108 @@ class TestSnapView(BaseTestSnapView):
with person_logged_in(archive.owner):
snap = self.makeBuild(archive=archive).snap
self.assertIn(
- "This snap package has not been built yet.",
- self.getMainText(snap))
+ "This snap package has not been built yet.", self.getMainText(snap)
+ )
def test_index_no_builds(self):
# A message is shown when there are no builds.
snap = self.factory.makeSnap()
self.assertIn(
- "This snap package has not been built yet.",
- self.getMainText(snap))
+ "This snap package has not been built yet.", self.getMainText(snap)
+ )
def test_index_pending_build(self):
# A pending build is listed as such.
build = self.makeBuild()
build.queueBuild()
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Latest builds
Status When complete Architecture Archive
Needs building in .* \(estimated\) i386
Primary Archive for Ubuntu Linux
- """, self.getMainText(build.snap))
+ """,
+ self.getMainText(build.snap),
+ )
def test_index_pending_build_request(self):
# A pending build request is listed as such.
snap = self.makeSnap()
with person_logged_in(snap.owner):
snap.requestBuilds(
- snap.owner, snap.distro_series.main_archive,
- PackagePublishingPocket.UPDATES)
- self.assertTextMatchesExpressionIgnoreWhitespace("""\
+ snap.owner,
+ snap.distro_series.main_archive,
+ PackagePublishingPocket.UPDATES,
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ """\
Latest builds
Status When complete Architecture Archive
Pending build request
Primary Archive for Ubuntu Linux
- """, self.getMainText(snap))
+ """,
+ self.getMainText(snap),
+ )
def test_index_failed_build_request(self):
# A failed build request is listed as such, with its error message.
snap = self.makeSnap()
with person_logged_in(snap.owner):
request = snap.requestBuilds(
- snap.owner, snap.distro_series.main_archive,
- PackagePublishingPocket.UPDATES)
+ snap.owner,
+ snap.distro_series.main_archive,
+ PackagePublishingPocket.UPDATES,
+ )
job = removeSecurityProxy(removeSecurityProxy(request)._job)
job.job._status = JobStatus.FAILED
job.job.date_finished = datetime.now(pytz.UTC) - timedelta(hours=1)
job.error_message = "Boom"
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""\
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""\
Latest builds
Status When complete Architecture Archive
Failed build request 1 hour ago \(Boom\)
Primary Archive for Ubuntu Linux
- """, self.getMainText(snap))
+ """,
+ self.getMainText(snap),
+ )
def test_index_store_upload(self):
# If the snap package is to be automatically uploaded to the store,
# the index page shows details of this.
with admin_logged_in():
snappyseries = self.factory.makeSnappySeries(
- usable_distro_series=[self.distroseries])
+ usable_distro_series=[self.distroseries]
+ )
snap = self.makeSnap(
- store_upload=True, store_series=snappyseries,
- store_name=self.getUniqueString("store-name"))
+ store_upload=True,
+ store_series=snappyseries,
+ store_name=self.getUniqueString("store-name"),
+ )
view = create_initialized_view(snap, "+index")
store_upload_tag = soupmatchers.Tag(
- "store upload", "div", attrs={"id": "store_upload"})
- self.assertThat(view(), soupmatchers.HTMLContains(
- soupmatchers.Tag(
- "distribution series", "dl", attrs={"id": "distro_series"}),
- soupmatchers.Within(
- store_upload_tag,
+ "store upload", "div", attrs={"id": "store_upload"}
+ )
+ self.assertThat(
+ view(),
+ soupmatchers.HTMLContains(
soupmatchers.Tag(
- "store series name", "span", text=snappyseries.title)),
- soupmatchers.Within(
- store_upload_tag,
- soupmatchers.Tag("store name", "span", text=snap.store_name))))
+ "distribution series", "dl", attrs={"id": "distro_series"}
+ ),
+ soupmatchers.Within(
+ store_upload_tag,
+ soupmatchers.Tag(
+ "store series name", "span", text=snappyseries.title
+ ),
+ ),
+ soupmatchers.Within(
+ store_upload_tag,
+ soupmatchers.Tag(
+ "store name", "span", text=snap.store_name
+ ),
+ ),
+ ),
+ )
def test_index_store_upload_no_distro_series(self):
# If the snap package is to be automatically uploaded to the store
@@ -1939,39 +2335,62 @@ class TestSnapView(BaseTestSnapView):
with admin_logged_in():
snappyseries = self.factory.makeSnappySeries(
usable_distro_series=[self.distroseries],
- can_infer_distro_series=True)
+ can_infer_distro_series=True,
+ )
snap = self.makeSnap(
- distroseries=None, store_upload=True, store_series=snappyseries,
- store_name=self.getUniqueString("store-name"))
+ distroseries=None,
+ store_upload=True,
+ store_series=snappyseries,
+ store_name=self.getUniqueString("store-name"),
+ )
view = create_initialized_view(snap, "+index")
store_upload_tag = soupmatchers.Tag(
- "store upload", "div", attrs={"id": "store_upload"})
- self.assertThat(view(), soupmatchers.HTMLContains(
- Not(soupmatchers.Tag(
- "distribution series", "dl", attrs={"id": "distro_series"})),
- soupmatchers.Within(
- store_upload_tag,
- soupmatchers.Tag(
- "store series name", "span", text=snappyseries.title)),
- soupmatchers.Within(
- store_upload_tag,
- soupmatchers.Tag("store name", "span", text=snap.store_name))))
+ "store upload", "div", attrs={"id": "store_upload"}
+ )
+ self.assertThat(
+ view(),
+ soupmatchers.HTMLContains(
+ Not(
+ soupmatchers.Tag(
+ "distribution series",
+ "dl",
+ attrs={"id": "distro_series"},
+ )
+ ),
+ soupmatchers.Within(
+ store_upload_tag,
+ soupmatchers.Tag(
+ "store series name", "span", text=snappyseries.title
+ ),
+ ),
+ soupmatchers.Within(
+ store_upload_tag,
+ soupmatchers.Tag(
+ "store name", "span", text=snap.store_name
+ ),
+ ),
+ ),
+ )
def setStatus(self, build, status):
build.updateStatus(
- BuildStatus.BUILDING, date_started=build.date_created)
+ BuildStatus.BUILDING, date_started=build.date_created
+ )
build.updateStatus(
- status, date_finished=build.date_started + timedelta(minutes=30))
+ status, date_finished=build.date_started + timedelta(minutes=30)
+ )
def test_builds_and_requests(self):
# SnapView.builds_and_requests produces reasonable results.
snap = self.makeSnap()
# Create oldest builds first so that they sort properly by id.
date_gen = time_counter(
- datetime(2000, 1, 1, tzinfo=pytz.UTC), timedelta(days=1))
+ datetime(2000, 1, 1, tzinfo=pytz.UTC), timedelta(days=1)
+ )
builds = [
self.makeBuild(snap=snap, date_created=next(date_gen))
- for i in range(11)]
+ for i in range(11)
+ ]
view = SnapView(snap, None)
self.assertEqual(list(reversed(builds)), view.builds_and_requests)
self.setStatus(builds[10], BuildStatus.FULLYBUILT)
@@ -1980,8 +2399,8 @@ class TestSnapView(BaseTestSnapView):
# When there are >= 9 pending builds, only the most recent of any
# completed builds is returned.
self.assertEqual(
- list(reversed(builds[:9])) + [builds[10]],
- view.builds_and_requests)
+ list(reversed(builds[:9])) + [builds[10]], view.builds_and_requests
+ )
for build in builds[:9]:
self.setStatus(build, BuildStatus.FULLYBUILT)
del get_property_cache(view).builds_and_requests
@@ -1992,48 +2411,67 @@ class TestSnapView(BaseTestSnapView):
# builds.
snap = self.makeSnap()
date_gen = time_counter(
- datetime(2000, 1, 1, tzinfo=pytz.UTC), timedelta(days=1))
+ datetime(2000, 1, 1, tzinfo=pytz.UTC), timedelta(days=1)
+ )
builds = [
self.makeBuild(snap=snap, date_created=next(date_gen))
- for i in range(3)]
+ for i in range(3)
+ ]
self.setStatus(builds[2], BuildStatus.FULLYBUILT)
with person_logged_in(snap.owner):
request = snap.requestBuilds(
- snap.owner, snap.distro_series.main_archive,
- PackagePublishingPocket.UPDATES)
+ snap.owner,
+ snap.distro_series.main_archive,
+ PackagePublishingPocket.UPDATES,
+ )
job = removeSecurityProxy(removeSecurityProxy(request)._job)
job.job.date_created = next(date_gen)
view = SnapView(snap, None)
# The pending build request is interleaved in date order with
# pending builds, and these are followed by completed builds.
- self.assertThat(view.builds_and_requests, MatchesListwise([
- MatchesStructure.byEquality(id=request.id),
- Equals(builds[1]),
- Equals(builds[0]),
- Equals(builds[2]),
- ]))
+ self.assertThat(
+ view.builds_and_requests,
+ MatchesListwise(
+ [
+ MatchesStructure.byEquality(id=request.id),
+ Equals(builds[1]),
+ Equals(builds[0]),
+ Equals(builds[2]),
+ ]
+ ),
+ )
transaction.commit()
builds.append(self.makeBuild(snap=snap))
del get_property_cache(view).builds_and_requests
- self.assertThat(view.builds_and_requests, MatchesListwise([
- Equals(builds[3]),
- MatchesStructure.byEquality(id=request.id),
- Equals(builds[1]),
- Equals(builds[0]),
- Equals(builds[2]),
- ]))
+ self.assertThat(
+ view.builds_and_requests,
+ MatchesListwise(
+ [
+ Equals(builds[3]),
+ MatchesStructure.byEquality(id=request.id),
+ Equals(builds[1]),
+ Equals(builds[0]),
+ Equals(builds[2]),
+ ]
+ ),
+ )
# If we pretend that the job failed, it is still listed, but after
# any pending builds.
job.job._status = JobStatus.FAILED
job.job.date_finished = job.date_created + timedelta(minutes=30)
del get_property_cache(view).builds_and_requests
- self.assertThat(view.builds_and_requests, MatchesListwise([
- Equals(builds[3]),
- Equals(builds[1]),
- Equals(builds[0]),
- MatchesStructure.byEquality(id=request.id),
- Equals(builds[2]),
- ]))
+ self.assertThat(
+ view.builds_and_requests,
+ MatchesListwise(
+ [
+ Equals(builds[3]),
+ Equals(builds[1]),
+ Equals(builds[0]),
+ MatchesStructure.byEquality(id=request.id),
+ Equals(builds[2]),
+ ]
+ ),
+ )
def test_store_channels_empty(self):
snap = self.factory.makeSnap()
@@ -2042,10 +2480,12 @@ class TestSnapView(BaseTestSnapView):
def test_store_channels_display(self):
snap = self.factory.makeSnap(
- store_channels=["track/stable/fix-123", "track/edge/fix-123"])
+ store_channels=["track/stable/fix-123", "track/edge/fix-123"]
+ )
view = create_initialized_view(snap, "+index")
self.assertEqual(
- "track/stable/fix-123, track/edge/fix-123", view.store_channels)
+ "track/stable/fix-123, track/edge/fix-123", view.store_channels
+ )
def test_authorize_navigation_no_store_secrets(self):
# A snap with no store secrets has an "Authorize store uploads"
@@ -2062,8 +2502,10 @@ class TestSnapView(BaseTestSnapView):
# navigation link.
owner = self.factory.makePerson()
snap = self.factory.makeSnap(
- registrant=owner, owner=owner,
- store_secrets={"root": Macaroon().serialize()})
+ registrant=owner,
+ owner=owner,
+ store_secrets={"root": Macaroon().serialize()},
+ )
authorize_url = canonical_url(snap, view_name="+authorize")
browser = self.getViewBrowser(snap, user=owner)
authorize_link = browser.getLink("Reauthorize store uploads")
@@ -2071,26 +2513,32 @@ class TestSnapView(BaseTestSnapView):
class TestSnapRequestBuildsView(BaseTestSnapView):
-
def setUp(self):
super().setUp()
self.ubuntu = getUtility(ILaunchpadCelebrities).ubuntu
self.distroseries = self.factory.makeDistroSeries(
- distribution=self.ubuntu, name="shiny", displayname="Shiny")
+ distribution=self.ubuntu, name="shiny", displayname="Shiny"
+ )
self.architectures = []
for processor, architecture in ("386", "i386"), ("amd64", "amd64"):
das = self.factory.makeDistroArchSeries(
- distroseries=self.distroseries, architecturetag=architecture,
- processor=getUtility(IProcessorSet).getByName(processor))
+ distroseries=self.distroseries,
+ architecturetag=architecture,
+ processor=getUtility(IProcessorSet).getByName(processor),
+ )
das.addOrUpdateChroot(self.factory.makeLibraryFileAlias())
self.architectures.append(das)
self.snap = self.factory.makeSnap(
- registrant=self.person, owner=self.person,
- distroseries=self.distroseries, name="snap-name")
+ registrant=self.person,
+ owner=self.person,
+ distroseries=self.distroseries,
+ name="snap-name",
+ )
def test_request_builds_page(self):
# The +request-builds page is sane.
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Request builds for snap-name
Snap packages
snap-name
@@ -2130,45 +2578,56 @@ class TestSnapRequestBuildsView(BaseTestSnapView):
or
Cancel
""",
- self.getMainText(self.snap, "+request-builds", user=self.person))
+ self.getMainText(self.snap, "+request-builds", user=self.person),
+ )
def test_request_builds_not_owner(self):
# A user without launchpad.Edit cannot request builds.
self.assertRaises(
- Unauthorized, self.getViewBrowser, self.snap, "+request-builds")
+ Unauthorized, self.getViewBrowser, self.snap, "+request-builds"
+ )
def test_request_builds_with_architectures_action(self):
# Requesting a build with architectures selected creates a pending
# build request limited to those architectures.
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
browser.getControl("amd64").selected = True
browser.getControl("i386").selected = True
browser.getControl("Request builds").click()
login_person(self.person)
[request] = self.snap.pending_build_requests
- self.assertThat(removeSecurityProxy(request), MatchesStructure(
- snap=Equals(self.snap),
- status=Equals(SnapBuildRequestStatus.PENDING),
- error_message=Is(None),
- builds=AfterPreprocessing(list, Equals([])),
- archive=Equals(self.ubuntu.main_archive),
- _job=MatchesStructure(
- requester=Equals(self.person),
- pocket=Equals(PackagePublishingPocket.UPDATES),
- channels=Equals({}),
- architectures=MatchesSetwise(Equals("amd64"), Equals("i386")),
- )))
+ self.assertThat(
+ removeSecurityProxy(request),
+ MatchesStructure(
+ snap=Equals(self.snap),
+ status=Equals(SnapBuildRequestStatus.PENDING),
+ error_message=Is(None),
+ builds=AfterPreprocessing(list, Equals([])),
+ archive=Equals(self.ubuntu.main_archive),
+ _job=MatchesStructure(
+ requester=Equals(self.person),
+ pocket=Equals(PackagePublishingPocket.UPDATES),
+ channels=Equals({}),
+ architectures=MatchesSetwise(
+ Equals("amd64"), Equals("i386")
+ ),
+ ),
+ ),
+ )
def test_request_builds_with_architectures_ppa(self):
# Selecting a different archive with architectures selected creates
# a build request targeting that archive and limited to those
# architectures.
ppa = self.factory.makeArchive(
- distribution=self.ubuntu, owner=self.person, name="snap-ppa")
+ distribution=self.ubuntu, owner=self.person, name="snap-ppa"
+ )
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
browser.getControl("PPA").click()
browser.getControl(name="field.archive.ppa").value = ppa.reference
browser.getControl("amd64").selected = True
@@ -2177,16 +2636,21 @@ class TestSnapRequestBuildsView(BaseTestSnapView):
login_person(self.person)
[request] = self.snap.pending_build_requests
- self.assertThat(request, MatchesStructure(
- archive=Equals(ppa),
- architectures=MatchesSetwise(Equals("amd64"))))
+ self.assertThat(
+ request,
+ MatchesStructure(
+ archive=Equals(ppa),
+ architectures=MatchesSetwise(Equals("amd64")),
+ ),
+ )
def test_request_builds_with_architectures_channels(self):
# Selecting different channels with architectures selected creates a
# build request using those channels and limited to those
# architectures.
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
browser.getControl(name="field.channels.core").value = "edge"
browser.getControl("amd64").selected = True
self.assertFalse(browser.getControl("i386").selected)
@@ -2194,40 +2658,52 @@ class TestSnapRequestBuildsView(BaseTestSnapView):
login_person(self.person)
[request] = self.snap.pending_build_requests
- self.assertThat(request, MatchesStructure(
- channels=MatchesDict({"core": Equals("edge")}),
- architectures=MatchesSetwise(Equals("amd64"))))
+ self.assertThat(
+ request,
+ MatchesStructure(
+ channels=MatchesDict({"core": Equals("edge")}),
+ architectures=MatchesSetwise(Equals("amd64")),
+ ),
+ )
def test_request_builds_no_architectures_action(self):
# Requesting a build with no architectures selected creates a
# pending build request.
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
self.assertFalse(browser.getControl("amd64").selected)
self.assertFalse(browser.getControl("i386").selected)
browser.getControl("Request builds").click()
login_person(self.person)
[request] = self.snap.pending_build_requests
- self.assertThat(removeSecurityProxy(request), MatchesStructure(
- snap=Equals(self.snap),
- status=Equals(SnapBuildRequestStatus.PENDING),
- error_message=Is(None),
- builds=AfterPreprocessing(list, Equals([])),
- archive=Equals(self.ubuntu.main_archive),
- _job=MatchesStructure(
- requester=Equals(self.person),
- pocket=Equals(PackagePublishingPocket.UPDATES),
- channels=Equals({}),
- architectures=Is(None))))
+ self.assertThat(
+ removeSecurityProxy(request),
+ MatchesStructure(
+ snap=Equals(self.snap),
+ status=Equals(SnapBuildRequestStatus.PENDING),
+ error_message=Is(None),
+ builds=AfterPreprocessing(list, Equals([])),
+ archive=Equals(self.ubuntu.main_archive),
+ _job=MatchesStructure(
+ requester=Equals(self.person),
+ pocket=Equals(PackagePublishingPocket.UPDATES),
+ channels=Equals({}),
+ architectures=Is(None),
+ ),
+ ),
+ )
def test_request_builds_no_architectures_ppa(self):
# Selecting a different archive with no architectures selected
# creates a build request targeting that archive.
ppa = self.factory.makeArchive(
- distribution=self.ubuntu, owner=self.person, name="snap-ppa")
+ distribution=self.ubuntu, owner=self.person, name="snap-ppa"
+ )
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
browser.getControl("PPA").click()
browser.getControl(name="field.archive.ppa").value = ppa.reference
self.assertFalse(browser.getControl("amd64").selected)
@@ -2236,15 +2712,17 @@ class TestSnapRequestBuildsView(BaseTestSnapView):
login_person(self.person)
[request] = self.snap.pending_build_requests
- self.assertThat(request, MatchesStructure(
- archive=Equals(ppa),
- architectures=Is(None)))
+ self.assertThat(
+ request,
+ MatchesStructure(archive=Equals(ppa), architectures=Is(None)),
+ )
def test_request_builds_no_architectures_channels(self):
# Selecting different channels with no architectures selected
# creates a build request using those channels.
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
browser.getControl(name="field.channels.core").value = "edge"
self.assertFalse(browser.getControl("amd64").selected)
self.assertFalse(browser.getControl("i386").selected)
@@ -2260,18 +2738,24 @@ class TestSnapRequestBuildsView(BaseTestSnapView):
login_person(self.person)
self.snap.distro_series = None
browser = self.getViewBrowser(
- self.snap, "+request-builds", user=self.person)
+ self.snap, "+request-builds", user=self.person
+ )
browser.getControl("Request builds").click()
login_person(self.person)
[request] = self.snap.pending_build_requests
- self.assertThat(removeSecurityProxy(request), MatchesStructure(
- snap=Equals(self.snap),
- status=Equals(SnapBuildRequestStatus.PENDING),
- error_message=Is(None),
- builds=AfterPreprocessing(list, Equals([])),
- archive=Equals(self.ubuntu.main_archive),
- _job=MatchesStructure(
- requester=Equals(self.person),
- pocket=Equals(PackagePublishingPocket.UPDATES),
- channels=Equals({}))))
+ self.assertThat(
+ removeSecurityProxy(request),
+ MatchesStructure(
+ snap=Equals(self.snap),
+ status=Equals(SnapBuildRequestStatus.PENDING),
+ error_message=Is(None),
+ builds=AfterPreprocessing(list, Equals([])),
+ archive=Equals(self.ubuntu.main_archive),
+ _job=MatchesStructure(
+ requester=Equals(self.person),
+ pocket=Equals(PackagePublishingPocket.UPDATES),
+ channels=Equals({}),
+ ),
+ ),
+ )
diff --git a/lib/lp/snappy/browser/tests/test_snapbuild.py b/lib/lp/snappy/browser/tests/test_snapbuild.py
index c6a6c56..5c25aaf 100644
--- a/lib/lp/snappy/browser/tests/test_snapbuild.py
+++ b/lib/lp/snappy/browser/tests/test_snapbuild.py
@@ -5,15 +5,12 @@
import re
+import soupmatchers
+import transaction
from fixtures import FakeLogger
from pymacaroons import Macaroon
-import soupmatchers
from storm.locals import Store
-from testtools.matchers import (
- Not,
- StartsWith,
- )
-import transaction
+from testtools.matchers import Not, StartsWith
from zope.component import getUtility
from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
@@ -26,23 +23,20 @@ from lp.services.job.interfaces.job import JobStatus
from lp.services.webapp import canonical_url
from lp.snappy.interfaces.snapbuildjob import ISnapStoreUploadJobSource
from lp.testing import (
- admin_logged_in,
ANONYMOUS,
BrowserTestCase,
+ TestCaseWithFactory,
+ admin_logged_in,
login,
person_logged_in,
- TestCaseWithFactory,
- )
-from lp.testing.layers import (
- DatabaseFunctionalLayer,
- LaunchpadFunctionalLayer,
- )
+)
+from lp.testing.layers import DatabaseFunctionalLayer, LaunchpadFunctionalLayer
from lp.testing.pages import (
extract_text,
find_main_content,
find_tag_by_id,
find_tags_by_class,
- )
+)
from lp.testing.views import create_initialized_view
@@ -53,11 +47,13 @@ class TestCanonicalUrlForSnapBuild(TestCaseWithFactory):
def test_canonical_url(self):
owner = self.factory.makePerson(name="person")
snap = self.factory.makeSnap(
- registrant=owner, owner=owner, name="snap")
+ registrant=owner, owner=owner, name="snap"
+ )
build = self.factory.makeSnapBuild(requester=owner, snap=snap)
self.assertThat(
canonical_url(build),
- StartsWith("http://launchpad.test/~person/+snap/snap/+build/"))
+ StartsWith("http://launchpad.test/~person/+snap/snap/+build/"),
+ )
class TestSnapBuildView(TestCaseWithFactory):
@@ -71,7 +67,8 @@ class TestSnapBuildView(TestCaseWithFactory):
build_view = create_initialized_view(build, "+index")
self.assertEqual(
[snapfile.libraryfile.filename],
- [lfa.filename for lfa in build_view.files])
+ [lfa.filename for lfa in build_view.files],
+ )
# Deleted files won't be included.
self.assertFalse(snapfile.libraryfile.deleted)
removeSecurityProxy(snapfile.libraryfile).content = None
@@ -82,12 +79,20 @@ class TestSnapBuildView(TestCaseWithFactory):
def test_revision_id(self):
build = self.factory.makeSnapBuild()
build.updateStatus(
- BuildStatus.FULLYBUILT, worker_status={"revision_id": "dummy"})
+ BuildStatus.FULLYBUILT, worker_status={"revision_id": "dummy"}
+ )
build_view = create_initialized_view(build, "+index")
- self.assertThat(build_view(), soupmatchers.HTMLContains(
- soupmatchers.Tag(
- "revision ID", "li", attrs={"id": "revision-id"},
- text=re.compile(r"^\s*VCS revision: dummy\s*$"))))
+ self.assertThat(
+ build_view(),
+ soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "revision ID",
+ "li",
+ attrs={"id": "revision-id"},
+ text=re.compile(r"^\s*VCS revision: dummy\s*$"),
+ )
+ ),
+ )
def test_no_store_status_if_not_fully_built(self):
build = self.factory.makeSnapBuild(status=BuildStatus.NEEDSBUILD)
@@ -103,125 +108,213 @@ class TestSnapBuildView(TestCaseWithFactory):
def test_store_upload_status_in_progress(self):
build = self.factory.makeSnapBuild(
- status=BuildStatus.FULLYBUILT, store_name="foo")
+ status=BuildStatus.FULLYBUILT, store_name="foo"
+ )
getUtility(ISnapStoreUploadJobSource).create(build)
build_view = create_initialized_view(build, "+index")
store_status = find_tag_by_id(build_view(), "store-status")
- self.assertThat(store_status, soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"},
- text=re.compile(r"^\s*Store upload in progress\s*$")))
+ self.assertThat(
+ store_status,
+ soupmatchers.Tag(
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ text=re.compile(r"^\s*Store upload in progress\s*$"),
+ ),
+ )
def test_store_upload_status_completed(self):
build = self.factory.makeSnapBuild(
- status=BuildStatus.FULLYBUILT, store_name="foo")
+ status=BuildStatus.FULLYBUILT, store_name="foo"
+ )
job = getUtility(ISnapStoreUploadJobSource).create(build)
naked_job = removeSecurityProxy(job)
naked_job.job._status = JobStatus.COMPLETED
naked_job.store_url = "http://sca.example/dev/click-apps/1/rev/1/"
build_view = create_initialized_view(build, "+index")
store_status = find_tag_by_id(build_view(), "store-status")
- self.assertThat(store_status, soupmatchers.Within(
- soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"}),
- soupmatchers.Tag(
- "store link", "a", attrs={"href": job.store_url},
- text=re.compile(
- r"^\s*Manage this package in the store\s*$"))))
+ self.assertThat(
+ store_status,
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ ),
+ soupmatchers.Tag(
+ "store link",
+ "a",
+ attrs={"href": job.store_url},
+ text=re.compile(
+ r"^\s*Manage this package in the store\s*$"
+ ),
+ ),
+ ),
+ )
def test_store_upload_status_completed_no_url(self):
# A package that has been uploaded to the store may lack a URL if
# the upload was queued behind others for manual review.
build = self.factory.makeSnapBuild(
- status=BuildStatus.FULLYBUILT, store_name="foo")
+ status=BuildStatus.FULLYBUILT, store_name="foo"
+ )
job = getUtility(ISnapStoreUploadJobSource).create(build)
naked_job = removeSecurityProxy(job)
naked_job.job._status = JobStatus.COMPLETED
build_view = create_initialized_view(build, "+index")
store_status = find_tag_by_id(build_view(), "store-status")
- self.assertThat(store_status, soupmatchers.Within(
- soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"}),
- soupmatchers.Tag(
- "store link", "a", attrs={"href": config.snappy.store_url},
- text=re.compile(r"^\s*Uploaded to the store\s*$"))))
+ self.assertThat(
+ store_status,
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ ),
+ soupmatchers.Tag(
+ "store link",
+ "a",
+ attrs={"href": config.snappy.store_url},
+ text=re.compile(r"^\s*Uploaded to the store\s*$"),
+ ),
+ ),
+ )
def test_store_upload_status_failed(self):
build = self.factory.makeSnapBuild(
- status=BuildStatus.FULLYBUILT, store_name="foo")
+ status=BuildStatus.FULLYBUILT, store_name="foo"
+ )
job = getUtility(ISnapStoreUploadJobSource).create(build)
naked_job = removeSecurityProxy(job)
naked_job.job._status = JobStatus.FAILED
naked_job.error_message = "Scan failed."
build_view = create_initialized_view(build, "+index")
store_status = find_tag_by_id(build_view(), "store-status")
- self.assertThat(store_status, soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"}))
- self.assertThat(store_status, soupmatchers.Within(
- soupmatchers.Tag(
- "store upload error messages", "ul",
- attrs={"id": "store-upload-error-messages"}),
+ self.assertThat(
+ store_status,
soupmatchers.Tag(
- "store upload error message", "li",
- text=re.compile(r"^\s*Scan failed.\s*$"))))
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ ),
+ )
+ self.assertThat(
+ store_status,
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "store upload error messages",
+ "ul",
+ attrs={"id": "store-upload-error-messages"},
+ ),
+ soupmatchers.Tag(
+ "store upload error message",
+ "li",
+ text=re.compile(r"^\s*Scan failed.\s*$"),
+ ),
+ ),
+ )
def test_store_upload_status_failed_with_extended_error_message(self):
build = self.factory.makeSnapBuild(
- status=BuildStatus.FULLYBUILT, store_name="foo")
+ status=BuildStatus.FULLYBUILT, store_name="foo"
+ )
job = getUtility(ISnapStoreUploadJobSource).create(build)
naked_job = removeSecurityProxy(job)
naked_job.job._status = JobStatus.FAILED
naked_job.error_message = "This should not be shown."
naked_job.error_messages = [
- {"message": (
- "The new version submitted for 'name' does not match the "
- "upload ('other-name').")},
+ {
+ "message": (
+ "The new version submitted for 'name' does not match the "
+ "upload ('other-name')."
+ )
+ },
{"message": "Scan failed.", "link": "link1"},
- {"message": "Classic not allowed.", "link": "link2"}]
+ {"message": "Classic not allowed.", "link": "link2"},
+ ]
build_view = create_initialized_view(build, "+index")
store_status = find_tag_by_id(build_view(), "store-status")
- self.assertThat(store_status, Not(soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"},
- text=re.compile('.*This should not be shown.*'))))
+ self.assertThat(
+ store_status,
+ Not(
+ soupmatchers.Tag(
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ text=re.compile(".*This should not be shown.*"),
+ )
+ ),
+ )
error_messages = soupmatchers.Tag(
- "store upload error messages", "ul",
- attrs={"id": "store-upload-error-messages"})
- self.assertThat(store_status, soupmatchers.Within(
- soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"}),
- error_messages))
- self.assertThat(store_status, soupmatchers.Within(
- error_messages,
- soupmatchers.Tag(
- "store upload error message", "li",
- text=re.compile(".*The new version.*"))))
- self.assertThat(store_status, soupmatchers.Within(
- error_messages,
+ "store upload error messages",
+ "ul",
+ attrs={"id": "store-upload-error-messages"},
+ )
+ self.assertThat(
+ store_status,
soupmatchers.Within(
soupmatchers.Tag(
- "store upload error message", "li",
- text=re.compile(r".*Scan failed\..*")),
- soupmatchers.Tag(
- "store upload error link", "a",
- attrs={"href": "link1"}, text="(?)"))))
- self.assertThat(store_status, soupmatchers.Within(
- error_messages,
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ ),
+ error_messages,
+ ),
+ )
+ self.assertThat(
+ store_status,
soupmatchers.Within(
+ error_messages,
soupmatchers.Tag(
- "store upload error message", "li",
- text=re.compile(r".*Classic not allowed\..*")),
- soupmatchers.Tag(
- "store upload error link", "a",
- attrs={"href": "link2"}, text="(?)"))))
+ "store upload error message",
+ "li",
+ text=re.compile(".*The new version.*"),
+ ),
+ ),
+ )
+ self.assertThat(
+ store_status,
+ soupmatchers.Within(
+ error_messages,
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "store upload error message",
+ "li",
+ text=re.compile(r".*Scan failed\..*"),
+ ),
+ soupmatchers.Tag(
+ "store upload error link",
+ "a",
+ attrs={"href": "link1"},
+ text="(?)",
+ ),
+ ),
+ ),
+ )
+ self.assertThat(
+ store_status,
+ soupmatchers.Within(
+ error_messages,
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "store upload error message",
+ "li",
+ text=re.compile(r".*Classic not allowed\..*"),
+ ),
+ soupmatchers.Tag(
+ "store upload error link",
+ "a",
+ attrs={"href": "link2"},
+ text="(?)",
+ ),
+ ),
+ ),
+ )
def test_store_upload_status_release_failed(self):
build = self.factory.makeSnapBuild(
- status=BuildStatus.FULLYBUILT, store_name="foo")
+ status=BuildStatus.FULLYBUILT, store_name="foo"
+ )
job = getUtility(ISnapStoreUploadJobSource).create(build)
naked_job = removeSecurityProxy(job)
naked_job.job._status = JobStatus.FAILED
@@ -229,15 +322,23 @@ class TestSnapBuildView(TestCaseWithFactory):
naked_job.error_message = "Failed to publish"
build_view = create_initialized_view(build, "+index")
store_status = find_tag_by_id(build_view(), "store-status")
- self.assertThat(store_status, soupmatchers.Within(
- soupmatchers.Tag(
- "store upload status", "dd",
- attrs={"id": "store-upload-status"},
- text=re.compile(
- r"^\s*Releasing package to channels failed:\s+"
- r"Failed to publish\s*$")),
- soupmatchers.Tag(
- "store link", "a", attrs={"href": job.store_url})))
+ self.assertThat(
+ store_status,
+ soupmatchers.Within(
+ soupmatchers.Tag(
+ "store upload status",
+ "dd",
+ attrs={"id": "store-upload-status"},
+ text=re.compile(
+ r"^\s*Releasing package to channels failed:\s+"
+ r"Failed to publish\s*$"
+ ),
+ ),
+ soupmatchers.Tag(
+ "store link", "a", attrs={"href": job.store_url}
+ ),
+ ),
+ )
class TestSnapBuildOperations(BrowserTestCase):
@@ -251,7 +352,8 @@ class TestSnapBuildOperations(BrowserTestCase):
self.build_url = canonical_url(self.build)
self.requester = self.build.requester
self.buildd_admin = self.factory.makePerson(
- member_of=[getUtility(ILaunchpadCelebrities).buildd_admin])
+ member_of=[getUtility(ILaunchpadCelebrities).buildd_admin]
+ )
def test_retry_build(self):
# The requester of a build can retry it.
@@ -272,10 +374,14 @@ class TestSnapBuildOperations(BrowserTestCase):
user = self.factory.makePerson()
browser = self.getViewBrowser(self.build, user=user)
self.assertRaises(
- LinkNotFoundError, browser.getLink, "Retry this build")
+ LinkNotFoundError, browser.getLink, "Retry this build"
+ )
self.assertRaises(
- Unauthorized, self.getUserBrowser, self.build_url + "/+retry",
- user=user)
+ Unauthorized,
+ self.getUserBrowser,
+ self.build_url + "/+retry",
+ user=user,
+ )
def test_retry_build_wrong_state(self):
# If the build isn't in an unsuccessful terminal state, you can't
@@ -283,7 +389,8 @@ class TestSnapBuildOperations(BrowserTestCase):
self.build.updateStatus(BuildStatus.FULLYBUILT)
browser = self.getViewBrowser(self.build, user=self.requester)
self.assertRaises(
- LinkNotFoundError, browser.getLink, "Retry this build")
+ LinkNotFoundError, browser.getLink, "Retry this build"
+ )
def test_cancel_build(self):
# The requester of a build can cancel it.
@@ -305,8 +412,11 @@ class TestSnapBuildOperations(BrowserTestCase):
browser = self.getViewBrowser(self.build, user=user)
self.assertRaises(LinkNotFoundError, browser.getLink, "Cancel build")
self.assertRaises(
- Unauthorized, self.getUserBrowser, self.build_url + "/+cancel",
- user=user)
+ Unauthorized,
+ self.getUserBrowser,
+ self.build_url + "/+cancel",
+ user=user,
+ )
def test_cancel_build_wrong_state(self):
# If the build isn't queued, you can't cancel it.
@@ -337,7 +447,8 @@ class TestSnapBuildOperations(BrowserTestCase):
browser.getControl("Rescore build").click()
self.assertEqual(
"Invalid integer data",
- extract_text(find_tags_by_class(browser.contents, "message")[1]))
+ extract_text(find_tags_by_class(browser.contents, "message")[1]),
+ )
def test_rescore_build_not_admin(self):
# A non-admin user cannot cancel a build.
@@ -347,8 +458,11 @@ class TestSnapBuildOperations(BrowserTestCase):
browser = self.getViewBrowser(self.build, user=user)
self.assertRaises(LinkNotFoundError, browser.getLink, "Rescore build")
self.assertRaises(
- Unauthorized, self.getUserBrowser, self.build_url + "/+rescore",
- user=user)
+ Unauthorized,
+ self.getUserBrowser,
+ self.build_url + "/+rescore",
+ user=user,
+ )
def test_rescore_build_wrong_state(self):
# If the build isn't NEEDSBUILD, you can't rescore it.
@@ -365,20 +479,31 @@ class TestSnapBuildOperations(BrowserTestCase):
with person_logged_in(self.requester):
self.build.cancel()
browser = self.getViewBrowser(
- self.build, "+rescore", user=self.buildd_admin)
+ self.build, "+rescore", user=self.buildd_admin
+ )
self.assertEqual(self.build_url, browser.url)
- self.assertThat(browser.contents, soupmatchers.HTMLContains(
- soupmatchers.Tag(
- "notification", "div", attrs={"class": "warning message"},
- text="Cannot rescore this build because it is not queued.")))
+ self.assertThat(
+ browser.contents,
+ soupmatchers.HTMLContains(
+ soupmatchers.Tag(
+ "notification",
+ "div",
+ attrs={"class": "warning message"},
+ text="Cannot rescore this build because it is not queued.",
+ )
+ ),
+ )
def setUpStoreUpload(self):
self.pushConfig(
- "snappy", store_url="http://sca.example/",
- store_upload_url="http://updown.example/")
+ "snappy",
+ store_url="http://sca.example/",
+ store_upload_url="http://updown.example/",
+ )
with admin_logged_in():
snappyseries = self.factory.makeSnappySeries(
- usable_distro_series=[self.build.snap.distro_series])
+ usable_distro_series=[self.build.snap.distro_series]
+ )
with person_logged_in(self.requester):
self.build.snap.store_series = snappyseries
self.build.snap.store_name = self.factory.getUniqueUnicode()
@@ -391,7 +516,8 @@ class TestSnapBuildOperations(BrowserTestCase):
self.build.updateStatus(BuildStatus.FULLYBUILT)
self.factory.makeSnapFile(
snapbuild=self.build,
- libraryfile=self.factory.makeLibraryFileAlias(db_only=True))
+ libraryfile=self.factory.makeLibraryFileAlias(db_only=True),
+ )
browser = self.getViewBrowser(self.build, user=self.requester)
browser.getControl("Upload this package to the store").click()
self.assertEqual(self.build_url, browser.url)
@@ -401,7 +527,8 @@ class TestSnapBuildOperations(BrowserTestCase):
self.assertEqual(self.build, job.snapbuild)
self.assertEqual(
"An upload has been scheduled and will run as soon as possible.",
- extract_text(find_tags_by_class(browser.contents, "message")[0]))
+ extract_text(find_tags_by_class(browser.contents, "message")[0]),
+ )
def test_store_upload_retry(self):
# A build with a previously-failed store upload can have the upload
@@ -410,7 +537,8 @@ class TestSnapBuildOperations(BrowserTestCase):
self.build.updateStatus(BuildStatus.FULLYBUILT)
self.factory.makeSnapFile(
snapbuild=self.build,
- libraryfile=self.factory.makeLibraryFileAlias(db_only=True))
+ libraryfile=self.factory.makeLibraryFileAlias(db_only=True),
+ )
old_job = getUtility(ISnapStoreUploadJobSource).create(self.build)
removeSecurityProxy(old_job).job._status = JobStatus.FAILED
browser = self.getViewBrowser(self.build, user=self.requester)
@@ -422,7 +550,8 @@ class TestSnapBuildOperations(BrowserTestCase):
self.assertEqual(self.build, job.snapbuild)
self.assertEqual(
"An upload has been scheduled and will run as soon as possible.",
- extract_text(find_tags_by_class(browser.contents, "message")[0]))
+ extract_text(find_tags_by_class(browser.contents, "message")[0]),
+ )
def test_store_upload_error_notifies(self):
# If a build cannot be scheduled for uploading to the store, we
@@ -434,20 +563,24 @@ class TestSnapBuildOperations(BrowserTestCase):
self.assertEqual(self.build_url, browser.url)
login(ANONYMOUS)
self.assertEqual(
- [], list(getUtility(ISnapStoreUploadJobSource).iterReady()))
+ [], list(getUtility(ISnapStoreUploadJobSource).iterReady())
+ )
self.assertEqual(
"Cannot upload this package because it has no files.",
- extract_text(find_tags_by_class(browser.contents, "message")[0]))
+ extract_text(find_tags_by_class(browser.contents, "message")[0]),
+ )
def test_builder_history(self):
Store.of(self.build).flush()
self.build.updateStatus(
- BuildStatus.FULLYBUILT, builder=self.factory.makeBuilder())
+ BuildStatus.FULLYBUILT, builder=self.factory.makeBuilder()
+ )
title = self.build.title
browser = self.getViewBrowser(self.build.builder, "+history")
self.assertTextMatchesExpressionIgnoreWhitespace(
"Build history.*%s" % title,
- extract_text(find_main_content(browser.contents)))
+ extract_text(find_main_content(browser.contents)),
+ )
self.assertEqual(self.build_url, browser.getLink(title).url)
def makeBuildingSnap(self, archive=None):
diff --git a/lib/lp/snappy/browser/tests/test_snaplisting.py b/lib/lp/snappy/browser/tests/test_snaplisting.py
index 31e6394..a242f19 100644
--- a/lib/lp/snappy/browser/tests/test_snaplisting.py
+++ b/lib/lp/snappy/browser/tests/test_snaplisting.py
@@ -3,26 +3,16 @@
"""Test snap package listings."""
-from datetime import (
- datetime,
- timedelta,
- )
+from datetime import datetime, timedelta
from functools import partial
import pytz
import soupmatchers
-from testtools.matchers import (
- MatchesAll,
- Not,
- )
+from testtools.matchers import MatchesAll, Not
from zope.security.proxy import removeSecurityProxy
from lp.code.tests.helpers import GitHostingFixture
-from lp.services.database.constants import (
- ONE_DAY_AGO,
- SEVEN_DAYS_AGO,
- UTC_NOW,
- )
+from lp.services.database.constants import ONE_DAY_AGO, SEVEN_DAYS_AGO, UTC_NOW
from lp.services.features.testing import FeatureFixture
from lp.services.webapp import canonical_url
from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS
@@ -32,7 +22,7 @@ from lp.testing import (
login,
person_logged_in,
record_two_runs,
- )
+)
from lp.testing.layers import LaunchpadFunctionalLayer
from lp.testing.matchers import HasQueryCount
from lp.testing.views import create_initialized_view
@@ -42,16 +32,21 @@ class TestSnapListing(BrowserTestCase):
layer = LaunchpadFunctionalLayer
- def assertSnapsLink(self, context, link_text, link_has_context=False,
- **kwargs):
+ def assertSnapsLink(
+ self, context, link_text, link_has_context=False, **kwargs
+ ):
if link_has_context:
expected_href = canonical_url(context, view_name="+snaps")
else:
expected_href = "+snaps"
matcher = soupmatchers.HTMLContains(
soupmatchers.Tag(
- "View snap packages link", "a", text=link_text,
- attrs={"href": expected_href}))
+ "View snap packages link",
+ "a",
+ text=link_text,
+ attrs={"href": expected_href},
+ )
+ )
self.assertThat(self.getViewBrowser(context).contents, Not(matcher))
login(ANONYMOUS)
self.factory.makeSnap(**kwargs)
@@ -75,24 +70,32 @@ class TestSnapListing(BrowserTestCase):
def test_person_links_to_snaps(self):
person = self.factory.makePerson()
self.assertSnapsLink(
- person, "View snap packages", link_has_context=True,
- registrant=person, owner=person)
+ person,
+ "View snap packages",
+ link_has_context=True,
+ registrant=person,
+ owner=person,
+ )
def test_project_links_to_snaps(self):
project = self.factory.makeProduct()
[ref] = self.factory.makeGitRefs(target=project)
self.assertSnapsLink(
- project, "View snap packages", link_has_context=True, git_ref=ref)
+ project, "View snap packages", link_has_context=True, git_ref=ref
+ )
def test_branch_snap_listing(self):
# We can see snap packages for a Bazaar branch.
branch = self.factory.makeAnyBranch()
self.factory.makeSnap(branch=branch)
text = self.getMainText(branch, "+snaps")
- self.assertTextMatchesExpressionIgnoreWhitespace("""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ """
Snap packages for lp:.*
Name Owner Registered
- snap-name.* Team Name.* .*""", text)
+ snap-name.* Team Name.* .*""",
+ text,
+ )
def test_git_repository_snap_listing(self):
# We can see snap packages for a Git repository.
@@ -100,57 +103,79 @@ class TestSnapListing(BrowserTestCase):
[ref] = self.factory.makeGitRefs(repository=repository)
self.factory.makeSnap(git_ref=ref)
text = self.getMainText(repository, "+snaps")
- self.assertTextMatchesExpressionIgnoreWhitespace("""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ """
Snap packages for lp:~.*
Name Owner Registered
- snap-name.* Team Name.* .*""", text)
+ snap-name.* Team Name.* .*""",
+ text,
+ )
def test_git_ref_snap_listing(self):
# We can see snap packages for a Git reference.
[ref] = self.factory.makeGitRefs()
self.factory.makeSnap(git_ref=ref)
text = self.getMainText(ref, "+snaps")
- self.assertTextMatchesExpressionIgnoreWhitespace("""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ """
Snap packages for ~.*:.*
Name Owner Registered
- snap-name.* Team Name.* .*""", text)
+ snap-name.* Team Name.* .*""",
+ text,
+ )
def test_person_snap_listing(self):
# We can see snap packages for a person.
owner = self.factory.makePerson(displayname="Snap Owner")
self.factory.makeSnap(
- registrant=owner, owner=owner, branch=self.factory.makeAnyBranch(),
- date_created=SEVEN_DAYS_AGO)
+ registrant=owner,
+ owner=owner,
+ branch=self.factory.makeAnyBranch(),
+ date_created=SEVEN_DAYS_AGO,
+ )
[ref] = self.factory.makeGitRefs()
self.factory.makeSnap(
- registrant=owner, owner=owner, git_ref=ref,
- date_created=ONE_DAY_AGO)
+ registrant=owner,
+ owner=owner,
+ git_ref=ref,
+ date_created=ONE_DAY_AGO,
+ )
remote_ref = self.factory.makeGitRefRemote()
self.factory.makeSnap(
- registrant=owner, owner=owner, git_ref=remote_ref,
- date_created=UTC_NOW)
+ registrant=owner,
+ owner=owner,
+ git_ref=remote_ref,
+ date_created=UTC_NOW,
+ )
text = self.getMainText(owner, "+snaps")
- self.assertTextMatchesExpressionIgnoreWhitespace("""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ """
Snap packages for Snap Owner
Name Source Registered
snap-name.* http://.* path-.* .*
snap-name.* ~.*:.* .*
- snap-name.* lp:.* .*""", text)
+ snap-name.* lp:.* .*""",
+ text,
+ )
def test_project_snap_listing(self):
# We can see snap packages for a project.
project = self.factory.makeProduct(displayname="Snappable")
self.factory.makeSnap(
branch=self.factory.makeProductBranch(product=project),
- date_created=ONE_DAY_AGO)
+ date_created=ONE_DAY_AGO,
+ )
[ref] = self.factory.makeGitRefs(target=project)
self.factory.makeSnap(git_ref=ref, date_created=UTC_NOW)
text = self.getMainText(project, "+snaps")
- self.assertTextMatchesExpressionIgnoreWhitespace("""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ """
Snap packages for Snappable
Name Owner Source Registered
snap-name.* Team Name.* ~.*:.* .*
- snap-name.* Team Name.* lp:.* .*""", text)
+ snap-name.* Team Name.* lp:.* .*""",
+ text,
+ )
def test_project_private_snap_listing(self):
# Only users with permission can see private snap packages in the list
@@ -162,9 +187,12 @@ class TestSnapListing(BrowserTestCase):
someone_else = self.factory.makePerson()
private_snap = self.factory.makeSnap(
name="private-snap",
- private=True, registrant=private_owner, owner=private_owner,
+ private=True,
+ registrant=private_owner,
+ owner=private_owner,
branch=self.factory.makeProductBranch(product=project),
- date_created=ONE_DAY_AGO)
+ date_created=ONE_DAY_AGO,
+ )
with person_logged_in(private_owner):
private_snap.subscribe(user_with_permission, private_owner)
[ref] = self.factory.makeGitRefs(target=project)
@@ -183,18 +211,21 @@ class TestSnapListing(BrowserTestCase):
# private_owner: full_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- full_list, self.getMainText(project, "+snaps", user=private_owner))
+ full_list, self.getMainText(project, "+snaps", user=private_owner)
+ )
# user_with_permission: full_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
full_list,
- self.getMainText(project, "+snaps", user=user_with_permission))
+ self.getMainText(project, "+snaps", user=user_with_permission),
+ )
# someone_else: public_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list,
- self.getMainText(project, "+snaps", user=someone_else))
+ public_list, self.getMainText(project, "+snaps", user=someone_else)
+ )
# Not logged in: public_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(project, "+snaps", user=None))
+ public_list, self.getMainText(project, "+snaps", user=None)
+ )
def test_person_private_snap_listing(self):
self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
@@ -203,14 +234,21 @@ class TestSnapListing(BrowserTestCase):
someone_else = self.factory.makePerson()
private_snap = self.factory.makeSnap(
name="private-snap",
- private=True, registrant=private_owner, owner=private_owner,
- date_created=ONE_DAY_AGO)
+ private=True,
+ registrant=private_owner,
+ owner=private_owner,
+ date_created=ONE_DAY_AGO,
+ )
with person_logged_in(private_owner):
private_snap.subscribe(user_with_permission, private_owner)
[ref] = self.factory.makeGitRefs()
self.factory.makeSnap(
- private=False, registrant=private_owner, owner=private_owner,
- git_ref=ref, date_created=UTC_NOW)
+ private=False,
+ registrant=private_owner,
+ owner=private_owner,
+ git_ref=ref,
+ date_created=UTC_NOW,
+ )
full_list = """
Snap packages for Random-user
@@ -225,19 +263,25 @@ class TestSnapListing(BrowserTestCase):
# private_owner: full_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- full_list, self.getMainText(private_owner, "+snaps",
- user=private_owner))
+ full_list,
+ self.getMainText(private_owner, "+snaps", user=private_owner),
+ )
# user_with_permission: full_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- full_list, self.getMainText(private_owner, "+snaps",
- user=user_with_permission))
+ full_list,
+ self.getMainText(
+ private_owner, "+snaps", user=user_with_permission
+ ),
+ )
# someone_else: public_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(private_owner, "+snaps",
- user=someone_else))
+ public_list,
+ self.getMainText(private_owner, "+snaps", user=someone_else),
+ )
# Not logged in: public_list.
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(private_owner, "+snaps", user=None))
+ public_list, self.getMainText(private_owner, "+snaps", user=None)
+ )
def test_branch_private_snap_listing(self):
# Only certain users can see private snaps on branch listing.
@@ -247,13 +291,20 @@ class TestSnapListing(BrowserTestCase):
someone_else = self.factory.makePerson()
branch = self.factory.makeAnyBranch()
private_snap = self.factory.makeSnap(
- private=True, name="private-snap",
- owner=private_owner, registrant=private_owner, branch=branch)
+ private=True,
+ name="private-snap",
+ owner=private_owner,
+ registrant=private_owner,
+ branch=branch,
+ )
with person_logged_in(private_owner):
private_snap.subscribe(user_with_permission, private_owner)
self.factory.makeSnap(
- private=False, owner=private_owner, registrant=private_owner,
- branch=branch)
+ private=False,
+ owner=private_owner,
+ registrant=private_owner,
+ branch=branch,
+ )
full_list = """
Snap packages for lp:.*
Name Owner Registered
@@ -265,14 +316,18 @@ class TestSnapListing(BrowserTestCase):
snap-name.* Random-user.* .*"""
self.assertTextMatchesExpressionIgnoreWhitespace(
- full_list, self.getMainText(branch, "+snaps", user=private_owner))
+ full_list, self.getMainText(branch, "+snaps", user=private_owner)
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- full_list, self.getMainText(branch, "+snaps",
- user=user_with_permission))
+ full_list,
+ self.getMainText(branch, "+snaps", user=user_with_permission),
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(branch, "+snaps", user=someone_else))
+ public_list, self.getMainText(branch, "+snaps", user=someone_else)
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(branch, "+snaps", user=None))
+ public_list, self.getMainText(branch, "+snaps", user=None)
+ )
def test_git_repository_private_snap_listing(self):
# Only certain users can see private snaps on git repo listing.
@@ -283,13 +338,20 @@ class TestSnapListing(BrowserTestCase):
repository = self.factory.makeGitRepository()
[ref] = self.factory.makeGitRefs(repository=repository)
private_snap = self.factory.makeSnap(
- private=True, name="private-snap",
- owner=private_owner, registrant=private_owner, git_ref=ref)
+ private=True,
+ name="private-snap",
+ owner=private_owner,
+ registrant=private_owner,
+ git_ref=ref,
+ )
with person_logged_in(private_owner):
private_snap.subscribe(user_with_permission, private_owner)
self.factory.makeSnap(
- private=False, owner=private_owner, registrant=private_owner,
- git_ref=ref)
+ private=False,
+ owner=private_owner,
+ registrant=private_owner,
+ git_ref=ref,
+ )
full_list = """
Snap packages for lp:~.*
@@ -303,15 +365,19 @@ class TestSnapListing(BrowserTestCase):
self.assertTextMatchesExpressionIgnoreWhitespace(
full_list,
- self.getMainText(repository, "+snaps", user=private_owner))
+ self.getMainText(repository, "+snaps", user=private_owner),
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
full_list,
- self.getMainText(repository, "+snaps", user=user_with_permission))
+ self.getMainText(repository, "+snaps", user=user_with_permission),
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
public_list,
- self.getMainText(repository, "+snaps", user=someone_else))
+ self.getMainText(repository, "+snaps", user=someone_else),
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(repository, "+snaps", user=None))
+ public_list, self.getMainText(repository, "+snaps", user=None)
+ )
def test_git_ref_private_snap_listing(self):
# Only certain users can see private snaps on git ref listing.
@@ -322,13 +388,20 @@ class TestSnapListing(BrowserTestCase):
repository = self.factory.makeGitRepository()
[ref] = self.factory.makeGitRefs(repository=repository)
private_snap = self.factory.makeSnap(
- private=True, name="private-snap",
- owner=private_owner, registrant=private_owner, git_ref=ref)
+ private=True,
+ name="private-snap",
+ owner=private_owner,
+ registrant=private_owner,
+ git_ref=ref,
+ )
with person_logged_in(private_owner):
private_snap.subscribe(user_with_permission, private_owner)
self.factory.makeSnap(
- private=False, owner=private_owner, registrant=private_owner,
- git_ref=ref)
+ private=False,
+ owner=private_owner,
+ registrant=private_owner,
+ git_ref=ref,
+ )
full_list = """
Snap packages for ~.*:.*
@@ -341,19 +414,24 @@ class TestSnapListing(BrowserTestCase):
snap-name.* Random-user.* .*"""
self.assertTextMatchesExpressionIgnoreWhitespace(
- full_list, self.getMainText(ref, "+snaps", user=private_owner))
+ full_list, self.getMainText(ref, "+snaps", user=private_owner)
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
full_list,
- self.getMainText(ref, "+snaps", user=user_with_permission))
+ self.getMainText(ref, "+snaps", user=user_with_permission),
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(ref, "+snaps", user=someone_else))
+ public_list, self.getMainText(ref, "+snaps", user=someone_else)
+ )
self.assertTextMatchesExpressionIgnoreWhitespace(
- public_list, self.getMainText(ref, "+snaps", user=None))
+ public_list, self.getMainText(ref, "+snaps", user=None)
+ )
def assertSnapsQueryCount(self, context, item_creator):
self.pushConfig("launchpad", default_batch_size=10)
recorder1, recorder2 = record_two_runs(
- lambda: self.getMainText(context, "+snaps"), item_creator, 5)
+ lambda: self.getMainText(context, "+snaps"), item_creator, 5
+ )
self.assertThat(recorder2, HasQueryCount.byEquality(recorder1))
def test_branch_query_count(self):
@@ -408,11 +486,13 @@ class TestSnapListing(BrowserTestCase):
project = self.factory.makeProduct()
if (i % 2) == 0:
branch = self.factory.makeProductBranch(
- owner=person, product=project)
+ owner=person, product=project
+ )
self.factory.makeSnap(branch=branch)
else:
[ref] = self.factory.makeGitRefs(
- owner=person, target=project)
+ owner=person, target=project
+ )
self.factory.makeSnap(git_ref=ref)
self.assertSnapsQueryCount(person, create_snap)
@@ -439,33 +519,46 @@ class TestSnapListing(BrowserTestCase):
def makeSnapsAndMatchers(self, create_snap, count, start_time):
snaps = [create_snap() for i in range(count)]
for i, snap in enumerate(snaps):
- removeSecurityProxy(snap).date_last_modified = (
- start_time - timedelta(seconds=i))
+ removeSecurityProxy(
+ snap
+ ).date_last_modified = start_time - timedelta(seconds=i)
return [
soupmatchers.Tag(
- "snap link", "a", text=snap.name,
+ "snap link",
+ "a",
+ text=snap.name,
attrs={
- "href": canonical_url(snap, path_only_if_possible=True)})
- for snap in snaps]
+ "href": canonical_url(snap, path_only_if_possible=True)
+ },
+ )
+ for snap in snaps
+ ]
def assertBatches(self, context, link_matchers, batched, start, size):
view = create_initialized_view(context, "+snaps")
listing_tag = soupmatchers.Tag(
- "snap listing", "table", attrs={"class": "listing sortable"})
+ "snap listing", "table", attrs={"class": "listing sortable"}
+ )
batch_nav_tag = soupmatchers.Tag(
- "batch nav links", "td",
- attrs={"class": "batch-navigation-links"})
+ "batch nav links", "td", attrs={"class": "batch-navigation-links"}
+ )
present_links = ([batch_nav_tag] if batched else []) + [
- matcher for i, matcher in enumerate(link_matchers)
- if i in range(start, start + size)]
+ matcher
+ for i, matcher in enumerate(link_matchers)
+ if i in range(start, start + size)
+ ]
absent_links = ([] if batched else [batch_nav_tag]) + [
- matcher for i, matcher in enumerate(link_matchers)
- if i not in range(start, start + size)]
+ matcher
+ for i, matcher in enumerate(link_matchers)
+ if i not in range(start, start + size)
+ ]
self.assertThat(
view.render(),
MatchesAll(
soupmatchers.HTMLContains(listing_tag, *present_links),
- Not(soupmatchers.HTMLContains(*absent_links))))
+ Not(soupmatchers.HTMLContains(*absent_links)),
+ ),
+ )
def test_branch_batches_snaps(self):
branch = self.factory.makeAnyBranch()
@@ -473,8 +566,11 @@ class TestSnapListing(BrowserTestCase):
now = datetime.now(pytz.UTC)
link_matchers = self.makeSnapsAndMatchers(create_snap, 3, now)
self.assertBatches(branch, link_matchers, False, 0, 3)
- link_matchers.extend(self.makeSnapsAndMatchers(
- create_snap, 7, now - timedelta(seconds=3)))
+ link_matchers.extend(
+ self.makeSnapsAndMatchers(
+ create_snap, 7, now - timedelta(seconds=3)
+ )
+ )
self.assertBatches(branch, link_matchers, True, 0, 5)
def test_git_repository_batches_snaps(self):
@@ -484,8 +580,11 @@ class TestSnapListing(BrowserTestCase):
now = datetime.now(pytz.UTC)
link_matchers = self.makeSnapsAndMatchers(create_snap, 3, now)
self.assertBatches(repository, link_matchers, False, 0, 3)
- link_matchers.extend(self.makeSnapsAndMatchers(
- create_snap, 7, now - timedelta(seconds=3)))
+ link_matchers.extend(
+ self.makeSnapsAndMatchers(
+ create_snap, 7, now - timedelta(seconds=3)
+ )
+ )
self.assertBatches(repository, link_matchers, True, 0, 5)
def test_git_ref_batches_snaps(self):
@@ -494,19 +593,26 @@ class TestSnapListing(BrowserTestCase):
now = datetime.now(pytz.UTC)
link_matchers = self.makeSnapsAndMatchers(create_snap, 3, now)
self.assertBatches(ref, link_matchers, False, 0, 3)
- link_matchers.extend(self.makeSnapsAndMatchers(
- create_snap, 7, now - timedelta(seconds=3)))
+ link_matchers.extend(
+ self.makeSnapsAndMatchers(
+ create_snap, 7, now - timedelta(seconds=3)
+ )
+ )
self.assertBatches(ref, link_matchers, True, 0, 5)
def test_person_batches_snaps(self):
owner = self.factory.makePerson()
create_snap = partial(
- self.factory.makeSnap, registrant=owner, owner=owner)
+ self.factory.makeSnap, registrant=owner, owner=owner
+ )
now = datetime.now(pytz.UTC)
link_matchers = self.makeSnapsAndMatchers(create_snap, 3, now)
self.assertBatches(owner, link_matchers, False, 0, 3)
- link_matchers.extend(self.makeSnapsAndMatchers(
- create_snap, 7, now - timedelta(seconds=3)))
+ link_matchers.extend(
+ self.makeSnapsAndMatchers(
+ create_snap, 7, now - timedelta(seconds=3)
+ )
+ )
self.assertBatches(owner, link_matchers, True, 0, 5)
def test_project_batches_snaps(self):
@@ -516,6 +622,9 @@ class TestSnapListing(BrowserTestCase):
now = datetime.now(pytz.UTC)
link_matchers = self.makeSnapsAndMatchers(create_snap, 3, now)
self.assertBatches(project, link_matchers, False, 0, 3)
- link_matchers.extend(self.makeSnapsAndMatchers(
- create_snap, 7, now - timedelta(seconds=3)))
+ link_matchers.extend(
+ self.makeSnapsAndMatchers(
+ create_snap, 7, now - timedelta(seconds=3)
+ )
+ )
self.assertBatches(project, link_matchers, True, 0, 5)
diff --git a/lib/lp/snappy/browser/tests/test_snapsubscription.py b/lib/lp/snappy/browser/tests/test_snapsubscription.py
index 575c0d6..b196192 100644
--- a/lib/lp/snappy/browser/tests/test_snapsubscription.py
+++ b/lib/lp/snappy/browser/tests/test_snapsubscription.py
@@ -11,18 +11,14 @@ from lp.registry.enums import BranchSharingPolicy
from lp.services.features.testing import FeatureFixture
from lp.services.webapp import canonical_url
from lp.snappy.interfaces.snap import SNAP_TESTING_FLAGS
-from lp.testing import (
- admin_logged_in,
- BrowserTestCase,
- person_logged_in,
- )
+from lp.testing import BrowserTestCase, admin_logged_in, person_logged_in
from lp.testing.layers import DatabaseFunctionalLayer
from lp.testing.pages import (
extract_text,
find_main_content,
find_tag_by_id,
find_tags_by_class,
- )
+)
class BaseTestSnapView(BrowserTestCase):
@@ -33,53 +29,70 @@ class BaseTestSnapView(BrowserTestCase):
super().setUp()
self.useFixture(FeatureFixture(SNAP_TESTING_FLAGS))
self.useFixture(FakeLogger())
- self.person = self.factory.makePerson(name='snap-owner')
+ self.person = self.factory.makePerson(name="snap-owner")
def makeSnap(self, project=None, **kwargs):
[ref] = self.factory.makeGitRefs(
- owner=self.person, target=self.person, name="snap-repository",
- paths=["refs/heads/master"])
+ owner=self.person,
+ target=self.person,
+ name="snap-repository",
+ paths=["refs/heads/master"],
+ )
if project is None:
project = self.factory.makeProduct(
- owner=self.person, registrant=self.person)
+ owner=self.person, registrant=self.person
+ )
return self.factory.makeSnap(
- registrant=self.person, owner=self.person, name="snap-name",
- git_ref=ref, project=project, **kwargs)
+ registrant=self.person,
+ owner=self.person,
+ name="snap-name",
+ git_ref=ref,
+ project=project,
+ **kwargs,
+ )
def getSubscriptionPortletText(self, browser):
return extract_text(
- find_tag_by_id(browser.contents, 'portlet-subscribers'))
+ find_tag_by_id(browser.contents, "portlet-subscribers")
+ )
def extractMainText(self, browser):
return extract_text(find_main_content(browser.contents))
def extractInfoMessageContent(self, browser):
return extract_text(
- find_tags_by_class(browser.contents, 'informational message')[0])
+ find_tags_by_class(browser.contents, "informational message")[0]
+ )
class TestPublicSnapSubscriptionViews(BaseTestSnapView):
-
def test_subscribe_self(self):
snap = self.makeSnap()
another_user = self.factory.makePerson(name="another-user")
browser = self.getViewBrowser(snap, user=another_user)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Subscribe yourself
Subscribe someone else
Subscribers
Snap-owner
- """, self.getSubscriptionPortletText(browser))
+ """,
+ self.getSubscriptionPortletText(browser),
+ )
# Go to "subscribe myself" page, and click the button.
browser = self.getViewBrowser(
- snap, view_name="+subscribe", user=another_user)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ snap, view_name="+subscribe", user=another_user
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Subscribe to snap recipe
Snap packages
snap-name
Subscribe to snap recipe or Cancel
- """, self.extractMainText(browser))
+ """,
+ self.extractMainText(browser),
+ )
browser.getControl("Subscribe").click()
# We should be redirected back to snap page.
@@ -87,13 +100,16 @@ class TestPublicSnapSubscriptionViews(BaseTestSnapView):
self.assertEqual(canonical_url(snap), browser.url)
# And the new user should be listed in the subscribers list.
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit your subscription
Subscribe someone else
Subscribers
Another-user
Snap-owner
- """, self.getSubscriptionPortletText(browser))
+ """,
+ self.getSubscriptionPortletText(browser),
+ )
def test_unsubscribe_self(self):
snap = self.makeSnap()
@@ -102,17 +118,23 @@ class TestPublicSnapSubscriptionViews(BaseTestSnapView):
snap.subscribe(another_user, snap.owner)
subscription = snap.getSubscription(another_user)
browser = self.getViewBrowser(subscription, user=another_user)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit subscription to snap recipe for Another-user
Snap packages
snap-name
If you unsubscribe from a snap recipe it will no longer show up on
your personal pages. or Cancel
- """, self.extractMainText(browser))
+ """,
+ self.extractMainText(browser),
+ )
browser.getControl("Unsubscribe").click()
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Another-user has been unsubscribed from this snap recipe.
- """, self.extractInfoMessageContent(browser))
+ """,
+ self.extractInfoMessageContent(browser),
+ )
with person_logged_in(self.person):
self.assertIsNone(snap.getSubscription(another_user))
@@ -120,17 +142,22 @@ class TestPublicSnapSubscriptionViews(BaseTestSnapView):
snap = self.makeSnap()
another_user = self.factory.makePerson(name="another-user")
browser = self.getViewBrowser(snap, user=snap.owner)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit your subscription
Subscribe someone else
Subscribers
Snap-owner
- """, self.getSubscriptionPortletText(browser))
+ """,
+ self.getSubscriptionPortletText(browser),
+ )
# Go to "subscribe" page, and click the button.
browser = self.getViewBrowser(
- snap, view_name="+addsubscriber", user=another_user)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ snap, view_name="+addsubscriber", user=another_user
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Subscribe to snap recipe
Snap packages
snap-name
@@ -140,8 +167,10 @@ class TestPublicSnapSubscriptionViews(BaseTestSnapView):
The person subscribed to the related snap recipe.
or
Cancel
- """, self.extractMainText(browser))
- browser.getControl(name="field.person").value = 'another-user'
+ """,
+ self.extractMainText(browser),
+ )
+ browser.getControl(name="field.person").value = "another-user"
browser.getControl("Subscribe").click()
# We should be redirected back to snap page.
@@ -149,13 +178,16 @@ class TestPublicSnapSubscriptionViews(BaseTestSnapView):
self.assertEqual(canonical_url(snap), browser.url)
# And the new user should be listed in the subscribers list.
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit your subscription
Subscribe someone else
Subscribers
Another-user
Snap-owner
- """, self.getSubscriptionPortletText(browser))
+ """,
+ self.getSubscriptionPortletText(browser),
+ )
def test_unsubscribe_someone_else(self):
snap = self.makeSnap()
@@ -165,45 +197,61 @@ class TestPublicSnapSubscriptionViews(BaseTestSnapView):
subscription = snap.getSubscription(another_user)
browser = self.getViewBrowser(subscription, user=snap.owner)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit subscription to snap recipe for Another-user
Snap packages
snap-name
If you unsubscribe from a snap recipe it will no longer show up on
your personal pages. or Cancel
- """, self.extractMainText(browser))
+ """,
+ self.extractMainText(browser),
+ )
browser.getControl("Unsubscribe").click()
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Another-user has been unsubscribed from this snap recipe.
- """, self.extractInfoMessageContent(browser))
+ """,
+ self.extractInfoMessageContent(browser),
+ )
with person_logged_in(self.person):
self.assertIsNone(snap.getSubscription(another_user))
class TestPrivateSnapSubscriptionViews(BaseTestSnapView):
-
def makePrivateSnap(self, **kwargs):
project = self.factory.makeProduct(
- owner=self.person, registrant=self.person,
+ owner=self.person,
+ registrant=self.person,
information_type=InformationType.PROPRIETARY,
- branch_sharing_policy=BranchSharingPolicy.PROPRIETARY)
+ branch_sharing_policy=BranchSharingPolicy.PROPRIETARY,
+ )
return self.makeSnap(
- information_type=InformationType.PROPRIETARY,
- project=project)
+ information_type=InformationType.PROPRIETARY, project=project
+ )
def test_cannot_subscribe_to_private_snap(self):
snap = self.makePrivateSnap()
another_user = self.factory.makePerson(name="another-user")
# Unsubscribed user should not see the snap page.
self.assertRaises(
- Unauthorized, self.getViewBrowser, snap, user=another_user)
+ Unauthorized, self.getViewBrowser, snap, user=another_user
+ )
# Nor the subscribe pages.
self.assertRaises(
- Unauthorized, self.getViewBrowser,
- snap, view_name="+subscribe", user=another_user)
+ Unauthorized,
+ self.getViewBrowser,
+ snap,
+ view_name="+subscribe",
+ user=another_user,
+ )
self.assertRaises(
- Unauthorized, self.getViewBrowser,
- snap, view_name="+addsubscriber", user=another_user)
+ Unauthorized,
+ self.getViewBrowser,
+ snap,
+ view_name="+addsubscriber",
+ user=another_user,
+ )
def test_snap_owner_can_subscribe_someone_to_private_snap(self):
snap = self.makePrivateSnap()
@@ -211,8 +259,10 @@ class TestPrivateSnapSubscriptionViews(BaseTestSnapView):
# Go to "subscribe" page, and click the button.
browser = self.getViewBrowser(
- snap, view_name="+addsubscriber", user=self.person)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ snap, view_name="+addsubscriber", user=self.person
+ )
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Subscribe to snap recipe
Snap packages
snap-name
@@ -222,20 +272,25 @@ class TestPrivateSnapSubscriptionViews(BaseTestSnapView):
The person subscribed to the related snap recipe.
or
Cancel
- """, self.extractMainText(browser))
- browser.getControl(name="field.person").value = 'another-user'
+ """,
+ self.extractMainText(browser),
+ )
+ browser.getControl(name="field.person").value = "another-user"
browser.getControl("Subscribe").click()
# Now the new user should be listed in the subscribers list,
# and have access to the snap page.
browser = self.getViewBrowser(snap, user=another_user)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit your subscription
Subscribe someone else
Subscribers
Another-user
Snap-owner
- """, self.getSubscriptionPortletText(browser))
+ """,
+ self.getSubscriptionPortletText(browser),
+ )
def test_unsubscribe_self(self):
snap = self.makePrivateSnap()
@@ -244,16 +299,22 @@ class TestPrivateSnapSubscriptionViews(BaseTestSnapView):
snap.subscribe(another_user, self.person)
subscription = snap.getSubscription(another_user)
browser = self.getViewBrowser(subscription, user=another_user)
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Edit subscription to snap recipe for Another-user
Snap packages
snap-name
If you unsubscribe from a snap recipe it will no longer show up on
your personal pages. or Cancel
- """, self.extractMainText(browser))
+ """,
+ self.extractMainText(browser),
+ )
browser.getControl("Unsubscribe").click()
- self.assertTextMatchesExpressionIgnoreWhitespace(r"""
+ self.assertTextMatchesExpressionIgnoreWhitespace(
+ r"""
Another-user has been unsubscribed from this snap recipe.
- """, self.extractInfoMessageContent(browser))
+ """,
+ self.extractInfoMessageContent(browser),
+ )
with person_logged_in(self.person):
self.assertIsNone(snap.getSubscription(another_user))
diff --git a/lib/lp/snappy/browser/widgets/snaparchive.py b/lib/lp/snappy/browser/widgets/snaparchive.py
index 538123e..a1400c4 100644
--- a/lib/lp/snappy/browser/widgets/snaparchive.py
+++ b/lib/lp/snappy/browser/widgets/snaparchive.py
@@ -2,8 +2,8 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'SnapArchiveWidget',
- ]
+ "SnapArchiveWidget",
+]
from zope.browserpage import ViewPageTemplateFile
from zope.component import getUtility
@@ -12,14 +12,14 @@ from zope.formlib.interfaces import (
IInputWidget,
MissingInputError,
WidgetInputError,
- )
+)
from zope.formlib.utility import setUpWidget
from zope.formlib.widget import (
BrowserWidget,
InputErrors,
InputWidget,
renderElement,
- )
+)
from zope.interface import implementer
from zope.schema import Choice
@@ -29,7 +29,7 @@ from lp.app.validators import LaunchpadValidationError
from lp.services.webapp.interfaces import (
IAlwaysSubmittedWidget,
IMultiLineWidgetLayout,
- )
+)
from lp.snappy.interfaces.snap import ISnap
from lp.soyuz.interfaces.archive import IArchive
@@ -46,11 +46,13 @@ class SnapArchiveWidget(BrowserWidget, InputWidget):
return
fields = [
Choice(
- __name__="ppa", title="PPA", required=True, vocabulary="PPA"),
- ]
+ __name__="ppa", title="PPA", required=True, vocabulary="PPA"
+ ),
+ ]
for field in fields:
setUpWidget(
- self, field.__name__, field, IInputWidget, prefix=self.name)
+ self, field.__name__, field, IInputWidget, prefix=self.name
+ )
self._widgets_set_up = True
def setUpOptions(self):
@@ -58,18 +60,25 @@ class SnapArchiveWidget(BrowserWidget, InputWidget):
self.options = {}
for option in ["primary", "ppa"]:
attributes = dict(
- type="radio", name=self.name, value=option,
- id="%s.option.%s" % (self.name, option))
- if (self.default_option is not None and
- self.request.form_ng.getOne(
- self.name, self.default_option) == option):
+ type="radio",
+ name=self.name,
+ value=option,
+ id="%s.option.%s" % (self.name, option),
+ )
+ if (
+ self.default_option is not None
+ and self.request.form_ng.getOne(self.name, self.default_option)
+ == option
+ ):
attributes["checked"] = "checked"
self.options[option] = renderElement("input", **attributes)
@property
def main_archive(self):
- if (ISnap.providedBy(self.context.context) and
- self.context.context.distro_series is not None):
+ if (
+ ISnap.providedBy(self.context.context)
+ and self.context.context.distro_series is not None
+ ):
return self.context.context.distro_series.main_archive
else:
return getUtility(ILaunchpadCelebrities).ubuntu.main_archive
@@ -118,16 +127,22 @@ class SnapArchiveWidget(BrowserWidget, InputWidget):
ppa = self.ppa_widget.getInputValue()
except MissingInputError:
raise WidgetInputError(
- self.name, self.label,
- LaunchpadValidationError("Please choose a PPA."))
+ self.name,
+ self.label,
+ LaunchpadValidationError("Please choose a PPA."),
+ )
except ConversionError:
entered_name = self.request.form_ng.getOne(
- "%s.ppa" % self.name)
+ "%s.ppa" % self.name
+ )
raise WidgetInputError(
- self.name, self.label,
+ self.name,
+ self.label,
LaunchpadValidationError(
- "There is no PPA named '%s' registered in Launchpad." %
- entered_name))
+ "There is no PPA named '%s' registered in Launchpad."
+ % entered_name
+ ),
+ )
return ppa
def error(self):
diff --git a/lib/lp/snappy/browser/widgets/snapbuildchannels.py b/lib/lp/snappy/browser/widgets/snapbuildchannels.py
index 8d0264d..17a6786 100644
--- a/lib/lp/snappy/browser/widgets/snapbuildchannels.py
+++ b/lib/lp/snappy/browser/widgets/snapbuildchannels.py
@@ -4,17 +4,13 @@
"""A widget for selecting source snap channels for builds."""
__all__ = [
- 'SnapBuildChannelsWidget',
- ]
+ "SnapBuildChannelsWidget",
+]
from zope.browserpage import ViewPageTemplateFile
from zope.formlib.interfaces import IInputWidget
from zope.formlib.utility import setUpWidget
-from zope.formlib.widget import (
- BrowserWidget,
- InputErrors,
- InputWidget,
- )
+from zope.formlib.widget import BrowserWidget, InputErrors, InputWidget
from zope.interface import implementer
from zope.schema import TextLine
from zope.security.proxy import isinstance as zope_isinstance
@@ -24,7 +20,7 @@ from lp.services.features import getFeatureFlag
from lp.services.webapp.interfaces import (
IAlwaysSubmittedWidget,
ISingleLineWidgetLayout,
- )
+)
from lp.snappy.interfaces.snap import SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG
@@ -39,34 +35,41 @@ class SnapBuildChannelsWidget(BrowserWidget, InputWidget):
def __init__(self, context, request):
super().__init__(context, request)
self.hint = (
- 'The channels to use for build tools when building the snap '
- 'package.\n')
+ "The channels to use for build tools when building the snap "
+ "package.\n"
+ )
default_snapcraft_channel = (
- getFeatureFlag(SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG) or "apt")
+ getFeatureFlag(SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG) or "apt"
+ )
if default_snapcraft_channel == "apt":
self.hint += (
'If unset, or if the channel for snapcraft is set to "apt", '
- 'the default is to install snapcraft from the source archive '
- 'using apt.')
+ "the default is to install snapcraft from the source archive "
+ "using apt."
+ )
else:
self.hint += (
'If unset, the default is to install snapcraft from the "%s" '
'channel. Setting the channel for snapcraft to "apt" causes '
- 'snapcraft to be installed from the source archive using '
- 'apt.' % default_snapcraft_channel)
+ "snapcraft to be installed from the source archive using "
+ "apt." % default_snapcraft_channel
+ )
def setUpSubWidgets(self):
if self._widgets_set_up:
return
fields = [
TextLine(
- __name__=snap_name, title="%s channel" % snap_name,
- required=False)
+ __name__=snap_name,
+ title="%s channel" % snap_name,
+ required=False,
+ )
for snap_name in self.snap_names
- ]
+ ]
for field in fields:
setUpWidget(
- self, field.__name__, field, IInputWidget, prefix=self.name)
+ self, field.__name__, field, IInputWidget, prefix=self.name
+ )
self._widgets_set_up = True
def setRenderedValue(self, value):
@@ -76,13 +79,15 @@ class SnapBuildChannelsWidget(BrowserWidget, InputWidget):
value = {}
for snap_name in self.snap_names:
getattr(self, "%s_widget" % snap_name).setRenderedValue(
- value.get(snap_name))
+ value.get(snap_name)
+ )
def hasInput(self):
"""See `IInputWidget`."""
return any(
"%s.%s" % (self.name, snap_name) in self.request.form
- for snap_name in self.snap_names)
+ for snap_name in self.snap_names
+ )
def hasValidInput(self):
"""See `IInputWidget`."""
diff --git a/lib/lp/snappy/browser/widgets/storechannels.py b/lib/lp/snappy/browser/widgets/storechannels.py
index 225372a..271eac0 100644
--- a/lib/lp/snappy/browser/widgets/storechannels.py
+++ b/lib/lp/snappy/browser/widgets/storechannels.py
@@ -2,27 +2,20 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'StoreChannelsWidget',
- ]
+ "StoreChannelsWidget",
+]
from zope.browserpage import ViewPageTemplateFile
-from zope.formlib.interfaces import (
- IInputWidget,
- WidgetInputError,
- )
+from zope.formlib.interfaces import IInputWidget, WidgetInputError
from zope.formlib.utility import setUpWidget
from zope.formlib.widget import (
BrowserWidget,
CustomWidgetFactory,
InputErrors,
InputWidget,
- )
+)
from zope.interface import implementer
-from zope.schema import (
- Choice,
- List,
- TextLine,
- )
+from zope.schema import Choice, List, TextLine
from lp import _
from lp.app.errors import UnexpectedFormData
@@ -32,18 +25,18 @@ from lp.services.channels import (
CHANNEL_COMPONENTS_DELIMITER,
channel_list_to_string,
channel_string_to_list,
- )
+)
from lp.services.webapp.interfaces import (
IAlwaysSubmittedWidget,
ISingleLineWidgetLayout,
- )
+)
@implementer(ISingleLineWidgetLayout, IAlwaysSubmittedWidget, IInputWidget)
class StoreChannelsWidget(BrowserWidget, InputWidget):
template = ViewPageTemplateFile("templates/storechannels.pt")
- _default_track = 'latest'
+ _default_track = "latest"
_widgets_set_up = False
def __init__(self, field, value_type, request):
@@ -57,33 +50,45 @@ class StoreChannelsWidget(BrowserWidget, InputWidget):
return
fields = [
TextLine(
- __name__="track", title="Track", required=False,
+ __name__="track",
+ title="Track",
+ required=False,
description=_(
"Track defines a series for your software. "
"If not specified, the default track ('latest') is "
- "assumed.")),
+ "assumed."
+ ),
+ ),
List(
- __name__="risks", title="Risk", required=False,
+ __name__="risks",
+ title="Risk",
+ required=False,
value_type=Choice(vocabulary="SnapStoreChannel"),
- description=_("Risks denote the stability of your software.")),
+ description=_("Risks denote the stability of your software."),
+ ),
TextLine(
- __name__="branch", title="Branch", required=False,
+ __name__="branch",
+ title="Branch",
+ required=False,
description=_(
"Branches provide users with an easy way to test bug "
"fixes. They are temporary and created on demand. If "
- "not specified, no branch is used.")),
- ]
+ "not specified, no branch is used."
+ ),
+ ),
+ ]
self.risks_widget = CustomWidgetFactory(LabeledMultiCheckBoxWidget)
for field in fields:
setUpWidget(
- self, field.__name__, field, IInputWidget, prefix=self.name)
- self.risks_widget.orientation = 'horizontal'
+ self, field.__name__, field, IInputWidget, prefix=self.name
+ )
+ self.risks_widget.orientation = "horizontal"
self._widgets_set_up = True
@property
def has_risks_vocabulary(self):
- risks_widget = getattr(self, 'risks_widget', None)
+ risks_widget = getattr(self, "risks_widget", None)
return risks_widget and bool(risks_widget.vocabulary)
def setRenderedValue(self, value):
@@ -102,10 +107,12 @@ class StoreChannelsWidget(BrowserWidget, InputWidget):
branches.add(branch)
if len(tracks) != 1:
raise ValueError(
- "Channels belong to different tracks: %r" % value)
+ "Channels belong to different tracks: %r" % value
+ )
if len(branches) != 1:
raise ValueError(
- "Channels belong to different branches: %r" % value)
+ "Channels belong to different branches: %r" % value
+ )
track = tracks.pop()
self.track_widget.setRenderedValue(track)
self.risks_widget.setRenderedValue(risks)
@@ -138,16 +145,21 @@ class StoreChannelsWidget(BrowserWidget, InputWidget):
branch = self.branch_widget.getInputValue()
if track and CHANNEL_COMPONENTS_DELIMITER in track:
error_msg = "Track name cannot include '%s'." % (
- CHANNEL_COMPONENTS_DELIMITER)
+ CHANNEL_COMPONENTS_DELIMITER
+ )
raise WidgetInputError(
- self.name, self.label, LaunchpadValidationError(error_msg))
+ self.name, self.label, LaunchpadValidationError(error_msg)
+ )
if branch and CHANNEL_COMPONENTS_DELIMITER in branch:
error_msg = "Branch name cannot include '%s'." % (
- CHANNEL_COMPONENTS_DELIMITER)
+ CHANNEL_COMPONENTS_DELIMITER
+ )
raise WidgetInputError(
- self.name, self.label, LaunchpadValidationError(error_msg))
+ self.name, self.label, LaunchpadValidationError(error_msg)
+ )
channels = [
- channel_list_to_string(track, risk, branch) for risk in risks]
+ channel_list_to_string(track, risk, branch) for risk in risks
+ ]
return channels
def error(self):
diff --git a/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py b/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py
index 5116684..bd11e85 100644
--- a/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py
+++ b/lib/lp/snappy/browser/widgets/tests/test_snaparchivewidget.py
@@ -1,20 +1,17 @@
# Copyright 2015-2019 Canonical Ltd. This software is licensed under the
# GNU Affero General Public License version 3 (see the file LICENSE).
-from functools import partial
import re
+from functools import partial
from lazr.restful.fields import Reference
-from testscenarios import (
- load_tests_apply_scenarios,
- WithScenarios,
- )
+from testscenarios import WithScenarios, load_tests_apply_scenarios
from zope.component import getUtility
from zope.formlib.interfaces import (
IBrowserWidget,
IInputWidget,
WidgetInputError,
- )
+)
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.validators import LaunchpadValidationError
@@ -26,10 +23,7 @@ from lp.snappy.interfaces.snap import ISnap
from lp.soyuz.enums import ArchivePurpose
from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.vocabularies import PPAVocabulary
-from lp.testing import (
- TestCaseWithFactory,
- verifyObject,
- )
+from lp.testing import TestCaseWithFactory, verifyObject
from lp.testing.layers import DatabaseFunctionalLayer
@@ -53,11 +47,13 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
scenarios = [
("Snap", {"context_factory": make_snap}),
- ("Snap with no distroseries",
- {"context_factory": partial(make_snap, distroseries=None)}),
+ (
+ "Snap with no distroseries",
+ {"context_factory": partial(make_snap, distroseries=None)},
+ ),
("Branch", {"context_factory": make_branch}),
("GitRepository", {"context_factory": make_git_repository}),
- ]
+ ]
def setUp(self):
super().setUp()
@@ -75,7 +71,8 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
def test_template(self):
self.assertTrue(
self.widget.template.filename.endswith("snaparchive.pt"),
- "Template was not set up.")
+ "Template was not set up.",
+ )
def test_default_option(self):
# The primary field is the default option.
@@ -86,7 +83,8 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
self.widget.setUpSubWidgets()
self.assertTrue(self.widget._widgets_set_up)
self.assertIsInstance(
- self.widget.ppa_widget.context.vocabulary, PPAVocabulary)
+ self.widget.ppa_widget.context.vocabulary, PPAVocabulary
+ )
def test_setUpSubWidgets_second_call(self):
# The setUpSubWidgets method exits early if a flag is set to
@@ -101,55 +99,61 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
self.widget.setUpSubWidgets()
self.widget.setUpOptions()
self.assertEqual(
- '<input class="radioType" checked="checked" ' +
- 'id="field.archive.option.primary" name="field.archive" '
+ '<input class="radioType" checked="checked" '
+ + 'id="field.archive.option.primary" name="field.archive" '
'type="radio" value="primary" />',
- self.widget.options["primary"])
+ self.widget.options["primary"],
+ )
self.assertEqual(
- '<input class="radioType" ' +
- 'id="field.archive.option.ppa" name="field.archive" '
+ '<input class="radioType" '
+ + 'id="field.archive.option.ppa" name="field.archive" '
'type="radio" value="ppa" />',
- self.widget.options["ppa"])
+ self.widget.options["ppa"],
+ )
def test_setUpOptions_primary_checked(self):
# The primary radio button is selected when the form is submitted
# when the archive field's value is 'primary'.
form = {
"field.archive": "primary",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.widget.setUpSubWidgets()
self.widget.setUpOptions()
self.assertEqual(
- '<input class="radioType" checked="checked" ' +
- 'id="field.archive.option.primary" name="field.archive" '
+ '<input class="radioType" checked="checked" '
+ + 'id="field.archive.option.primary" name="field.archive" '
'type="radio" value="primary" />',
- self.widget.options["primary"])
+ self.widget.options["primary"],
+ )
self.assertEqual(
- '<input class="radioType" ' +
- 'id="field.archive.option.ppa" name="field.archive" '
+ '<input class="radioType" '
+ + 'id="field.archive.option.ppa" name="field.archive" '
'type="radio" value="ppa" />',
- self.widget.options["ppa"])
+ self.widget.options["ppa"],
+ )
def test_setUpOptions_ppa_checked(self):
# The ppa radio button is selected when the form is submitted when
# the archive field's value is 'ppa'.
form = {
"field.archive": "ppa",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.widget.setUpSubWidgets()
self.widget.setUpOptions()
self.assertEqual(
- '<input class="radioType" ' +
- 'id="field.archive.option.primary" name="field.archive" '
+ '<input class="radioType" '
+ + 'id="field.archive.option.primary" name="field.archive" '
'type="radio" value="primary" />',
- self.widget.options["primary"])
+ self.widget.options["primary"],
+ )
self.assertEqual(
- '<input class="radioType" checked="checked" ' +
- 'id="field.archive.option.ppa" name="field.archive" '
+ '<input class="radioType" checked="checked" '
+ + 'id="field.archive.option.ppa" name="field.archive" '
'type="radio" value="ppa" />',
- self.widget.options["ppa"])
+ self.widget.options["ppa"],
+ )
def test_setRenderedValue_primary(self):
# Passing a primary archive will set the widget's render state to
@@ -165,7 +169,8 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
self.widget.setUpSubWidgets()
archive = self.factory.makeArchive(
distribution=self.distroseries.distribution,
- purpose=ArchivePurpose.PPA)
+ purpose=ArchivePurpose.PPA,
+ )
self.widget.setRenderedValue(archive)
self.widget.setRenderedValue(self.distroseries.main_archive)
self.assertEqual("primary", self.widget.default_option)
@@ -176,7 +181,8 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
self.widget.setUpSubWidgets()
archive = self.factory.makeArchive(
distribution=self.distroseries.distribution,
- purpose=ArchivePurpose.PPA)
+ purpose=ArchivePurpose.PPA,
+ )
self.widget.setRenderedValue(archive)
self.assertEqual("ppa", self.widget.default_option)
self.assertEqual(archive, self.widget.ppa_widget._getCurrentValue())
@@ -189,7 +195,8 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
def test_hasInput_true(self):
# hasInput is false when the widget's name is in the form data.
self.widget.request = LaunchpadTestRequest(
- form={"field.archive": "primary"})
+ form={"field.archive": "primary"}
+ )
self.assertTrue(self.widget.hasInput())
def test_hasValidInput_false(self):
@@ -198,7 +205,7 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
form = {
"field.archive": "ppa",
"field.archive.ppa": "non-existent",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertFalse(self.widget.hasValidInput())
@@ -208,7 +215,7 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
form = {
"field.archive": "ppa",
"field.archive.ppa": archive.reference,
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertTrue(self.widget.hasValidInput())
@@ -223,13 +230,17 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
# context's primary archive if the context is a Snap and has a
# distroseries, or the Ubuntu primary archive otherwise.
self.widget.request = LaunchpadTestRequest(
- form={"field.archive": "primary"})
- if (ISnap.providedBy(self.context) and
- self.context.distro_series is not None):
+ form={"field.archive": "primary"}
+ )
+ if (
+ ISnap.providedBy(self.context)
+ and self.context.distro_series is not None
+ ):
expected_main_archive = self.context.distro_series.main_archive
else:
- expected_main_archive = (
- getUtility(ILaunchpadCelebrities).ubuntu.main_archive)
+ expected_main_archive = getUtility(
+ ILaunchpadCelebrities
+ ).ubuntu.main_archive
self.assertEqual(expected_main_archive, self.widget.getInputValue())
def test_getInputValue_ppa_missing(self):
@@ -242,10 +253,11 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
form = {
"field.archive": "ppa",
"field.archive.ppa": "non-existent",
- }
+ }
self.assertGetInputValueError(
form,
- "There is no PPA named 'non-existent' registered in Launchpad.")
+ "There is no PPA named 'non-existent' registered in Launchpad.",
+ )
def test_getInputValue_ppa(self):
# The field value is the PPA when the ppa radio button is selected
@@ -254,7 +266,7 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
form = {
"field.archive": "ppa",
"field.archive.ppa": archive.reference,
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertEqual(archive, self.widget.getInputValue())
@@ -270,7 +282,7 @@ class TestSnapArchiveWidget(WithScenarios, TestCaseWithFactory):
"field.archive.option.primary",
"field.archive.option.ppa",
"field.archive.ppa",
- ]
+ ]
ids = [field["id"] for field in fields]
self.assertContentEqual(expected_ids, ids)
diff --git a/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py b/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py
index 543d4ed..9b71b4c 100644
--- a/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py
+++ b/lib/lp/snappy/browser/widgets/tests/test_snapbuildchannelswidget.py
@@ -3,23 +3,15 @@
import re
-from zope.formlib.interfaces import (
- IBrowserWidget,
- IInputWidget,
- )
+from zope.formlib.interfaces import IBrowserWidget, IInputWidget
from zope.schema import Dict
from lp.services.beautifulsoup import BeautifulSoup
from lp.services.features.testing import FeatureFixture
from lp.services.webapp.servers import LaunchpadTestRequest
-from lp.snappy.browser.widgets.snapbuildchannels import (
- SnapBuildChannelsWidget,
- )
+from lp.snappy.browser.widgets.snapbuildchannels import SnapBuildChannelsWidget
from lp.snappy.interfaces.snap import SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG
-from lp.testing import (
- TestCaseWithFactory,
- verifyObject,
- )
+from lp.testing import TestCaseWithFactory, verifyObject
from lp.testing.layers import DatabaseFunctionalLayer
@@ -31,7 +23,8 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
super().setUp()
field = Dict(
__name__="auto_build_channels",
- title="Source snap channels for automatic builds")
+ title="Source snap channels for automatic builds",
+ )
self.context = self.factory.makeSnap()
self.field = field.bind(self.context)
self.request = LaunchpadTestRequest()
@@ -44,41 +37,47 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
def test_template(self):
self.assertTrue(
self.widget.template.filename.endswith("snapbuildchannels.pt"),
- "Template was not set up.")
+ "Template was not set up.",
+ )
def test_hint_no_feature_flag(self):
self.assertEqual(
- 'The channels to use for build tools when building the snap '
- 'package.\n'
+ "The channels to use for build tools when building the snap "
+ "package.\n"
'If unset, or if the channel for snapcraft is set to "apt", '
- 'the default is to install snapcraft from the source archive '
- 'using apt.',
- self.widget.hint)
+ "the default is to install snapcraft from the source archive "
+ "using apt.",
+ self.widget.hint,
+ )
def test_hint_feature_flag_apt(self):
self.useFixture(
- FeatureFixture({SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG: "apt"}))
+ FeatureFixture({SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG: "apt"})
+ )
widget = SnapBuildChannelsWidget(self.field, self.request)
self.assertEqual(
- 'The channels to use for build tools when building the snap '
- 'package.\n'
+ "The channels to use for build tools when building the snap "
+ "package.\n"
'If unset, or if the channel for snapcraft is set to "apt", '
- 'the default is to install snapcraft from the source archive '
- 'using apt.',
- widget.hint)
+ "the default is to install snapcraft from the source archive "
+ "using apt.",
+ widget.hint,
+ )
def test_hint_feature_flag_real_channel(self):
self.useFixture(
- FeatureFixture({SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG: "stable"}))
+ FeatureFixture({SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG: "stable"})
+ )
widget = SnapBuildChannelsWidget(self.field, self.request)
self.assertEqual(
- 'The channels to use for build tools when building the snap '
- 'package.\n'
+ "The channels to use for build tools when building the snap "
+ "package.\n"
'If unset, the default is to install snapcraft from the "stable" '
'channel. Setting the channel for snapcraft to "apt" causes '
- 'snapcraft to be installed from the source archive using '
- 'apt.',
- widget.hint)
+ "snapcraft to be installed from the source archive using "
+ "apt.",
+ widget.hint,
+ )
def test_setUpSubWidgets_first_call(self):
# The subwidgets are set up and a flag is set.
@@ -125,20 +124,30 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
self.assertIsNone(self.widget.core20_widget._getCurrentValue())
self.assertIsNone(self.widget.core22_widget._getCurrentValue())
self.assertEqual(
- "stable", self.widget.snapcraft_widget._getCurrentValue())
+ "stable", self.widget.snapcraft_widget._getCurrentValue()
+ )
def test_setRenderedValue_all_channels(self):
self.widget.setRenderedValue(
- {"core": "candidate", "core18": "beta", "core20": "edge",
- "core22": "edge/feature", "snapcraft": "stable"})
+ {
+ "core": "candidate",
+ "core18": "beta",
+ "core20": "edge",
+ "core22": "edge/feature",
+ "snapcraft": "stable",
+ }
+ )
self.assertEqual(
- "candidate", self.widget.core_widget._getCurrentValue())
+ "candidate", self.widget.core_widget._getCurrentValue()
+ )
self.assertEqual("beta", self.widget.core18_widget._getCurrentValue())
self.assertEqual("edge", self.widget.core20_widget._getCurrentValue())
self.assertEqual(
- "edge/feature", self.widget.core22_widget._getCurrentValue())
+ "edge/feature", self.widget.core22_widget._getCurrentValue()
+ )
self.assertEqual(
- "stable", self.widget.snapcraft_widget._getCurrentValue())
+ "stable", self.widget.snapcraft_widget._getCurrentValue()
+ )
def test_hasInput_false(self):
# hasInput is false when there are no channels in the form data.
@@ -148,7 +157,8 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
def test_hasInput_true(self):
# hasInput is true when there are channels in the form data.
self.widget.request = LaunchpadTestRequest(
- form={"field.auto_build_channels.snapcraft": "stable"})
+ form={"field.auto_build_channels.snapcraft": "stable"}
+ )
self.assertTrue(self.widget.hasInput())
def test_hasValidInput_true(self):
@@ -161,7 +171,7 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
"field.auto_build_channels.core20": "edge",
"field.auto_build_channels.core22": "edge/feature",
"field.auto_build_channels.snapcraft": "stable",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertTrue(self.widget.hasValidInput())
@@ -172,12 +182,17 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
"field.auto_build_channels.core20": "edge",
"field.auto_build_channels.core22": "edge/feature",
"field.auto_build_channels.snapcraft": "stable",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertEqual(
- {"core18": "beta", "core20": "edge", "core22": "edge/feature",
- "snapcraft": "stable"},
- self.widget.getInputValue())
+ {
+ "core18": "beta",
+ "core20": "edge",
+ "core22": "edge/feature",
+ "snapcraft": "stable",
+ },
+ self.widget.getInputValue(),
+ )
def test_call(self):
# The __call__ method sets up the widgets.
@@ -195,6 +210,6 @@ class TestSnapBuildChannelsWidget(TestCaseWithFactory):
"field.auto_build_channels.core20",
"field.auto_build_channels.core22",
"field.auto_build_channels.snapcraft",
- ]
+ ]
ids = [field["id"] for field in fields]
self.assertContentEqual(expected_ids, ids)
diff --git a/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py b/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py
index 9c9c79f..83d42be 100644
--- a/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py
+++ b/lib/lp/snappy/browser/widgets/tests/test_storechannelswidget.py
@@ -7,7 +7,7 @@ from zope.formlib.interfaces import (
IBrowserWidget,
IInputWidget,
WidgetInputError,
- )
+)
from zope.schema import List
from lp.app.validators import LaunchpadValidationError
@@ -17,10 +17,7 @@ from lp.services.webapp.escaping import html_escape
from lp.services.webapp.servers import LaunchpadTestRequest
from lp.snappy.browser.widgets.storechannels import StoreChannelsWidget
from lp.snappy.vocabularies import SnapStoreChannelVocabulary
-from lp.testing import (
- TestCaseWithFactory,
- verifyObject,
- )
+from lp.testing import TestCaseWithFactory, verifyObject
from lp.testing.layers import DatabaseFunctionalLayer
@@ -43,7 +40,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
def test_template(self):
self.assertTrue(
self.widget.template.filename.endswith("storechannels.pt"),
- "Template was not set up.")
+ "Template was not set up.",
+ )
def test_setUpSubWidgets_first_call(self):
# The subwidgets are set up and a flag is set.
@@ -51,7 +49,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
self.assertTrue(self.widget._widgets_set_up)
self.assertIsNotNone(getattr(self.widget, "track_widget", None))
self.assertIsInstance(
- self.widget.risks_widget.vocabulary, SnapStoreChannelVocabulary)
+ self.widget.risks_widget.vocabulary, SnapStoreChannelVocabulary
+ )
self.assertTrue(self.widget.has_risks_vocabulary)
self.assertIsNotNone(getattr(self.widget, "branch_widget", None))
@@ -72,7 +71,7 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
def test_setRenderedValue_no_track_or_branch(self):
# Channels do not include a track or branch
- risks = ['candidate', 'edge']
+ risks = ["candidate", "edge"]
self.widget.setRenderedValue(risks)
self.assertIsNone(self.widget.track_widget._getCurrentValue())
self.assertEqual(risks, self.widget.risks_widget._getCurrentValue())
@@ -80,32 +79,37 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
def test_setRenderedValue_with_track(self):
# Channels including a track
- channels = ['2.2/candidate', '2.2/edge']
+ channels = ["2.2/candidate", "2.2/edge"]
self.widget.setRenderedValue(channels)
- self.assertEqual('2.2', self.widget.track_widget._getCurrentValue())
+ self.assertEqual("2.2", self.widget.track_widget._getCurrentValue())
self.assertEqual(
- ['candidate', 'edge'], self.widget.risks_widget._getCurrentValue())
+ ["candidate", "edge"], self.widget.risks_widget._getCurrentValue()
+ )
self.assertIsNone(self.widget.branch_widget._getCurrentValue())
def test_setRenderedValue_with_branch(self):
# Channels including a branch
- channels = ['candidate/fix-123', 'edge/fix-123']
+ channels = ["candidate/fix-123", "edge/fix-123"]
self.widget.setRenderedValue(channels)
self.assertIsNone(self.widget.track_widget._getCurrentValue())
self.assertEqual(
- ['candidate', 'edge'], self.widget.risks_widget._getCurrentValue())
+ ["candidate", "edge"], self.widget.risks_widget._getCurrentValue()
+ )
self.assertEqual(
- 'fix-123', self.widget.branch_widget._getCurrentValue())
+ "fix-123", self.widget.branch_widget._getCurrentValue()
+ )
def test_setRenderedValue_with_track_and_branch(self):
# Channels including a track and branch
- channels = ['2.2/candidate/fix-123', '2.2/edge/fix-123']
+ channels = ["2.2/candidate/fix-123", "2.2/edge/fix-123"]
self.widget.setRenderedValue(channels)
- self.assertEqual('2.2', self.widget.track_widget._getCurrentValue())
+ self.assertEqual("2.2", self.widget.track_widget._getCurrentValue())
self.assertEqual(
- ['candidate', 'edge'], self.widget.risks_widget._getCurrentValue())
+ ["candidate", "edge"], self.widget.risks_widget._getCurrentValue()
+ )
self.assertEqual(
- 'fix-123', self.widget.branch_widget._getCurrentValue())
+ "fix-123", self.widget.branch_widget._getCurrentValue()
+ )
def test_setRenderedValue_invalid_value(self):
# Multiple channels, different tracks or branches, unsupported
@@ -113,30 +117,36 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
ValueError,
"Channels belong to different tracks: "
"['2.2/candidate', '2.1/edge']",
- self.widget.setRenderedValue, ['2.2/candidate', '2.1/edge'])
+ self.widget.setRenderedValue,
+ ["2.2/candidate", "2.1/edge"],
+ )
self.assertRaisesWithContent(
ValueError,
"Channels belong to different branches: "
"['candidate/fix-123', 'edge/fix-124']",
self.widget.setRenderedValue,
- ['candidate/fix-123', 'edge/fix-124'])
+ ["candidate/fix-123", "edge/fix-124"],
+ )
self.assertRaisesWithContent(
ValueError,
"Channels belong to different tracks: "
"['2.2/candidate', 'edge/fix-123']",
self.widget.setRenderedValue,
- ['2.2/candidate', 'edge/fix-123'])
+ ["2.2/candidate", "edge/fix-123"],
+ )
def test_hasInput_false(self):
# hasInput is false when there is no risk set in the form data.
self.widget.request = LaunchpadTestRequest(
- form={"field.channels.track": "track"})
+ form={"field.channels.track": "track"}
+ )
self.assertFalse(self.widget.hasInput())
def test_hasInput_true(self):
# hasInput is true if there are risks set in the form data.
self.widget.request = LaunchpadTestRequest(
- form={"field.channels.risks": ["beta"]})
+ form={"field.channels.risks": ["beta"]}
+ )
self.assertTrue(self.widget.hasInput())
def test_hasValidInput_false(self):
@@ -146,7 +156,7 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "",
"field.channels.risks": ["invalid"],
"field.channels.branch": "",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertFalse(self.widget.hasValidInput())
@@ -156,7 +166,7 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "track",
"field.channels.risks": ["stable", "beta"],
"field.channels.branch": "branch",
- }
+ }
self.widget.request = LaunchpadTestRequest(form=form)
self.assertTrue(self.widget.hasValidInput())
@@ -172,7 +182,7 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "tra/ck",
"field.channels.risks": ["beta"],
"field.channels.branch": "",
- }
+ }
self.assertGetInputValueError(form, "Track name cannot include '/'.")
def test_getInputValue_invalid_branch(self):
@@ -181,7 +191,7 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "",
"field.channels.risks": ["beta"],
"field.channels.branch": "bra/nch",
- }
+ }
self.assertGetInputValueError(form, "Branch name cannot include '/'.")
def test_getInputValue_no_track_or_branch(self):
@@ -190,7 +200,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "",
"field.channels.risks": ["beta", "edge"],
"field.channels.branch": "",
- })
+ }
+ )
expected = ["beta", "edge"]
self.assertEqual(expected, self.widget.getInputValue())
@@ -200,7 +211,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "track",
"field.channels.risks": ["beta", "edge"],
"field.channels.branch": "",
- })
+ }
+ )
expected = ["track/beta", "track/edge"]
self.assertEqual(expected, self.widget.getInputValue())
@@ -210,7 +222,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "",
"field.channels.risks": ["beta", "edge"],
"field.channels.branch": "fix-123",
- })
+ }
+ )
expected = ["beta/fix-123", "edge/fix-123"]
self.assertEqual(expected, self.widget.getInputValue())
@@ -220,7 +233,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
"field.channels.track": "track",
"field.channels.risks": ["beta", "edge"],
"field.channels.branch": "fix-123",
- })
+ }
+ )
expected = ["track/beta/fix-123", "track/edge/fix-123"]
self.assertEqual(expected, self.widget.getInputValue())
@@ -232,7 +246,8 @@ class TestStoreChannelsWidget(TestCaseWithFactory):
soup = BeautifulSoup(markup)
fields = soup.find_all(["input"], {"id": re.compile(".*")})
expected_ids = [
- "field.channels.risks.%d" % i for i in range(len(StoreRisk))]
+ "field.channels.risks.%d" % i for i in range(len(StoreRisk))
+ ]
expected_ids.append("field.channels.track")
expected_ids.append("field.channels.branch")
ids = [field["id"] for field in fields]
diff --git a/lib/lp/snappy/interfaces/snap.py b/lib/lp/snappy/interfaces/snap.py
index 25b1f51..dd70dcd 100644
--- a/lib/lp/snappy/interfaces/snap.py
+++ b/lib/lp/snappy/interfaces/snap.py
@@ -4,46 +4,44 @@
"""Snap package interfaces."""
__all__ = [
- 'BadMacaroon',
- 'BadSnapSearchContext',
- 'BadSnapSource',
- 'CannotAuthorizeStoreUploads',
- 'CannotFetchSnapcraftYaml',
- 'CannotModifySnapProcessor',
- 'CannotParseSnapcraftYaml',
- 'CannotRequestAutoBuilds',
- 'DuplicateSnapName',
- 'ISnap',
- 'ISnapBuildRequest',
- 'ISnapEdit',
- 'ISnapSet',
- 'ISnapView',
- 'MissingSnapcraftYaml',
- 'NoSourceForSnap',
- 'NoSuchSnap',
- 'SNAP_PRIVATE_FEATURE_FLAG',
- 'SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG',
- 'SNAP_TESTING_FLAGS',
- 'SNAP_WEBHOOKS_FEATURE_FLAG',
- 'SnapAuthorizationBadGeneratedMacaroon',
- 'SnapBuildAlreadyPending',
- 'SnapBuildArchiveOwnerMismatch',
- 'SnapBuildDisallowedArchitecture',
- 'SnapBuildRequestStatus',
- 'SnapNotOwner',
- 'SnapPrivacyMismatch',
- 'SnapPrivacyPillarError',
- 'SnapPrivateFeatureDisabled',
- ]
+ "BadMacaroon",
+ "BadSnapSearchContext",
+ "BadSnapSource",
+ "CannotAuthorizeStoreUploads",
+ "CannotFetchSnapcraftYaml",
+ "CannotModifySnapProcessor",
+ "CannotParseSnapcraftYaml",
+ "CannotRequestAutoBuilds",
+ "DuplicateSnapName",
+ "ISnap",
+ "ISnapBuildRequest",
+ "ISnapEdit",
+ "ISnapSet",
+ "ISnapView",
+ "MissingSnapcraftYaml",
+ "NoSourceForSnap",
+ "NoSuchSnap",
+ "SNAP_PRIVATE_FEATURE_FLAG",
+ "SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG",
+ "SNAP_TESTING_FLAGS",
+ "SNAP_WEBHOOKS_FEATURE_FLAG",
+ "SnapAuthorizationBadGeneratedMacaroon",
+ "SnapBuildAlreadyPending",
+ "SnapBuildArchiveOwnerMismatch",
+ "SnapBuildDisallowedArchitecture",
+ "SnapBuildRequestStatus",
+ "SnapNotOwner",
+ "SnapPrivacyMismatch",
+ "SnapPrivacyPillarError",
+ "SnapPrivateFeatureDisabled",
+]
import http.client
-from lazr.enum import (
- EnumeratedType,
- Item,
- )
+from lazr.enum import EnumeratedType, Item
from lazr.lifecycle.snapshot import doNotSnapshot
from lazr.restful.declarations import (
+ REQUEST_USER,
call_with,
collection_default_content,
error_status,
@@ -58,18 +56,10 @@ from lazr.restful.declarations import (
operation_parameters,
operation_returns_collection_of,
operation_returns_entry,
- REQUEST_USER,
- )
-from lazr.restful.fields import (
- CollectionField,
- Reference,
- ReferenceChoice,
- )
+)
+from lazr.restful.fields import CollectionField, Reference, ReferenceChoice
from lazr.restful.interface import copy_field
-from zope.interface import (
- Attribute,
- Interface,
- )
+from zope.interface import Attribute, Interface
from zope.schema import (
Bool,
Choice,
@@ -80,11 +70,8 @@ from zope.schema import (
Set,
Text,
TextLine,
- )
-from zope.security.interfaces import (
- Forbidden,
- Unauthorized,
- )
+)
+from zope.security.interfaces import Forbidden, Unauthorized
from lp import _
from lp.app.enums import InformationType
@@ -101,22 +88,17 @@ from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.product import IProduct
from lp.registry.interfaces.role import IHasOwner
-from lp.services.fields import (
- PersonChoice,
- PublicPersonChoice,
- URIField,
- )
+from lp.services.fields import PersonChoice, PublicPersonChoice, URIField
from lp.services.webhooks.interfaces import IWebhookTarget
from lp.snappy.interfaces.snapbase import ISnapBase
from lp.snappy.interfaces.snappyseries import (
ISnappyDistroSeries,
ISnappySeries,
- )
+)
from lp.snappy.validators.channels import channels_validator
from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
-
SNAP_PRIVATE_FEATURE_FLAG = "snap.allow_private"
SNAP_SNAPCRAFT_CHANNEL_FEATURE_FLAG = "snap.channels.snapcraft"
SNAP_WEBHOOKS_FEATURE_FLAG = "snap.webhooks.enabled"
@@ -125,7 +107,7 @@ SNAP_WEBHOOKS_FEATURE_FLAG = "snap.webhooks.enabled"
SNAP_TESTING_FLAGS = {
SNAP_PRIVATE_FEATURE_FLAG: "on",
SNAP_WEBHOOKS_FEATURE_FLAG: "on",
- }
+}
@error_status(http.client.BAD_REQUEST)
@@ -134,7 +116,8 @@ class SnapBuildAlreadyPending(Exception):
def __init__(self):
super().__init__(
- "An identical build of this snap package is already pending.")
+ "An identical build of this snap package is already pending."
+ )
@error_status(http.client.FORBIDDEN)
@@ -152,7 +135,8 @@ class SnapBuildArchiveOwnerMismatch(Forbidden):
def __init__(self):
super().__init__(
"Snap package builds against private archives are only allowed "
- "if the snap package owner and the archive owner are equal.")
+ "if the snap package owner and the archive owner are equal."
+ )
@error_status(http.client.BAD_REQUEST)
@@ -161,8 +145,9 @@ class SnapBuildDisallowedArchitecture(Exception):
def __init__(self, das, pocket):
super().__init__(
- "This snap package is not allowed to build for %s/%s." %
- (das.distroseries.getSuite(pocket), das.architecturetag))
+ "This snap package is not allowed to build for %s/%s."
+ % (das.distroseries.getSuite(pocket), das.architecturetag)
+ )
@error_status(http.client.UNAUTHORIZED)
@@ -179,7 +164,8 @@ class DuplicateSnapName(Exception):
def __init__(self):
super().__init__(
- "There is already a snap package with the same name and owner.")
+ "There is already a snap package with the same name and owner."
+ )
@error_status(http.client.UNAUTHORIZED)
@@ -189,6 +175,7 @@ class SnapNotOwner(Unauthorized):
class NoSuchSnap(NameLookupFailed):
"""The requested snap package does not exist."""
+
_message_prefix = "No such snap package with this owner"
@@ -199,7 +186,8 @@ class NoSourceForSnap(Exception):
def __init__(self):
super().__init__(
"New snap packages must have either a Bazaar branch or a Git "
- "branch.")
+ "branch."
+ )
@error_status(http.client.BAD_REQUEST)
@@ -213,8 +201,9 @@ class SnapPrivacyMismatch(Exception):
def __init__(self, message=None):
super().__init__(
- message or
- "Snap recipe contains private information and cannot be public.")
+ message
+ or "Snap recipe contains private information and cannot be public."
+ )
@error_status(http.client.BAD_REQUEST)
@@ -223,7 +212,8 @@ class SnapPrivacyPillarError(Exception):
def __init__(self, message=None):
super().__init__(
- message or "Private Snap recipes should have a pillar.")
+ message or "Private Snap recipes should have a pillar."
+ )
class BadSnapSearchContext(Exception):
@@ -235,11 +225,12 @@ class CannotModifySnapProcessor(Exception):
"""Tried to enable or disable a restricted processor on an snap package."""
_fmt = (
- '%(processor)s is restricted, and may only be enabled or disabled '
- 'by administrators.')
+ "%(processor)s is restricted, and may only be enabled or disabled "
+ "by administrators."
+ )
def __init__(self, processor):
- super().__init__(self._fmt % {'processor': processor.name})
+ super().__init__(self._fmt % {"processor": processor.name})
@error_status(http.client.BAD_REQUEST)
@@ -264,7 +255,8 @@ class CannotRequestAutoBuilds(Exception):
def __init__(self, field):
super().__init__(
"This snap package cannot have automatic builds created for it "
- "because %s is not set." % field)
+ "because %s is not set." % field
+ )
class MissingSnapcraftYaml(Exception):
@@ -289,23 +281,29 @@ class CannotParseSnapcraftYaml(Exception):
class SnapBuildRequestStatus(EnumeratedType):
"""The status of a request to build a snap package."""
- PENDING = Item("""
+ PENDING = Item(
+ """
Pending
This snap build request is pending.
- """)
+ """
+ )
- FAILED = Item("""
+ FAILED = Item(
+ """
Failed
This snap build request failed.
- """)
+ """
+ )
- COMPLETED = Item("""
+ COMPLETED = Item(
+ """
Completed
This snap build request completed successfully.
- """)
+ """
+ )
# XXX cjwatson 2018-06-14 bug=760849: "beta" is a lie to get WADL
@@ -317,52 +315,89 @@ class ISnapBuildRequest(Interface):
id = Int(title=_("ID"), required=True, readonly=True)
- date_requested = exported(Datetime(
- title=_("The time when this request was made"),
- required=True, readonly=True))
+ date_requested = exported(
+ Datetime(
+ title=_("The time when this request was made"),
+ required=True,
+ readonly=True,
+ )
+ )
- date_finished = exported(Datetime(
- title=_("The time when this request finished"),
- required=False, readonly=True))
+ date_finished = exported(
+ Datetime(
+ title=_("The time when this request finished"),
+ required=False,
+ readonly=True,
+ )
+ )
- snap = exported(Reference(
- # Really ISnap, patched in lp.snappy.interfaces.webservice.
- Interface,
- title=_("Snap package"), required=True, readonly=True))
+ snap = exported(
+ Reference(
+ # Really ISnap, patched in lp.snappy.interfaces.webservice.
+ Interface,
+ title=_("Snap package"),
+ required=True,
+ readonly=True,
+ )
+ )
- status = exported(Choice(
- title=_("Status"), vocabulary=SnapBuildRequestStatus,
- required=True, readonly=True))
+ status = exported(
+ Choice(
+ title=_("Status"),
+ vocabulary=SnapBuildRequestStatus,
+ required=True,
+ readonly=True,
+ )
+ )
- error_message = exported(TextLine(
- title=_("Error message"), required=True, readonly=True))
+ error_message = exported(
+ TextLine(title=_("Error message"), required=True, readonly=True)
+ )
- builds = exported(CollectionField(
- title=_("Builds produced by this request"),
- # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
- value_type=Reference(schema=Interface),
- required=True, readonly=True))
+ builds = exported(
+ CollectionField(
+ title=_("Builds produced by this request"),
+ # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
+ value_type=Reference(schema=Interface),
+ required=True,
+ readonly=True,
+ )
+ )
requester = Reference(
- title=_("The person requesting the builds."), schema=IPerson,
- required=True, readonly=True)
+ title=_("The person requesting the builds."),
+ schema=IPerson,
+ required=True,
+ readonly=True,
+ )
archive = Reference(
IArchive,
title="The source archive for builds produced by this request",
- required=True, readonly=True)
+ required=True,
+ readonly=True,
+ )
pocket = Choice(
title=_("The source pocket for builds produced by this request."),
- vocabulary=PackagePublishingPocket, required=True, readonly=True)
+ vocabulary=PackagePublishingPocket,
+ required=True,
+ readonly=True,
+ )
channels = Dict(
title=_("Source snap channels for builds produced by this request"),
- key_type=TextLine(), required=False, readonly=True)
+ key_type=TextLine(),
+ required=False,
+ readonly=True,
+ )
architectures = Set(
title=_("If set, this request is limited to these architecture tags"),
- value_type=TextLine(), required=False, readonly=True)
+ value_type=TextLine(),
+ required=False,
+ readonly=True,
+ )
class ISnapView(Interface):
@@ -370,25 +405,33 @@ class ISnapView(Interface):
id = Int(title=_("ID"), required=True, readonly=True)
- date_created = exported(Datetime(
- title=_("Date created"), required=True, readonly=True))
+ date_created = exported(
+ Datetime(title=_("Date created"), required=True, readonly=True)
+ )
- registrant = exported(PublicPersonChoice(
- title=_("Registrant"), required=True, readonly=True,
- vocabulary="ValidPersonOrTeam",
- description=_("The person who registered this snap package.")))
+ registrant = exported(
+ PublicPersonChoice(
+ title=_("Registrant"),
+ required=True,
+ readonly=True,
+ vocabulary="ValidPersonOrTeam",
+ description=_("The person who registered this snap package."),
+ )
+ )
source = Attribute(
- "The source branch for this snap package (VCS-agnostic).")
+ "The source branch for this snap package (VCS-agnostic)."
+ )
available_processors = Attribute(
"The architectures that are available to be enabled or disabled for "
- "this snap package.")
+ "this snap package."
+ )
@call_with(check_permissions=True, user=REQUEST_USER)
@operation_parameters(
- processors=List(
- value_type=Reference(schema=IProcessor), required=True))
+ processors=List(value_type=Reference(schema=IProcessor), required=True)
+ )
@export_write_operation()
@operation_for_version("devel")
def setProcessors(processors, check_permissions=False, user=None):
@@ -400,11 +443,17 @@ class ISnapView(Interface):
:return: Sequence of `IDistroArchSeries` instances.
"""
- can_upload_to_store = exported(Bool(
- title=_("Can upload to store"), required=True, readonly=True,
- description=_(
- "Whether everything is set up to allow uploading builds of this "
- "snap package to the store.")))
+ can_upload_to_store = exported(
+ Bool(
+ title=_("Can upload to store"),
+ required=True,
+ readonly=True,
+ description=_(
+ "Whether everything is set up to allow uploading builds of "
+ "this snap package to the store."
+ ),
+ )
+ )
@call_with(requester=REQUEST_USER)
@operation_parameters(
@@ -417,14 +466,25 @@ class ISnapView(Interface):
description=_(
"A dictionary mapping snap names to channels to use for this "
"build. Currently only 'core', 'core18', 'core20', 'core22', "
- "and 'snapcraft' keys are supported."),
- key_type=TextLine(), required=False))
+ "and 'snapcraft' keys are supported."
+ ),
+ key_type=TextLine(),
+ required=False,
+ ),
+ )
# Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
@export_factory_operation(Interface, [])
@operation_for_version("devel")
- def requestBuild(requester, archive, distro_arch_series, pocket,
- snap_base=None, channels=None, build_request=None,
- target_architectures=None):
+ def requestBuild(
+ requester,
+ archive,
+ distro_arch_series,
+ pocket,
+ snap_base=None,
+ channels=None,
+ build_request=None,
+ target_architectures=None,
+ ):
"""Request that the snap package be built.
:param requester: The person requesting the build.
@@ -450,8 +510,12 @@ class ISnapView(Interface):
description=_(
"A dictionary mapping snap names to channels to use for this "
"build. Currently only 'core', 'core18', 'core20', 'core22', "
- "and 'snapcraft' keys are supported."),
- key_type=TextLine(), required=False))
+ "and 'snapcraft' keys are supported."
+ ),
+ key_type=TextLine(),
+ required=False,
+ ),
+ )
@export_factory_operation(ISnapBuildRequest, [])
@operation_for_version("devel")
def requestBuilds(requester, archive, pocket, channels=None):
@@ -469,10 +533,17 @@ class ISnapView(Interface):
:return: An `ISnapBuildRequest`.
"""
- def requestBuildsFromJob(requester, archive, pocket, channels=None,
- architectures=None, allow_failures=False,
- fetch_snapcraft_yaml=True, build_request=None,
- logger=None):
+ def requestBuildsFromJob(
+ requester,
+ archive,
+ pocket,
+ channels=None,
+ architectures=None,
+ allow_failures=False,
+ fetch_snapcraft_yaml=True,
+ build_request=None,
+ logger=None,
+ ):
"""Synchronous part of `Snap.requestBuilds`.
Request that the snap package be built for relevant architectures.
@@ -507,22 +578,36 @@ class ISnapView(Interface):
:return: `ISnapBuildRequest`.
"""
- pending_build_requests = exported(doNotSnapshot(CollectionField(
- title=_("Pending build requests for this snap package."),
- value_type=Reference(ISnapBuildRequest),
- required=True, readonly=True)))
+ pending_build_requests = exported(
+ doNotSnapshot(
+ CollectionField(
+ title=_("Pending build requests for this snap package."),
+ value_type=Reference(ISnapBuildRequest),
+ required=True,
+ readonly=True,
+ )
+ )
+ )
- failed_build_requests = exported(doNotSnapshot(CollectionField(
- title=_("Failed build requests for this snap package."),
- value_type=Reference(ISnapBuildRequest),
- required=True, readonly=True)))
+ failed_build_requests = exported(
+ doNotSnapshot(
+ CollectionField(
+ title=_("Failed build requests for this snap package."),
+ value_type=Reference(ISnapBuildRequest),
+ required=True,
+ readonly=True,
+ )
+ )
+ )
# XXX cjwatson 2018-06-20: Deprecated as an exported method; can become
# an internal helper method once production JavaScript no longer uses
# it.
@operation_parameters(
snap_build_ids=List(
- title=_("A list of snap build IDs."), value_type=Int()))
+ title=_("A list of snap build IDs."), value_type=Int()
+ )
+ )
@export_read_operation()
@operation_for_version("devel")
def getBuildSummariesForSnapBuildIds(snap_build_ids):
@@ -536,8 +621,8 @@ class ISnapView(Interface):
@call_with(user=REQUEST_USER)
@operation_parameters(
- store_upload_revision=Int(title="Store revision",
- required=True))
+ store_upload_revision=Int(title="Store revision", required=True)
+ )
# Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
@operation_returns_entry(Interface)
@export_read_operation()
@@ -554,11 +639,16 @@ class ISnapView(Interface):
@call_with(user=REQUEST_USER)
@operation_parameters(
request_ids=List(
- title=_("A list of snap build request IDs."), value_type=Int(),
- required=False),
+ title=_("A list of snap build request IDs."),
+ value_type=Int(),
+ required=False,
+ ),
build_ids=List(
- title=_("A list of snap build IDs."), value_type=Int(),
- required=False))
+ title=_("A list of snap build IDs."),
+ value_type=Int(),
+ required=False,
+ ),
+ )
@export_read_operation()
@operation_for_version("devel")
def getBuildSummaries(request_ids=None, build_ids=None, user=None):
@@ -572,38 +662,66 @@ class ISnapView(Interface):
builds respectively.
"""
- builds = exported(doNotSnapshot(CollectionField(
- title=_("All builds of this snap package."),
- description=_(
- "All builds of this snap package, sorted in descending order "
- "of finishing (or starting if not completed successfully)."),
- # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
- value_type=Reference(schema=Interface), readonly=True)))
+ builds = exported(
+ doNotSnapshot(
+ CollectionField(
+ title=_("All builds of this snap package."),
+ description=_(
+ "All builds of this snap package, sorted in descending "
+ "order of finishing (or starting if not completed "
+ "successfully)."
+ ),
+ # Really ISnapBuild, patched in
+ # lp.snappy.interfaces.webservice.
+ value_type=Reference(schema=Interface),
+ readonly=True,
+ )
+ )
+ )
- completed_builds = exported(doNotSnapshot(CollectionField(
- title=_("Completed builds of this snap package."),
- description=_(
- "Completed builds of this snap package, sorted in descending "
- "order of finishing."),
- # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
- value_type=Reference(schema=Interface), readonly=True)))
+ completed_builds = exported(
+ doNotSnapshot(
+ CollectionField(
+ title=_("Completed builds of this snap package."),
+ description=_(
+ "Completed builds of this snap package, sorted in "
+ "descending order of finishing."
+ ),
+ # Really ISnapBuild, patched in
+ # lp.snappy.interfaces.webservice.
+ value_type=Reference(schema=Interface),
+ readonly=True,
+ )
+ )
+ )
- pending_builds = exported(doNotSnapshot(CollectionField(
- title=_("Pending builds of this snap package."),
- description=_(
- "Pending builds of this snap package, sorted in descending "
- "order of creation."),
- # Really ISnapBuild, patched in lp.snappy.interfaces.webservice.
- value_type=Reference(schema=Interface), readonly=True)))
+ pending_builds = exported(
+ doNotSnapshot(
+ CollectionField(
+ title=_("Pending builds of this snap package."),
+ description=_(
+ "Pending builds of this snap package, sorted in "
+ "descending order of creation."
+ ),
+ # Really ISnapBuild, patched in
+ # lp.snappy.interfaces.webservice.
+ value_type=Reference(schema=Interface),
+ readonly=True,
+ )
+ )
+ )
subscriptions = CollectionField(
title=_("SnapSubscriptions associated with this snap recipe."),
readonly=True,
- value_type=Reference(Interface))
+ value_type=Reference(Interface),
+ )
subscribers = CollectionField(
title=_("Persons subscribed to this snap recipe."),
- readonly=True, value_type=Reference(IPerson))
+ readonly=True,
+ value_type=Reference(IPerson),
+ )
def getSubscription(person):
"""Returns the person's snap subscription for this snap recipe."""
@@ -634,8 +752,9 @@ class ISnapEdit(IWebhookTarget):
@operation_returns_collection_of(Interface)
@export_write_operation()
@operation_for_version("devel")
- def requestAutoBuilds(allow_failures=False, fetch_snapcraft_yaml=False,
- logger=None):
+ def requestAutoBuilds(
+ allow_failures=False, fetch_snapcraft_yaml=False, logger=None
+ ):
"""Create and return automatic builds for this snap package.
This webservice API method is deprecated. It is normally better to
@@ -684,13 +803,18 @@ class ISnapEdit(IWebhookTarget):
root_macaroon=TextLine(
title=_("Serialized root macaroon"),
description=_(
- "Only required if not already set by beginAuthorization."),
- required=False),
+ "Only required if not already set by beginAuthorization."
+ ),
+ required=False,
+ ),
discharge_macaroon=TextLine(
title=_("Serialized discharge macaroon"),
description=_(
- "Only required if root macaroon has SSO third-party caveat."),
- required=False))
+ "Only required if root macaroon has SSO third-party caveat."
+ ),
+ required=False,
+ ),
+ )
@export_write_operation()
@operation_for_version("devel")
def completeAuthorization(root_macaroon=None, discharge_macaroon=None):
@@ -719,187 +843,322 @@ class ISnapEditableAttributes(IHasOwner):
These attributes need launchpad.View to see, and launchpad.Edit to change.
"""
- date_last_modified = exported(Datetime(
- title=_("Date last modified"), required=True, readonly=True))
- owner = exported(PersonChoice(
- title=_("Owner"), required=True, readonly=False,
- vocabulary="AllUserTeamsParticipationPlusSelf",
- description=_("The owner of this snap package.")))
+ date_last_modified = exported(
+ Datetime(title=_("Date last modified"), required=True, readonly=True)
+ )
+
+ owner = exported(
+ PersonChoice(
+ title=_("Owner"),
+ required=True,
+ readonly=False,
+ vocabulary="AllUserTeamsParticipationPlusSelf",
+ description=_("The owner of this snap package."),
+ )
+ )
project = ReferenceChoice(
- title=_('The project that this Snap is associated with'),
- schema=IProduct, vocabulary='Product',
- required=False, readonly=False)
+ title=_("The project that this Snap is associated with"),
+ schema=IProduct,
+ vocabulary="Product",
+ required=False,
+ readonly=False,
+ )
- private = exported(Bool(
- title=_("Private"), required=False, readonly=False,
- description=_("Whether or not this snap is private.")))
+ private = exported(
+ Bool(
+ title=_("Private"),
+ required=False,
+ readonly=False,
+ description=_("Whether or not this snap is private."),
+ )
+ )
- information_type = exported(Choice(
- title=_("Information type"), vocabulary=InformationType,
- required=True, readonly=False, default=InformationType.PUBLIC,
- description=_(
- "The type of information contained in this Snap recipe.")))
+ information_type = exported(
+ Choice(
+ title=_("Information type"),
+ vocabulary=InformationType,
+ required=True,
+ readonly=False,
+ default=InformationType.PUBLIC,
+ description=_(
+ "The type of information contained in this Snap recipe."
+ ),
+ )
+ )
- distro_series = exported(Reference(
- IDistroSeries, title=_("Distro Series"),
- required=False, readonly=False,
- description=_(
- "The series for which the snap package should be built. If not "
- "set, Launchpad will infer an appropriate series from "
- "snapcraft.yaml.")))
-
- name = exported(TextLine(
- title=_("Snap recipe name"), required=True, readonly=False,
- constraint=name_validator,
- description=_("The name of the snap build recipe.")))
-
- description = exported(Text(
- title=_("Description"), required=False, readonly=False,
- description=_("A description of the snap package.")))
-
- branch = exported(ReferenceChoice(
- title=_("Bazaar branch"), schema=IBranch, vocabulary="Branch",
- required=False, readonly=False,
- description=_(
- "A Bazaar branch containing a snap/snapcraft.yaml, "
- "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
- ".snapcraft.yaml recipe at the top level.")))
+ distro_series = exported(
+ Reference(
+ IDistroSeries,
+ title=_("Distro Series"),
+ required=False,
+ readonly=False,
+ description=_(
+ "The series for which the snap package should be built. If "
+ "not set, Launchpad will infer an appropriate series from "
+ "snapcraft.yaml."
+ ),
+ )
+ )
- git_repository = exported(ReferenceChoice(
- title=_("Git repository"),
- schema=IGitRepository, vocabulary="GitRepository",
- required=False, readonly=True,
- description=_(
- "A Git repository with a branch containing a snap/snapcraft.yaml, "
- "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
- ".snapcraft.yaml recipe at the top level.")))
+ name = exported(
+ TextLine(
+ title=_("Snap recipe name"),
+ required=True,
+ readonly=False,
+ constraint=name_validator,
+ description=_("The name of the snap build recipe."),
+ )
+ )
- git_repository_url = exported(URIField(
- title=_("Git repository URL"), required=False, readonly=True,
- description=_(
- "The URL of a Git repository with a branch containing a "
- "snap/snapcraft.yaml, build-aux/snap/snapcraft.yaml, "
- "snapcraft.yaml, or .snapcraft.yaml recipe at the top level."),
- allowed_schemes=["git", "http", "https"],
- allow_userinfo=True,
- allow_port=True,
- allow_query=False,
- allow_fragment=False,
- trailing_slash=False))
+ description = exported(
+ Text(
+ title=_("Description"),
+ required=False,
+ readonly=False,
+ description=_("A description of the snap package."),
+ )
+ )
+
+ branch = exported(
+ ReferenceChoice(
+ title=_("Bazaar branch"),
+ schema=IBranch,
+ vocabulary="Branch",
+ required=False,
+ readonly=False,
+ description=_(
+ "A Bazaar branch containing a snap/snapcraft.yaml, "
+ "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
+ ".snapcraft.yaml recipe at the top level."
+ ),
+ )
+ )
+
+ git_repository = exported(
+ ReferenceChoice(
+ title=_("Git repository"),
+ schema=IGitRepository,
+ vocabulary="GitRepository",
+ required=False,
+ readonly=True,
+ description=_(
+ "A Git repository with a branch containing a "
+ "snap/snapcraft.yaml, build-aux/snap/snapcraft.yaml, "
+ "snapcraft.yaml, or .snapcraft.yaml recipe at the top level."
+ ),
+ )
+ )
+
+ git_repository_url = exported(
+ URIField(
+ title=_("Git repository URL"),
+ required=False,
+ readonly=True,
+ description=_(
+ "The URL of a Git repository with a branch containing a "
+ "snap/snapcraft.yaml, build-aux/snap/snapcraft.yaml, "
+ "snapcraft.yaml, or .snapcraft.yaml recipe at the top level."
+ ),
+ allowed_schemes=["git", "http", "https"],
+ allow_userinfo=True,
+ allow_port=True,
+ allow_query=False,
+ allow_fragment=False,
+ trailing_slash=False,
+ )
+ )
git_path = TextLine(
- title=_("Git branch path"), required=False, readonly=False,
+ title=_("Git branch path"),
+ required=False,
+ readonly=False,
description=_(
"The path of the Git branch containing a snap/snapcraft.yaml, "
"build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
- ".snapcraft.yaml recipe at the top level."))
+ ".snapcraft.yaml recipe at the top level."
+ ),
+ )
_api_git_path = exported(
TextLine(
- title=git_path.title, required=False, readonly=False,
- description=git_path.description),
- exported_as="git_path")
+ title=git_path.title,
+ required=False,
+ readonly=False,
+ description=git_path.description,
+ ),
+ exported_as="git_path",
+ )
- git_ref = exported(Reference(
- IGitRef, title=_("Git branch"), required=False, readonly=False,
- description=_(
- "The Git branch containing a snap/snapcraft.yaml, "
- "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
- ".snapcraft.yaml recipe at the top level.")))
+ git_ref = exported(
+ Reference(
+ IGitRef,
+ title=_("Git branch"),
+ required=False,
+ readonly=False,
+ description=_(
+ "The Git branch containing a snap/snapcraft.yaml, "
+ "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
+ ".snapcraft.yaml recipe at the top level."
+ ),
+ )
+ )
- build_source_tarball = exported(Bool(
- title=_("Build source tarball"),
- required=True, readonly=False,
- description=_(
- "Whether builds of this snap package should also build a tarball "
- "containing all source code, including external dependencies.")))
+ build_source_tarball = exported(
+ Bool(
+ title=_("Build source tarball"),
+ required=True,
+ readonly=False,
+ description=_(
+ "Whether builds of this snap package should also build a "
+ "tarball containing all source code, including external "
+ "dependencies."
+ ),
+ )
+ )
- auto_build = exported(Bool(
- title=_("Automatically build when branch changes"),
- required=True, readonly=False,
- description=_(
- "Whether this snap package is built automatically when the branch "
- "containing its snap/snapcraft.yaml, "
- "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
- ".snapcraft.yaml recipe changes.")))
+ auto_build = exported(
+ Bool(
+ title=_("Automatically build when branch changes"),
+ required=True,
+ readonly=False,
+ description=_(
+ "Whether this snap package is built automatically when the "
+ "branch containing its snap/snapcraft.yaml, "
+ "build-aux/snap/snapcraft.yaml, snapcraft.yaml, or "
+ ".snapcraft.yaml recipe changes."
+ ),
+ )
+ )
- auto_build_archive = exported(Reference(
- IArchive, title=_("Source archive for automatic builds"),
- required=False, readonly=False,
- description=_(
- "The archive from which automatic builds of this snap package "
- "should be built.")))
+ auto_build_archive = exported(
+ Reference(
+ IArchive,
+ title=_("Source archive for automatic builds"),
+ required=False,
+ readonly=False,
+ description=_(
+ "The archive from which automatic builds of this snap package "
+ "should be built."
+ ),
+ )
+ )
- auto_build_pocket = exported(Choice(
- title=_("Pocket for automatic builds"),
- vocabulary=PackagePublishingPocket, required=False, readonly=False,
- description=_(
- "The package stream within the source archive and distribution "
- "series to use when building the snap package. If the source "
- "archive is a PPA, then the PPA's archive dependencies will be "
- "used to select the pocket in the distribution's primary "
- "archive.")))
-
- auto_build_channels = exported(Dict(
- title=_("Source snap channels for automatic builds"),
- key_type=TextLine(), required=False, readonly=False,
- description=_(
- "A dictionary mapping snap names to channels to use when building "
- "this snap package. Currently only 'core', 'core18', "
- "'core20', 'core22', and 'snapcraft' keys are supported.")))
+ auto_build_pocket = exported(
+ Choice(
+ title=_("Pocket for automatic builds"),
+ vocabulary=PackagePublishingPocket,
+ required=False,
+ readonly=False,
+ description=_(
+ "The package stream within the source archive and "
+ "distribution series to use when building the snap package. "
+ "If the source archive is a PPA, then the PPA's archive "
+ "dependencies will be used to select the pocket in the "
+ "distribution's primary archive."
+ ),
+ )
+ )
- is_stale = exported(Bool(
- title=_("Snap package is stale and is due to be rebuilt."),
- required=True, readonly=True))
+ auto_build_channels = exported(
+ Dict(
+ title=_("Source snap channels for automatic builds"),
+ key_type=TextLine(),
+ required=False,
+ readonly=False,
+ description=_(
+ "A dictionary mapping snap names to channels to use when "
+ "building this snap package. Currently only 'core', "
+ "'core18', 'core20', 'core22', and 'snapcraft' keys are "
+ "supported."
+ ),
+ )
+ )
- store_upload = exported(Bool(
- title=_("Automatically upload to store"),
- required=True, readonly=False,
- description=_(
- "Whether builds of this snap package are automatically uploaded "
- "to the store.")))
+ is_stale = exported(
+ Bool(
+ title=_("Snap package is stale and is due to be rebuilt."),
+ required=True,
+ readonly=True,
+ )
+ )
+
+ store_upload = exported(
+ Bool(
+ title=_("Automatically upload to store"),
+ required=True,
+ readonly=False,
+ description=_(
+ "Whether builds of this snap package are automatically "
+ "uploaded to the store."
+ ),
+ )
+ )
# XXX cjwatson 2016-12-08: We should limit this to series that are
# compatible with distro_series, but that entails validating the case
# where both are changed in a single PATCH request in such a way that
# neither is compatible with the old value of the other. As far as I
# can tell lazr.restful only supports per-field validation.
- store_series = exported(ReferenceChoice(
- title=_("Store series"),
- schema=ISnappySeries, vocabulary="SnappySeries",
- required=False, readonly=False,
- description=_(
- "The series in which this snap package should be published in the "
- "store.")))
+ store_series = exported(
+ ReferenceChoice(
+ title=_("Store series"),
+ schema=ISnappySeries,
+ vocabulary="SnappySeries",
+ required=False,
+ readonly=False,
+ description=_(
+ "The series in which this snap package should be published in "
+ "the store."
+ ),
+ )
+ )
store_distro_series = ReferenceChoice(
title=_("Store and distro series"),
- schema=ISnappyDistroSeries, vocabulary="SnappyDistroSeries",
- required=False, readonly=False)
+ schema=ISnappyDistroSeries,
+ vocabulary="SnappyDistroSeries",
+ required=False,
+ readonly=False,
+ )
- store_name = exported(TextLine(
- title=_("Registered store package name"),
- required=False, readonly=False,
- description=_(
- "The registered name of this snap package in the store.")))
+ store_name = exported(
+ TextLine(
+ title=_("Registered store package name"),
+ required=False,
+ readonly=False,
+ description=_(
+ "The registered name of this snap package in the store."
+ ),
+ )
+ )
store_secrets = List(
- value_type=TextLine(), title=_("Store upload tokens"),
- required=False, readonly=False,
+ value_type=TextLine(),
+ title=_("Store upload tokens"),
+ required=False,
+ readonly=False,
description=_(
"Serialized secrets issued by the store and the login service to "
- "authorize uploads of this snap package."))
+ "authorize uploads of this snap package."
+ ),
+ )
- store_channels = exported(List(
- title=_("Store channels"),
- required=False, readonly=False, constraint=channels_validator,
- description=_(
- "Channels to release this snap package to after uploading it to "
- "the store. A channel is defined by a combination of an optional "
- " track, a risk, and an optional branch, e.g. "
- "'2.1/stable/fix-123', '2.1/stable', 'stable/fix-123', or "
- "'stable'.")))
+ store_channels = exported(
+ List(
+ title=_("Store channels"),
+ required=False,
+ readonly=False,
+ constraint=channels_validator,
+ description=_(
+ "Channels to release this snap package to after uploading it "
+ "to the store. A channel is defined by a combination of an "
+ "optional track, a risk, and an optional branch, e.g. "
+ "'2.1/stable/fix-123', '2.1/stable', 'stable/fix-123', or "
+ "'stable'."
+ ),
+ )
+ )
def setProject(project):
"""Set the pillar project of this snap recipe."""
@@ -911,23 +1170,37 @@ class ISnapAdminAttributes(Interface):
These attributes need launchpad.View to see, and launchpad.Admin to change.
"""
- require_virtualized = exported(Bool(
- title=_("Require virtualized builders"), required=True, readonly=False,
- description=_("Only build this snap package on virtual builders.")))
+ require_virtualized = exported(
+ Bool(
+ title=_("Require virtualized builders"),
+ required=True,
+ readonly=False,
+ description=_("Only build this snap package on virtual builders."),
+ )
+ )
- processors = exported(CollectionField(
- title=_("Processors"),
- description=_(
- "The architectures for which the snap package should be built."),
- value_type=Reference(schema=IProcessor),
- readonly=False))
+ processors = exported(
+ CollectionField(
+ title=_("Processors"),
+ description=_(
+ "The architectures for which the snap package should be built."
+ ),
+ value_type=Reference(schema=IProcessor),
+ readonly=False,
+ )
+ )
- allow_internet = exported(Bool(
- title=_("Allow external network access"),
- required=True, readonly=False,
- description=_(
- "Allow access to external network resources via a proxy. "
- "Resources hosted on Launchpad itself are always allowed.")))
+ allow_internet = exported(
+ Bool(
+ title=_("Allow external network access"),
+ required=True,
+ readonly=False,
+ description=_(
+ "Allow access to external network resources via a proxy. "
+ "Resources hosted on Launchpad itself are always allowed."
+ ),
+ )
+ )
def subscribe(person, subscribed_by):
"""Subscribe a person to this snap recipe."""
@@ -938,8 +1211,13 @@ class ISnapAdminAttributes(Interface):
# "devel".
@exported_as_webservice_entry(as_of="beta")
class ISnap(
- ISnapView, ISnapEdit, ISnapEditableAttributes, ISnapAdminAttributes,
- IPrivacy, IInformationType):
+ ISnapView,
+ ISnapEdit,
+ ISnapEditableAttributes,
+ ISnapAdminAttributes,
+ IPrivacy,
+ IInformationType,
+):
"""A buildable snap package."""
@@ -951,24 +1229,57 @@ class ISnapSet(Interface):
@operation_parameters(
information_type=copy_field(ISnap["information_type"], required=False),
processors=List(
- value_type=Reference(schema=IProcessor), required=False))
+ value_type=Reference(schema=IProcessor), required=False
+ ),
+ )
@export_factory_operation(
- ISnap, [
- "owner", "distro_series", "name", "description", "branch",
- "git_repository", "git_repository_url", "git_path", "git_ref",
- "auto_build", "auto_build_archive", "auto_build_pocket",
- "store_upload", "store_series", "store_name", "store_channels",
- "project"])
+ ISnap,
+ [
+ "owner",
+ "distro_series",
+ "name",
+ "description",
+ "branch",
+ "git_repository",
+ "git_repository_url",
+ "git_path",
+ "git_ref",
+ "auto_build",
+ "auto_build_archive",
+ "auto_build_pocket",
+ "store_upload",
+ "store_series",
+ "store_name",
+ "store_channels",
+ "project",
+ ],
+ )
@operation_for_version("devel")
- def new(registrant, owner, distro_series, name, description=None,
- branch=None, git_repository=None, git_repository_url=None,
- git_path=None, git_ref=None, auto_build=False,
- auto_build_archive=None, auto_build_pocket=None,
- require_virtualized=True, processors=None, date_created=None,
- information_type=InformationType.PUBLIC, store_upload=False,
- store_series=None,
- store_name=None, store_secrets=None, store_channels=None,
- project=None):
+ def new(
+ registrant,
+ owner,
+ distro_series,
+ name,
+ description=None,
+ branch=None,
+ git_repository=None,
+ git_repository_url=None,
+ git_path=None,
+ git_ref=None,
+ auto_build=False,
+ auto_build_archive=None,
+ auto_build_pocket=None,
+ require_virtualized=True,
+ processors=None,
+ date_created=None,
+ information_type=InformationType.PUBLIC,
+ store_upload=False,
+ store_series=None,
+ store_name=None,
+ store_secrets=None,
+ store_channels=None,
+ project=None,
+ ):
"""Create an `ISnap`."""
def exists(owner, name):
@@ -983,12 +1294,14 @@ class ISnapSet(Interface):
"""Return all snap packages with the given ids."""
def isValidInformationType(
- information_type, owner, branch=None, git_ref=None):
+ information_type, owner, branch=None, git_ref=None
+ ):
"""Whether or not the information type context is valid."""
@operation_parameters(
owner=Reference(IPerson, title=_("Owner"), required=True),
- name=TextLine(title=_("Snap name"), required=True))
+ name=TextLine(title=_("Snap name"), required=True),
+ )
@operation_returns_entry(ISnap)
@export_read_operation()
@operation_for_version("devel")
@@ -999,7 +1312,8 @@ class ISnapSet(Interface):
"""Returns the appropriate `ISnap` for the given pillar and name."""
@operation_parameters(
- owner=Reference(IPerson, title=_("Owner"), required=True))
+ owner=Reference(IPerson, title=_("Owner"), required=True)
+ )
@operation_returns_collection_of(ISnap)
@export_read_operation()
@operation_for_version("devel")
@@ -1053,7 +1367,8 @@ class ISnapSet(Interface):
@operation_parameters(
url=TextLine(title=_("The URL to search for.")),
- owner=Reference(IPerson, title=_("Owner"), required=False))
+ owner=Reference(IPerson, title=_("Owner"), required=False),
+ )
@call_with(visible_by_user=REQUEST_USER)
@operation_returns_collection_of(ISnap)
@export_read_operation()
@@ -1073,7 +1388,8 @@ class ISnapSet(Interface):
@operation_parameters(
url_prefix=TextLine(title=_("The URL prefix to search for.")),
- owner=Reference(IPerson, title=_("Owner"), required=False))
+ owner=Reference(IPerson, title=_("Owner"), required=False),
+ )
@call_with(visible_by_user=REQUEST_USER)
@operation_returns_collection_of(ISnap)
@export_read_operation()
@@ -1093,8 +1409,10 @@ class ISnapSet(Interface):
@operation_parameters(
url_prefixes=List(
- title=_("The URL prefixes to search for."), value_type=TextLine()),
- owner=Reference(IPerson, title=_("Owner"), required=False))
+ title=_("The URL prefixes to search for."), value_type=TextLine()
+ ),
+ owner=Reference(IPerson, title=_("Owner"), required=False),
+ )
@call_with(visible_by_user=REQUEST_USER)
@operation_returns_collection_of(ISnap)
@export_read_operation()
@@ -1115,8 +1433,10 @@ class ISnapSet(Interface):
@operation_parameters(
store_name=TextLine(
- title=_("The registered store package name to search for.")),
- owner=Reference(IPerson, title=_("Owner"), required=False))
+ title=_("The registered store package name to search for.")
+ ),
+ owner=Reference(IPerson, title=_("Owner"), required=False),
+ )
@call_with(visible_by_user=REQUEST_USER)
@operation_returns_collection_of(ISnap)
@export_read_operation()
diff --git a/lib/lp/snappy/interfaces/snapbase.py b/lib/lp/snappy/interfaces/snapbase.py
index d635bfb..08cfd79 100644
--- a/lib/lp/snappy/interfaces/snapbase.py
+++ b/lib/lp/snappy/interfaces/snapbase.py
@@ -8,11 +8,12 @@ __all__ = [
"ISnapBase",
"ISnapBaseSet",
"NoSuchSnapBase",
- ]
+]
import http.client
from lazr.restful.declarations import (
+ REQUEST_USER,
call_with,
collection_default_content,
error_status,
@@ -27,33 +28,19 @@ from lazr.restful.declarations import (
operation_for_version,
operation_parameters,
operation_returns_entry,
- REQUEST_USER,
- )
-from lazr.restful.fields import (
- CollectionField,
- Reference,
- )
+)
+from lazr.restful.fields import CollectionField, Reference
from lazr.restful.interface import copy_field
from zope.component import getUtility
from zope.interface import Interface
-from zope.schema import (
- Bool,
- Datetime,
- Dict,
- Int,
- List,
- TextLine,
- )
+from zope.schema import Bool, Datetime, Dict, Int, List, TextLine
from lp import _
from lp.app.errors import NameLookupFailed
from lp.app.validators.name import name_validator
from lp.buildmaster.interfaces.processor import IProcessor
from lp.registry.interfaces.distroseries import IDistroSeries
-from lp.services.fields import (
- ContentNameField,
- PublicPersonChoice,
- )
+from lp.services.fields import ContentNameField, PublicPersonChoice
from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.interfaces.archivedependency import IArchiveDependency
@@ -92,24 +79,39 @@ class ISnapBaseView(Interface):
id = Int(title=_("ID"), required=True, readonly=True)
- date_created = exported(Datetime(
- title=_("Date created"), required=True, readonly=True))
+ date_created = exported(
+ Datetime(title=_("Date created"), required=True, readonly=True)
+ )
- registrant = exported(PublicPersonChoice(
- title=_("Registrant"), required=True, readonly=True,
- vocabulary="ValidPersonOrTeam",
- description=_("The person who registered this base.")))
+ registrant = exported(
+ PublicPersonChoice(
+ title=_("Registrant"),
+ required=True,
+ readonly=True,
+ vocabulary="ValidPersonOrTeam",
+ description=_("The person who registered this base."),
+ )
+ )
- is_default = exported(Bool(
- title=_("Is default?"), required=True, readonly=True,
- description=_(
- "Whether this base is the default for snaps that do not specify a "
- "base.")))
+ is_default = exported(
+ Bool(
+ title=_("Is default?"),
+ required=True,
+ readonly=True,
+ description=_(
+ "Whether this base is the default for snaps that do not "
+ "specify a base."
+ ),
+ )
+ )
- dependencies = exported(CollectionField(
- title=_("Archive dependencies for this snap base."),
- value_type=Reference(schema=IArchiveDependency),
- readonly=True))
+ dependencies = exported(
+ CollectionField(
+ title=_("Archive dependencies for this snap base."),
+ value_type=Reference(schema=IArchiveDependency),
+ readonly=True,
+ )
+ )
@operation_parameters(dependency=Reference(schema=IArchive))
@operation_returns_entry(schema=IArchiveDependency)
@@ -124,11 +126,14 @@ class ISnapBaseView(Interface):
could not be found.
"""
- processors = exported(CollectionField(
- title=_("Processors"),
- description=_("The architectures that the snap base supports."),
- value_type=Reference(schema=IProcessor),
- readonly=True))
+ processors = exported(
+ CollectionField(
+ title=_("Processors"),
+ description=_("The architectures that the snap base supports."),
+ value_type=Reference(schema=IProcessor),
+ readonly=True,
+ )
+ )
class ISnapBaseEditableAttributes(Interface):
@@ -137,26 +142,44 @@ class ISnapBaseEditableAttributes(Interface):
Anyone can view these attributes, but they need launchpad.Edit to change.
"""
- name = exported(SnapBaseNameField(
- title=_("Name"), required=True, readonly=False,
- constraint=name_validator))
+ name = exported(
+ SnapBaseNameField(
+ title=_("Name"),
+ required=True,
+ readonly=False,
+ constraint=name_validator,
+ )
+ )
- display_name = exported(TextLine(
- title=_("Display name"), required=True, readonly=False))
+ display_name = exported(
+ TextLine(title=_("Display name"), required=True, readonly=False)
+ )
- distro_series = exported(Reference(
- IDistroSeries, title=_("Distro series"),
- required=True, readonly=False))
+ distro_series = exported(
+ Reference(
+ IDistroSeries,
+ title=_("Distro series"),
+ required=True,
+ readonly=False,
+ )
+ )
- build_channels = exported(Dict(
- title=_("Source snap channels for builds"),
- key_type=TextLine(), required=True, readonly=False,
- description=_(
- "A dictionary mapping snap names to channels to use when building "
- "snaps that specify this base. The special '_byarch' key may "
- "have a mapping of architecture names to mappings of snap names "
- "to channels, which if present override the channels declared at "
- "the top level when building for those architectures.")))
+ build_channels = exported(
+ Dict(
+ title=_("Source snap channels for builds"),
+ key_type=TextLine(),
+ required=True,
+ readonly=False,
+ description=_(
+ "A dictionary mapping snap names to channels to use when "
+ "building snaps that specify this base. The special "
+ "'_byarch' key may have a mapping of architecture names to "
+ "mappings of snap names to channels, which if present "
+ "override the channels declared at the top level when "
+ "building for those architectures."
+ ),
+ )
+ )
class ISnapBaseEdit(Interface):
@@ -177,7 +200,8 @@ class ISnapBaseEdit(Interface):
"""
@operation_parameters(
- component=copy_field(IArchiveDependency["component_name"]))
+ component=copy_field(IArchiveDependency["component_name"])
+ )
@export_operation_as("addArchiveDependency")
@export_factory_operation(IArchiveDependency, ["dependency", "pocket"])
@operation_for_version("devel")
@@ -195,8 +219,7 @@ class ISnapBaseEdit(Interface):
:return: an `IArchiveDependency`.
"""
- @operation_parameters(
- dependency=Reference(schema=IArchive, required=True))
+ @operation_parameters(dependency=Reference(schema=IArchive, required=True))
@export_write_operation()
@operation_for_version("devel")
def removeArchiveDependency(dependency):
@@ -206,8 +229,8 @@ class ISnapBaseEdit(Interface):
"""
@operation_parameters(
- processors=List(
- value_type=Reference(schema=IProcessor), required=True))
+ processors=List(value_type=Reference(schema=IProcessor), required=True)
+ )
@export_write_operation()
@operation_for_version("devel")
def setProcessors(processors):
@@ -236,16 +259,27 @@ class ISnapBaseSetEdit(Interface):
@call_with(registrant=REQUEST_USER)
@operation_parameters(
processors=List(
- value_type=Reference(schema=IProcessor), required=False))
+ value_type=Reference(schema=IProcessor), required=False
+ )
+ )
@export_factory_operation(
- ISnapBase, ["name", "display_name", "distro_series", "build_channels"])
+ ISnapBase, ["name", "display_name", "distro_series", "build_channels"]
+ )
@operation_for_version("devel")
- def new(registrant, name, display_name, distro_series, build_channels,
- processors=None, date_created=None):
+ def new(
+ registrant,
+ name,
+ display_name,
+ distro_series,
+ build_channels,
+ processors=None,
+ date_created=None,
+ ):
"""Create an `ISnapBase`."""
@operation_parameters(
- snap_base=Reference(title=_("Base"), required=True, schema=ISnapBase))
+ snap_base=Reference(title=_("Base"), required=True, schema=ISnapBase)
+ )
@export_write_operation()
@operation_for_version("devel")
def setDefault(snap_base):
@@ -268,8 +302,7 @@ class ISnapBaseSet(ISnapBaseSetEdit):
def __getitem__(name):
"""Return the `ISnapBase` with this name."""
- @operation_parameters(
- name=TextLine(title=_("Base name"), required=True))
+ @operation_parameters(name=TextLine(title=_("Base name"), required=True))
@operation_returns_entry(ISnapBase)
@export_read_operation()
@operation_for_version("devel")
diff --git a/lib/lp/snappy/interfaces/snapbuild.py b/lib/lp/snappy/interfaces/snapbuild.py
index 60dd0f4..fe3fe2a 100644
--- a/lib/lp/snappy/interfaces/snapbuild.py
+++ b/lib/lp/snappy/interfaces/snapbuild.py
@@ -4,20 +4,17 @@
"""Snap package build interfaces."""
__all__ = [
- 'CannotScheduleStoreUpload',
- 'ISnapBuild',
- 'ISnapBuildSet',
- 'ISnapBuildStatusChangedEvent',
- 'ISnapFile',
- 'SnapBuildStoreUploadStatus',
- ]
+ "CannotScheduleStoreUpload",
+ "ISnapBuild",
+ "ISnapBuildSet",
+ "ISnapBuildStatusChangedEvent",
+ "ISnapFile",
+ "SnapBuildStoreUploadStatus",
+]
import http.client
-from lazr.enum import (
- EnumeratedType,
- Item,
- )
+from lazr.enum import EnumeratedType, Item
from lazr.restful.declarations import (
error_status,
export_read_operation,
@@ -25,25 +22,11 @@ from lazr.restful.declarations import (
exported,
exported_as_webservice_entry,
operation_for_version,
- )
-from lazr.restful.fields import (
- CollectionField,
- Reference,
- )
-from zope.interface import (
- Attribute,
- Interface,
- )
+)
+from lazr.restful.fields import CollectionField, Reference
+from zope.interface import Attribute, Interface
from zope.interface.interfaces import IObjectEvent
-from zope.schema import (
- Bool,
- Choice,
- Datetime,
- Dict,
- Int,
- List,
- TextLine,
- )
+from zope.schema import Bool, Choice, Datetime, Dict, Int, List, TextLine
from lp import _
from lp.app.interfaces.launchpad import IPrivacy
@@ -51,19 +34,16 @@ from lp.buildmaster.interfaces.buildfarmjob import (
IBuildFarmJobAdmin,
IBuildFarmJobEdit,
ISpecificBuildFarmJobSource,
- )
+)
from lp.buildmaster.interfaces.packagebuild import (
IPackageBuild,
IPackageBuildView,
- )
+)
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.services.database.constants import DEFAULT
from lp.services.librarian.interfaces import ILibraryFileAlias
-from lp.snappy.interfaces.snap import (
- ISnap,
- ISnapBuildRequest,
- )
+from lp.snappy.interfaces.snap import ISnap, ISnapBuildRequest
from lp.snappy.interfaces.snapbase import ISnapBase
from lp.soyuz.interfaces.archive import IArchive
from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
@@ -85,11 +65,16 @@ class ISnapFile(Interface):
# Really ISnapBuild, patched in _schema_circular_imports.py.
Interface,
title=_("The snap package build producing this file."),
- required=True, readonly=True)
+ required=True,
+ readonly=True,
+ )
libraryfile = Reference(
- ILibraryFileAlias, title=_("The library file alias for this file."),
- required=True, readonly=True)
+ ILibraryFileAlias,
+ title=_("The library file alias for this file."),
+ required=True,
+ readonly=True,
+ )
class SnapBuildStoreUploadStatus(EnumeratedType):
@@ -99,39 +84,49 @@ class SnapBuildStoreUploadStatus(EnumeratedType):
that process.
"""
- UNSCHEDULED = Item("""
+ UNSCHEDULED = Item(
+ """
Unscheduled
No upload of this snap build to the store is scheduled.
- """)
+ """
+ )
- PENDING = Item("""
+ PENDING = Item(
+ """
Pending
This snap build is queued for upload to the store.
- """)
+ """
+ )
- FAILEDTOUPLOAD = Item("""
+ FAILEDTOUPLOAD = Item(
+ """
Failed to upload
The last attempt to upload this snap build to the store failed.
- """)
+ """
+ )
# This is an impossible state for new releases (2019-06-19), due
# to the store handling releases for us, however historical tasks
# can have this status, so it is maintained here.
- FAILEDTORELEASE = Item("""
+ FAILEDTORELEASE = Item(
+ """
Failed to release to channels
The last attempt to release this snap build to its intended set of
channels failed.
- """)
+ """
+ )
- UPLOADED = Item("""
+ UPLOADED = Item(
+ """
Uploaded
This snap build was successfully uploaded to the store.
- """)
+ """
+ )
class ISnapBuildView(IPackageBuildView, IPrivacy):
@@ -140,131 +135,210 @@ class ISnapBuildView(IPackageBuildView, IPrivacy):
build_request = Reference(
ISnapBuildRequest,
title=_("The build request that caused this build to be created."),
- required=False, readonly=True)
+ required=False,
+ readonly=True,
+ )
- requester = exported(Reference(
- IPerson,
- title=_("The person who requested this build."),
- required=True, readonly=True))
+ requester = exported(
+ Reference(
+ IPerson,
+ title=_("The person who requested this build."),
+ required=True,
+ readonly=True,
+ )
+ )
- snap = exported(Reference(
- ISnap,
- title=_("The snap package to build."),
- required=True, readonly=True))
+ snap = exported(
+ Reference(
+ ISnap,
+ title=_("The snap package to build."),
+ required=True,
+ readonly=True,
+ )
+ )
- archive = exported(Reference(
- IArchive,
- title=_("The archive from which to build the snap package."),
- required=True, readonly=True))
+ archive = exported(
+ Reference(
+ IArchive,
+ title=_("The archive from which to build the snap package."),
+ required=True,
+ readonly=True,
+ )
+ )
- distro_arch_series = exported(Reference(
- IDistroArchSeries,
- title=_("The series and architecture to build on."),
- required=True, readonly=True))
+ distro_arch_series = exported(
+ Reference(
+ IDistroArchSeries,
+ title=_("The series and architecture to build on."),
+ required=True,
+ readonly=True,
+ )
+ )
arch_tag = exported(
- TextLine(title=_("Architecture tag"), required=True, readonly=True))
+ TextLine(title=_("Architecture tag"), required=True, readonly=True)
+ )
target_architectures = exported(
List(
TextLine(),
title=_("The target architectures to build for."),
- required=False, readonly=True,
+ required=False,
+ readonly=True,
)
)
- pocket = exported(Choice(
- title=_("The pocket for which to build."),
- description=(
- "The package stream within the source archive and distribution "
- "series to use when building the snap package. If the source "
- "archive is a PPA, then the PPA's archive dependencies will be "
- "used to select the pocket in the distribution's primary "
- "archive."),
- vocabulary=PackagePublishingPocket, required=True, readonly=True))
-
- snap_base = exported(Reference(
- ISnapBase,
- title=_("The snap base to use for this build."),
- required=False, readonly=True))
-
- channels = exported(Dict(
- title=_("Source snap channels to use for this build."),
- description=_(
- "A dictionary mapping snap names to channels to use for this "
- "build. Currently only 'core', 'core18', 'core20', 'core22', "
- "and 'snapcraft' keys are supported."),
- key_type=TextLine()))
+ pocket = exported(
+ Choice(
+ title=_("The pocket for which to build."),
+ description=(
+ "The package stream within the source archive and "
+ "distribution series to use when building the snap package. "
+ "If the source archive is a PPA, then the PPA's archive "
+ "dependencies will be used to select the pocket in the "
+ "distribution's primary archive."
+ ),
+ vocabulary=PackagePublishingPocket,
+ required=True,
+ readonly=True,
+ )
+ )
+
+ snap_base = exported(
+ Reference(
+ ISnapBase,
+ title=_("The snap base to use for this build."),
+ required=False,
+ readonly=True,
+ )
+ )
+
+ channels = exported(
+ Dict(
+ title=_("Source snap channels to use for this build."),
+ description=_(
+ "A dictionary mapping snap names to channels to use for this "
+ "build. Currently only 'core', 'core18', 'core20', 'core22', "
+ "and 'snapcraft' keys are supported."
+ ),
+ key_type=TextLine(),
+ )
+ )
virtualized = Bool(
- title=_("If True, this build is virtualized."), readonly=True)
+ title=_("If True, this build is virtualized."), readonly=True
+ )
- score = exported(Int(
- title=_("Score of the related build farm job (if any)."),
- required=False, readonly=True))
+ score = exported(
+ Int(
+ title=_("Score of the related build farm job (if any)."),
+ required=False,
+ readonly=True,
+ )
+ )
eta = Datetime(
title=_("The datetime when the build job is estimated to complete."),
- readonly=True)
+ readonly=True,
+ )
estimate = Bool(
- title=_("If true, the date value is an estimate."), readonly=True)
+ title=_("If true, the date value is an estimate."), readonly=True
+ )
date = Datetime(
- title=_("The date when the build completed or is estimated to "
- "complete."), readonly=True)
+ title=_(
+ "The date when the build completed or is estimated to " "complete."
+ ),
+ readonly=True,
+ )
- revision_id = exported(TextLine(
- title=_("Revision ID"), required=False, readonly=True,
- description=_(
- "The revision ID of the branch used for this build, if "
- "available.")))
+ revision_id = exported(
+ TextLine(
+ title=_("Revision ID"),
+ required=False,
+ readonly=True,
+ description=_(
+ "The revision ID of the branch used for this build, if "
+ "available."
+ ),
+ )
+ )
store_upload_jobs = CollectionField(
title=_("Store upload jobs for this build."),
# Really ISnapStoreUploadJob.
value_type=Reference(schema=Interface),
- readonly=True)
+ readonly=True,
+ )
# Really ISnapStoreUploadJob.
last_store_upload_job = Reference(
- title=_("Last store upload job for this build."), schema=Interface)
-
- store_upload_status = exported(Choice(
- title=_("Store upload status"),
- vocabulary=SnapBuildStoreUploadStatus, required=True, readonly=False))
-
- store_upload_url = exported(TextLine(
- title=_("Store URL"),
- description=_(
- "The URL to use for managing this package in the store."),
- required=False, readonly=True))
-
- store_upload_revision = exported(Int(
- title=_("Store revision"),
- description=_("The revision assigned to this package by the store."),
- required=False, readonly=True))
-
- store_upload_error_message = exported(TextLine(
- title=_("Store upload error message"),
- description=_(
- "The error message, if any, from the last attempt to upload "
- "this snap build to the store. (Deprecated; use "
- "store_upload_error_messages instead.)"),
- required=False, readonly=True))
-
- store_upload_error_messages = exported(List(
- title=_("Store upload error messages"),
- description=_(
- "A list of dict(message, link) where message is an error "
- "description and link, if any, is an external link to extra "
- "details, from the last attempt to upload this snap build "
- "to the store."),
- value_type=Dict(key_type=TextLine()),
- required=False, readonly=True))
+ title=_("Last store upload job for this build."), schema=Interface
+ )
+
+ store_upload_status = exported(
+ Choice(
+ title=_("Store upload status"),
+ vocabulary=SnapBuildStoreUploadStatus,
+ required=True,
+ readonly=False,
+ )
+ )
+
+ store_upload_url = exported(
+ TextLine(
+ title=_("Store URL"),
+ description=_(
+ "The URL to use for managing this package in the store."
+ ),
+ required=False,
+ readonly=True,
+ )
+ )
+
+ store_upload_revision = exported(
+ Int(
+ title=_("Store revision"),
+ description=_(
+ "The revision assigned to this package by the store."
+ ),
+ required=False,
+ readonly=True,
+ )
+ )
+
+ store_upload_error_message = exported(
+ TextLine(
+ title=_("Store upload error message"),
+ description=_(
+ "The error message, if any, from the last attempt to upload "
+ "this snap build to the store. (Deprecated; use "
+ "store_upload_error_messages instead.)"
+ ),
+ required=False,
+ readonly=True,
+ )
+ )
+
+ store_upload_error_messages = exported(
+ List(
+ title=_("Store upload error messages"),
+ description=_(
+ "A list of dict(message, link) where message is an error "
+ "description and link, if any, is an external link to extra "
+ "details, from the last attempt to upload this snap build "
+ "to the store."
+ ),
+ value_type=Dict(key_type=TextLine()),
+ required=False,
+ readonly=True,
+ )
+ )
store_upload_metadata = Attribute(
- _("A dict of data about store upload progress."))
+ _("A dict of data about store upload progress.")
+ )
def getFiles():
"""Retrieve the build's `ISnapFile` records.
@@ -326,17 +400,27 @@ class ISnapBuildAdmin(IBuildFarmJobAdmin):
# "devel".
@exported_as_webservice_entry(as_of="beta")
class ISnapBuild(
- ISnapBuildView, ISnapBuildEdit, ISnapBuildAdmin, IPackageBuild):
+ ISnapBuildView, ISnapBuildEdit, ISnapBuildAdmin, IPackageBuild
+):
"""Build information for snap package builds."""
class ISnapBuildSet(ISpecificBuildFarmJobSource):
"""Utility for `ISnapBuild`."""
- def new(requester, snap, archive, distro_arch_series, pocket,
- snap_base=None, channels=None, date_created=DEFAULT,
- store_upload_metadata=None, build_request=None,
- target_architectures=None):
+ def new(
+ requester,
+ snap,
+ archive,
+ distro_arch_series,
+ pocket,
+ snap_base=None,
+ channels=None,
+ date_created=DEFAULT,
+ store_upload_metadata=None,
+ build_request=None,
+ target_architectures=None,
+ ):
"""Create an `ISnapBuild`."""
def preloadBuildsData(builds):
diff --git a/lib/lp/snappy/interfaces/snapbuildjob.py b/lib/lp/snappy/interfaces/snapbuildjob.py
index 456e061..12e4b8b 100644
--- a/lib/lp/snappy/interfaces/snapbuildjob.py
+++ b/lib/lp/snappy/interfaces/snapbuildjob.py
@@ -4,29 +4,19 @@
"""Snap build job interfaces."""
__all__ = [
- 'ISnapBuildJob',
- 'ISnapBuildStoreUploadStatusChangedEvent',
- 'ISnapStoreUploadJob',
- 'ISnapStoreUploadJobSource',
- ]
+ "ISnapBuildJob",
+ "ISnapBuildStoreUploadStatusChangedEvent",
+ "ISnapStoreUploadJob",
+ "ISnapStoreUploadJobSource",
+]
from lazr.restful.fields import Reference
-from zope.interface import (
- Attribute,
- Interface,
- )
+from zope.interface import Attribute, Interface
from zope.interface.interfaces import IObjectEvent
-from zope.schema import (
- Int,
- TextLine,
- )
+from zope.schema import Int, TextLine
from lp import _
-from lp.services.job.interfaces.job import (
- IJob,
- IJobSource,
- IRunnableJob,
- )
+from lp.services.job.interfaces.job import IJob, IJobSource, IRunnableJob
from lp.snappy.interfaces.snapbuild import ISnapBuild
@@ -34,12 +24,18 @@ class ISnapBuildJob(Interface):
"""A job related to a snap package."""
job = Reference(
- title=_("The common Job attributes."), schema=IJob,
- required=True, readonly=True)
+ title=_("The common Job attributes."),
+ schema=IJob,
+ required=True,
+ readonly=True,
+ )
snapbuild = Reference(
title=_("The snap build to use for this job."),
- schema=ISnapBuild, required=True, readonly=True)
+ schema=ISnapBuild,
+ required=True,
+ readonly=True,
+ )
metadata = Attribute(_("A dict of data about the job."))
@@ -52,35 +48,46 @@ class ISnapStoreUploadJob(IRunnableJob):
"""A Job that uploads a snap build to the store."""
store_metadata = Attribute(
- _("Combined metadata for this job and matching snapbuild"))
+ _("Combined metadata for this job and matching snapbuild")
+ )
error_message = TextLine(
- title=_("Error message"), required=False, readonly=True)
+ title=_("Error message"), required=False, readonly=True
+ )
error_detail = TextLine(
- title=_("Error message detail"), required=False, readonly=True)
+ title=_("Error message detail"), required=False, readonly=True
+ )
upload_id = Int(
title=_(
"The ID returned by the store when uploading this build's snap "
- "file."),
- required=False, readonly=True)
+ "file."
+ ),
+ required=False,
+ readonly=True,
+ )
status_url = TextLine(
title=_("The URL on the store to get the status of this build"),
- required=False, readonly=True)
+ required=False,
+ readonly=True,
+ )
store_url = TextLine(
title=_("The URL on the store corresponding to this build"),
- required=False, readonly=True)
+ required=False,
+ readonly=True,
+ )
store_revision = Int(
title=_("The revision assigned to this build by the store"),
- required=False, readonly=True)
+ required=False,
+ readonly=True,
+ )
class ISnapStoreUploadJobSource(IJobSource):
-
def create(snapbuild):
"""Upload a snap build to the store.
diff --git a/lib/lp/snappy/interfaces/snapjob.py b/lib/lp/snappy/interfaces/snapjob.py
index 9a5b1f2..6a185df 100644
--- a/lib/lp/snappy/interfaces/snapjob.py
+++ b/lib/lp/snappy/interfaces/snapjob.py
@@ -4,37 +4,20 @@
"""Snap job interfaces."""
__all__ = [
- 'ISnapJob',
- 'ISnapRequestBuildsJob',
- 'ISnapRequestBuildsJobSource',
- ]
+ "ISnapJob",
+ "ISnapRequestBuildsJob",
+ "ISnapRequestBuildsJobSource",
+]
from lazr.restful.fields import Reference
-from zope.interface import (
- Attribute,
- Interface,
- )
-from zope.schema import (
- Choice,
- Datetime,
- Dict,
- List,
- Set,
- TextLine,
- )
+from zope.interface import Attribute, Interface
+from zope.schema import Choice, Datetime, Dict, List, Set, TextLine
from lp import _
from lp.registry.interfaces.person import IPerson
from lp.registry.interfaces.pocket import PackagePublishingPocket
-from lp.services.job.interfaces.job import (
- IJob,
- IJobSource,
- IRunnableJob,
- )
-from lp.snappy.interfaces.snap import (
- ISnap,
- ISnapBuildRequest,
- )
+from lp.services.job.interfaces.job import IJob, IJobSource, IRunnableJob
+from lp.snappy.interfaces.snap import ISnap, ISnapBuildRequest
from lp.snappy.interfaces.snapbuild import ISnapBuild
from lp.soyuz.interfaces.archive import IArchive
@@ -43,12 +26,18 @@ class ISnapJob(Interface):
"""A job related to a snap package."""
job = Reference(
- title=_("The common Job attributes."), schema=IJob,
- required=True, readonly=True)
+ title=_("The common Job attributes."),
+ schema=IJob,
+ required=True,
+ readonly=True,
+ )
snap = Reference(
title=_("The snap package to use for this job."),
- schema=ISnap, required=True, readonly=True)
+ schema=ISnap,
+ required=True,
+ readonly=True,
+ )
metadata = Attribute(_("A dict of data about the job."))
@@ -57,52 +46,77 @@ class ISnapRequestBuildsJob(IRunnableJob):
"""A Job that processes a request for builds of a snap package."""
requester = Reference(
- title=_("The person requesting the builds."), schema=IPerson,
- required=True, readonly=True)
+ title=_("The person requesting the builds."),
+ schema=IPerson,
+ required=True,
+ readonly=True,
+ )
archive = Reference(
- title=_("The archive to associate the builds with."), schema=IArchive,
- required=True, readonly=True)
+ title=_("The archive to associate the builds with."),
+ schema=IArchive,
+ required=True,
+ readonly=True,
+ )
pocket = Choice(
title=_("The pocket that should be targeted."),
- vocabulary=PackagePublishingPocket, required=True, readonly=True)
+ vocabulary=PackagePublishingPocket,
+ required=True,
+ readonly=True,
+ )
channels = Dict(
title=_("Source snap channels to use for these builds."),
description=_(
"A dictionary mapping snap names to channels to use for these "
"builds. Currently only 'core', 'core18', 'core20', 'core22', "
- "and 'snapcraft' keys are supported."),
- key_type=TextLine(), required=False, readonly=True)
+ "and 'snapcraft' keys are supported."
+ ),
+ key_type=TextLine(),
+ required=False,
+ readonly=True,
+ )
architectures = Set(
title=_("If set, limit builds to these architecture tags."),
- value_type=TextLine(), required=False, readonly=True)
+ value_type=TextLine(),
+ required=False,
+ readonly=True,
+ )
date_created = Datetime(
title=_("Time when this job was created."),
- required=True, readonly=True)
+ required=True,
+ readonly=True,
+ )
date_finished = Datetime(
- title=_("Time when this job finished."),
- required=True, readonly=True)
+ title=_("Time when this job finished."), required=True, readonly=True
+ )
error_message = TextLine(
title=_("Error message resulting from running this job."),
- required=False, readonly=True)
+ required=False,
+ readonly=True,
+ )
build_request = Reference(
title=_("The build request corresponding to this job."),
- schema=ISnapBuildRequest, required=True, readonly=True)
+ schema=ISnapBuildRequest,
+ required=True,
+ readonly=True,
+ )
builds = List(
title=_("The builds created by this request."),
- value_type=Reference(schema=ISnapBuild), required=True, readonly=True)
+ value_type=Reference(schema=ISnapBuild),
+ required=True,
+ readonly=True,
+ )
class ISnapRequestBuildsJobSource(IJobSource):
-
def create(snap, requester, archive, pocket, channels, architectures=None):
"""Request builds of a snap package.
diff --git a/lib/lp/snappy/interfaces/snappyseries.py b/lib/lp/snappy/interfaces/snappyseries.py
index 57af216..f53dc2f 100644
--- a/lib/lp/snappy/interfaces/snappyseries.py
+++ b/lib/lp/snappy/interfaces/snappyseries.py
@@ -4,14 +4,15 @@
"""Snappy series interfaces."""
__all__ = [
- 'ISnappyDistroSeries',
- 'ISnappyDistroSeriesSet',
- 'ISnappySeries',
- 'ISnappySeriesSet',
- 'NoSuchSnappySeries',
- ]
+ "ISnappyDistroSeries",
+ "ISnappyDistroSeriesSet",
+ "ISnappySeries",
+ "ISnappySeriesSet",
+ "NoSuchSnappySeries",
+]
from lazr.restful.declarations import (
+ REQUEST_USER,
call_with,
collection_default_content,
export_factory_operation,
@@ -22,30 +23,18 @@ from lazr.restful.declarations import (
operation_for_version,
operation_parameters,
operation_returns_entry,
- REQUEST_USER,
- )
+)
from lazr.restful.fields import Reference
from zope.component import getUtility
from zope.interface import Interface
-from zope.schema import (
- Bool,
- Choice,
- Datetime,
- Int,
- List,
- TextLine,
- )
+from zope.schema import Bool, Choice, Datetime, Int, List, TextLine
from lp import _
from lp.app.errors import NameLookupFailed
from lp.app.validators.name import name_validator
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.series import SeriesStatus
-from lp.services.fields import (
- ContentNameField,
- PublicPersonChoice,
- Title,
- )
+from lp.services.fields import ContentNameField, PublicPersonChoice, Title
class NoSuchSnappySeries(NameLookupFailed):
@@ -77,13 +66,19 @@ class ISnappySeriesView(Interface):
id = Int(title=_("ID"), required=True, readonly=True)
- date_created = exported(Datetime(
- title=_("Date created"), required=True, readonly=True))
+ date_created = exported(
+ Datetime(title=_("Date created"), required=True, readonly=True)
+ )
- registrant = exported(PublicPersonChoice(
- title=_("Registrant"), required=True, readonly=True,
- vocabulary="ValidPersonOrTeam",
- description=_("The person who registered this snappy series.")))
+ registrant = exported(
+ PublicPersonChoice(
+ title=_("Registrant"),
+ required=True,
+ readonly=True,
+ vocabulary="ValidPersonOrTeam",
+ description=_("The person who registered this snappy series."),
+ )
+ )
class ISnappySeriesEditableAttributes(Interface):
@@ -92,34 +87,57 @@ class ISnappySeriesEditableAttributes(Interface):
Anyone can view these attributes, but they need launchpad.Edit to change.
"""
- name = exported(SnappySeriesNameField(
- title=_("Name"), required=True, readonly=False,
- constraint=name_validator))
+ name = exported(
+ SnappySeriesNameField(
+ title=_("Name"),
+ required=True,
+ readonly=False,
+ constraint=name_validator,
+ )
+ )
- display_name = exported(TextLine(
- title=_("Display name"), required=True, readonly=False))
+ display_name = exported(
+ TextLine(title=_("Display name"), required=True, readonly=False)
+ )
title = Title(title=_("Title"), required=True, readonly=True)
- status = exported(Choice(
- title=_("Status"), required=True, vocabulary=SeriesStatus))
+ status = exported(
+ Choice(title=_("Status"), required=True, vocabulary=SeriesStatus)
+ )
- preferred_distro_series = exported(Reference(
- IDistroSeries, title=_("Preferred distro series"),
- required=False, readonly=False))
+ preferred_distro_series = exported(
+ Reference(
+ IDistroSeries,
+ title=_("Preferred distro series"),
+ required=False,
+ readonly=False,
+ )
+ )
- usable_distro_series = exported(List(
- title=_("Usable distro series"),
- description=_(
- "The distro series that can be used for this snappy series."),
- value_type=Reference(schema=IDistroSeries),
- required=True, readonly=False))
+ usable_distro_series = exported(
+ List(
+ title=_("Usable distro series"),
+ description=_(
+ "The distro series that can be used for this snappy series."
+ ),
+ value_type=Reference(schema=IDistroSeries),
+ required=True,
+ readonly=False,
+ )
+ )
- can_infer_distro_series = exported(Bool(
- title=_("Can infer distro series?"), required=True, readonly=False,
- description=_(
- "True if inferring a distro series from snapcraft.yaml is "
- "supported for this snappy series.")))
+ can_infer_distro_series = exported(
+ Bool(
+ title=_("Can infer distro series?"),
+ required=True,
+ readonly=False,
+ description=_(
+ "True if inferring a distro series from snapcraft.yaml is "
+ "supported for this snappy series."
+ ),
+ )
+ )
# XXX cjwatson 2016-04-13 bug=760849: "beta" is a lie to get WADL
@@ -134,15 +152,20 @@ class ISnappyDistroSeries(Interface):
"""A snappy/distro series link."""
snappy_series = Reference(
- ISnappySeries, title=_("Snappy series"), readonly=True)
+ ISnappySeries, title=_("Snappy series"), readonly=True
+ )
distro_series = Reference(
- IDistroSeries, title=_("Distro series"), required=False, readonly=True)
+ IDistroSeries, title=_("Distro series"), required=False, readonly=True
+ )
preferred = Bool(
title=_("Preferred"),
- required=True, readonly=False,
+ required=True,
+ readonly=False,
description=_(
"True if this identifies the default distro series for builds "
- "for this snappy series."))
+ "for this snappy series."
+ ),
+ )
title = Title(title=_("Title"), required=True, readonly=True)
@@ -152,7 +175,8 @@ class ISnappySeriesSetEdit(Interface):
@call_with(registrant=REQUEST_USER)
@export_factory_operation(
- ISnappySeries, ["name", "display_name", "status"])
+ ISnappySeries, ["name", "display_name", "status"]
+ )
@operation_for_version("devel")
def new(registrant, name, display_name, status, date_created=None):
"""Create an `ISnappySeries`."""
@@ -169,7 +193,8 @@ class ISnappySeriesSet(ISnappySeriesSetEdit):
"""Return the `ISnappySeries` with this name."""
@operation_parameters(
- name=TextLine(title=_("Snappy series name"), required=True))
+ name=TextLine(title=_("Snappy series name"), required=True)
+ )
@operation_returns_entry(ISnappySeries)
@export_read_operation()
@operation_for_version("devel")
diff --git a/lib/lp/snappy/interfaces/snapstoreclient.py b/lib/lp/snappy/interfaces/snapstoreclient.py
index f809b99..e45e8a9 100644
--- a/lib/lp/snappy/interfaces/snapstoreclient.py
+++ b/lib/lp/snappy/interfaces/snapstoreclient.py
@@ -4,17 +4,17 @@
"""Interface for communication with the snap store."""
__all__ = [
- 'BadRefreshResponse',
- 'BadRequestPackageUploadResponse',
- 'BadScanStatusResponse',
- 'ISnapStoreClient',
- 'NeedsRefreshResponse',
- 'ScanFailedResponse',
- 'SnapStoreError',
- 'UnauthorizedUploadResponse',
- 'UploadFailedResponse',
- 'UploadNotScannedYetResponse',
- ]
+ "BadRefreshResponse",
+ "BadRequestPackageUploadResponse",
+ "BadScanStatusResponse",
+ "ISnapStoreClient",
+ "NeedsRefreshResponse",
+ "ScanFailedResponse",
+ "SnapStoreError",
+ "UnauthorizedUploadResponse",
+ "UploadFailedResponse",
+ "UploadNotScannedYetResponse",
+]
import http.client
@@ -23,9 +23,9 @@ from zope.interface import Interface
class SnapStoreError(Exception):
-
def __init__(
- self, message="", detail=None, messages=None, can_retry=False):
+ self, message="", detail=None, messages=None, can_retry=False
+ ):
super().__init__(message)
self.message = message
self.detail = detail
diff --git a/lib/lp/snappy/interfaces/snapsubscription.py b/lib/lp/snappy/interfaces/snapsubscription.py
index 184aa74..81491be 100644
--- a/lib/lp/snappy/interfaces/snapsubscription.py
+++ b/lib/lp/snappy/interfaces/snapsubscription.py
@@ -3,16 +3,11 @@
"""Snap subscription model."""
-__all__ = [
- 'ISnapSubscription'
-]
+__all__ = ["ISnapSubscription"]
from lazr.restful.fields import Reference
from zope.interface import Interface
-from zope.schema import (
- Datetime,
- Int,
- )
+from zope.schema import Datetime, Int
from lp import _
from lp.services.fields import PersonChoice
@@ -22,18 +17,25 @@ from lp.snappy.interfaces.snap import ISnap
class ISnapSubscription(Interface):
"""A person subscription to a specific Snap recipe."""
- id = Int(title=_('ID'), readonly=True, required=True)
+ id = Int(title=_("ID"), readonly=True, required=True)
person = PersonChoice(
- title=_('Person'), required=True, vocabulary='ValidPersonOrTeam',
+ title=_("Person"),
+ required=True,
+ vocabulary="ValidPersonOrTeam",
readonly=True,
- description=_("The person subscribed to the related snap recipe."))
+ description=_("The person subscribed to the related snap recipe."),
+ )
snap = Reference(ISnap, title=_("Snap"), required=True, readonly=True)
subscribed_by = PersonChoice(
- title=_('Subscribed by'), required=True,
- vocabulary='ValidPersonOrTeam', readonly=True,
- description=_("The person who created this subscription."))
+ title=_("Subscribed by"),
+ required=True,
+ vocabulary="ValidPersonOrTeam",
+ readonly=True,
+ description=_("The person who created this subscription."),
+ )
date_created = Datetime(
- title=_('Date subscribed'), required=True, readonly=True)
+ title=_("Date subscribed"), required=True, readonly=True
+ )
def canBeUnsubscribedByUser(user):
"""Can the user unsubscribe the subscriber from the snap recipe?"""
diff --git a/lib/lp/snappy/interfaces/webservice.py b/lib/lp/snappy/interfaces/webservice.py
index d96695c..0b34a82 100644
--- a/lib/lp/snappy/interfaces/webservice.py
+++ b/lib/lp/snappy/interfaces/webservice.py
@@ -10,56 +10,46 @@ which tells `lazr.restful` that it should look for webservice exports here.
"""
__all__ = [
- 'ISnap',
- 'ISnapBase',
- 'ISnapBaseSet',
- 'ISnapBuild',
- 'ISnapBuildRequest',
- 'ISnappySeries',
- 'ISnappySeriesSet',
- 'ISnapSet',
- ]
+ "ISnap",
+ "ISnapBase",
+ "ISnapBaseSet",
+ "ISnapBuild",
+ "ISnapBuildRequest",
+ "ISnappySeries",
+ "ISnappySeriesSet",
+ "ISnapSet",
+]
from lp.services.webservice.apihelpers import (
patch_collection_property,
patch_collection_return_type,
patch_entry_return_type,
patch_reference_property,
- )
+)
from lp.snappy.interfaces.snap import (
ISnap,
ISnapBuildRequest,
ISnapEdit,
ISnapSet,
ISnapView,
- )
-from lp.snappy.interfaces.snapbase import (
- ISnapBase,
- ISnapBaseSet,
- )
-from lp.snappy.interfaces.snapbuild import (
- ISnapBuild,
- ISnapFile,
- )
-from lp.snappy.interfaces.snappyseries import (
- ISnappySeries,
- ISnappySeriesSet,
- )
-
+)
+from lp.snappy.interfaces.snapbase import ISnapBase, ISnapBaseSet
+from lp.snappy.interfaces.snapbuild import ISnapBuild, ISnapFile
+from lp.snappy.interfaces.snappyseries import ISnappySeries, ISnappySeriesSet
# ISnapFile
-patch_reference_property(ISnapFile, 'snapbuild', ISnapBuild)
+patch_reference_property(ISnapFile, "snapbuild", ISnapBuild)
# ISnapBuildRequest
-patch_reference_property(ISnapBuildRequest, 'snap', ISnap)
-patch_collection_property(ISnapBuildRequest, 'builds', ISnapBuild)
+patch_reference_property(ISnapBuildRequest, "snap", ISnap)
+patch_collection_property(ISnapBuildRequest, "builds", ISnapBuild)
# ISnapView
-patch_entry_return_type(ISnapView, 'requestBuild', ISnapBuild)
-patch_collection_property(ISnapView, 'builds', ISnapBuild)
-patch_collection_property(ISnapView, 'completed_builds', ISnapBuild)
-patch_collection_property(ISnapView, 'pending_builds', ISnapBuild)
-patch_entry_return_type(ISnapView, 'getBuildByStoreRevision', ISnapBuild)
+patch_entry_return_type(ISnapView, "requestBuild", ISnapBuild)
+patch_collection_property(ISnapView, "builds", ISnapBuild)
+patch_collection_property(ISnapView, "completed_builds", ISnapBuild)
+patch_collection_property(ISnapView, "pending_builds", ISnapBuild)
+patch_entry_return_type(ISnapView, "getBuildByStoreRevision", ISnapBuild)
# ISnapEdit
-patch_collection_return_type(ISnapEdit, 'requestAutoBuilds', ISnapBuild)
+patch_collection_return_type(ISnapEdit, "requestAutoBuilds", ISnapBuild)
diff --git a/lib/lp/snappy/mail/snapbuild.py b/lib/lp/snappy/mail/snapbuild.py
index aee1685..ca70774 100644
--- a/lib/lp/snappy/mail/snapbuild.py
+++ b/lib/lp/snappy/mail/snapbuild.py
@@ -2,21 +2,18 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'SnapBuildMailer',
- ]
+ "SnapBuildMailer",
+]
from lp.app.browser.tales import DurationFormatterAPI
from lp.services.config import config
-from lp.services.mail.basemailer import (
- BaseMailer,
- RecipientReason,
- )
+from lp.services.mail.basemailer import BaseMailer, RecipientReason
from lp.services.webapp import canonical_url
class SnapBuildMailer(BaseMailer):
- app = 'snappy'
+ app = "snappy"
@classmethod
def forStatus(cls, build):
@@ -28,8 +25,12 @@ class SnapBuildMailer(BaseMailer):
recipients = {requester: RecipientReason.forBuildRequester(requester)}
return cls(
"[Snap build #%(build_id)d] %(build_title)s",
- "snapbuild-notification.txt", recipients,
- config.canonical.noreply_from_address, "snap-build-status", build)
+ "snapbuild-notification.txt",
+ recipients,
+ config.canonical.noreply_from_address,
+ "snap-build-status",
+ build,
+ )
@classmethod
def forUnauthorizedUpload(cls, build):
@@ -41,9 +42,12 @@ class SnapBuildMailer(BaseMailer):
recipients = {requester: RecipientReason.forBuildRequester(requester)}
return cls(
"Store authorization failed for %(snap_name)s",
- "snapbuild-unauthorized.txt", recipients,
+ "snapbuild-unauthorized.txt",
+ recipients,
config.canonical.noreply_from_address,
- "snap-build-upload-unauthorized", build)
+ "snap-build-upload-unauthorized",
+ build,
+ )
@classmethod
def forRefreshFailure(cls, build):
@@ -55,9 +59,12 @@ class SnapBuildMailer(BaseMailer):
recipients = {requester: RecipientReason.forBuildRequester(requester)}
return cls(
"Refreshing store authorization failed for %(snap_name)s",
- "snapbuild-refreshfailed.txt", recipients,
+ "snapbuild-refreshfailed.txt",
+ recipients,
config.canonical.noreply_from_address,
- "snap-build-upload-refresh-failed", build)
+ "snap-build-upload-refresh-failed",
+ build,
+ )
@classmethod
def forUploadFailure(cls, build):
@@ -69,9 +76,12 @@ class SnapBuildMailer(BaseMailer):
recipients = {requester: RecipientReason.forBuildRequester(requester)}
return cls(
"Store upload failed for %(snap_name)s",
- "snapbuild-uploadfailed.txt", recipients,
+ "snapbuild-uploadfailed.txt",
+ recipients,
config.canonical.noreply_from_address,
- "snap-build-upload-failed", build)
+ "snap-build-upload-failed",
+ build,
+ )
@classmethod
def forUploadScanFailure(cls, build):
@@ -83,15 +93,29 @@ class SnapBuildMailer(BaseMailer):
recipients = {requester: RecipientReason.forBuildRequester(requester)}
return cls(
"Store upload scan failed for %(snap_name)s",
- "snapbuild-scanfailed.txt", recipients,
+ "snapbuild-scanfailed.txt",
+ recipients,
config.canonical.noreply_from_address,
- "snap-build-upload-scan-failed", build)
-
- def __init__(self, subject, template_name, recipients, from_address,
- notification_type, build):
+ "snap-build-upload-scan-failed",
+ build,
+ )
+
+ def __init__(
+ self,
+ subject,
+ template_name,
+ recipients,
+ from_address,
+ notification_type,
+ build,
+ ):
super().__init__(
- subject, template_name, recipients, from_address,
- notification_type=notification_type)
+ subject,
+ template_name,
+ recipients,
+ from_address,
+ notification_type=notification_type,
+ )
self.build = build
def _getHeaders(self, email, recipient):
@@ -111,25 +135,28 @@ class SnapBuildMailer(BaseMailer):
error_message = upload_job.error_message or ""
store_url = upload_job.store_url or ""
params = super()._getTemplateParams(email, recipient)
- params.update({
- "archive_tag": build.archive.reference,
- "build_id": build.id,
- "build_title": build.title,
- "snap_name": build.snap.name,
- "distroseries": build.snap.distro_series,
- "architecturetag": build.distro_arch_series.architecturetag,
- "pocket": build.pocket.name,
- "build_state": build.status.title,
- "build_duration": "",
- "log_url": "",
- "upload_log_url": "",
- "builder_url": "",
- "build_url": canonical_url(build),
- "snap_authorize_url": canonical_url(
- build.snap, view_name="+authorize"),
- "store_error_message": error_message,
- "store_url": store_url,
- })
+ params.update(
+ {
+ "archive_tag": build.archive.reference,
+ "build_id": build.id,
+ "build_title": build.title,
+ "snap_name": build.snap.name,
+ "distroseries": build.snap.distro_series,
+ "architecturetag": build.distro_arch_series.architecturetag,
+ "pocket": build.pocket.name,
+ "build_state": build.status.title,
+ "build_duration": "",
+ "log_url": "",
+ "upload_log_url": "",
+ "builder_url": "",
+ "build_url": canonical_url(build),
+ "snap_authorize_url": canonical_url(
+ build.snap, view_name="+authorize"
+ ),
+ "store_error_message": error_message,
+ "store_url": store_url,
+ }
+ )
if build.duration is not None:
duration_formatter = DurationFormatterAPI(build.duration)
params["build_duration"] = duration_formatter.approximateduration()
@@ -143,5 +170,4 @@ class SnapBuildMailer(BaseMailer):
def _getFooter(self, email, recipient, params):
"""See `BaseMailer`."""
- return ("%(build_url)s\n"
- "%(reason)s\n" % params)
+ return "%(build_url)s\n" "%(reason)s\n" % params
diff --git a/lib/lp/snappy/model/snap.py b/lib/lp/snappy/model/snap.py
index bfc5795..28e1d28 100644
--- a/lib/lp/snappy/model/snap.py
+++ b/lib/lp/snappy/model/snap.py
@@ -2,26 +2,25 @@
# GNU Affero General Public License version 3 (see the file LICENSE).
__all__ = [
- 'get_snap_privacy_filter',
- 'Snap',
- ]
+ "get_snap_privacy_filter",
+ "Snap",
+]
import base64
+import typing as t
from collections import OrderedDict
-from datetime import (
- datetime,
- timedelta,
- )
+from datetime import datetime, timedelta
from operator import attrgetter
-import typing as t
from urllib.parse import urlsplit
+import pytz
+import six
+import yaml
from breezy import urlutils
from lazr.lifecycle.event import ObjectCreatedEvent
from pymacaroons import Macaroon
-import pytz
-import six
from storm.expr import (
+ SQL,
And,
Coalesce,
Desc,
@@ -31,46 +30,35 @@ from storm.expr import (
Not,
Or,
Select,
- SQL,
- )
+)
from storm.locals import (
+ JSON,
Bool,
DateTime,
Int,
- JSON,
Reference,
Store,
Storm,
Unicode,
- )
-import yaml
-from zope.component import (
- getAdapter,
- getUtility,
- )
+)
+from zope.component import getAdapter, getUtility
from zope.event import notify
-from zope.interface import (
- directlyProvides,
- implementer,
- )
+from zope.interface import directlyProvides, implementer
from zope.security.interfaces import Unauthorized
from zope.security.proxy import removeSecurityProxy
-from lp.app.browser.tales import (
- ArchiveFormatterAPI,
- DateTimeFormatterAPI,
- )
+from lp.app.browser.tales import ArchiveFormatterAPI, DateTimeFormatterAPI
from lp.app.enums import (
FREE_INFORMATION_TYPES,
- InformationType,
PRIVATE_INFORMATION_TYPES,
PUBLIC_INFORMATION_TYPES,
- )
+ InformationType,
+)
from lp.app.errors import (
IncompatibleArguments,
SubscriptionPrivacyViolation,
UserCannotUnsubscribePerson,
- )
+)
from lp.app.interfaces.launchpad import ILaunchpadCelebrities
from lp.app.interfaces.security import IAuthorization
from lp.app.interfaces.services import IService
@@ -86,53 +74,44 @@ from lp.code.errors import (
GitRepositoryBlobNotFound,
GitRepositoryBlobUnsupportedRemote,
GitRepositoryScanFault,
- )
+)
from lp.code.interfaces.branch import IBranch
-from lp.code.interfaces.branchcollection import (
- IAllBranches,
- IBranchCollection,
- )
+from lp.code.interfaces.branchcollection import IAllBranches, IBranchCollection
from lp.code.interfaces.gitcollection import (
IAllGitRepositories,
IGitCollection,
- )
-from lp.code.interfaces.gitref import (
- IGitRef,
- IGitRefRemoteSet,
- )
+)
+from lp.code.interfaces.gitref import IGitRef, IGitRefRemoteSet
from lp.code.interfaces.gitrepository import (
IGitRepository,
IHasGitRepositoryURL,
- )
+)
from lp.code.model.branch import Branch
from lp.code.model.branchcollection import GenericBranchCollection
from lp.code.model.branchnamespace import (
BRANCH_POLICY_ALLOWED_TYPES,
BRANCH_POLICY_REQUIRED_GRANTS,
- )
+)
from lp.code.model.gitcollection import GenericGitCollection
from lp.code.model.gitrepository import GitRepository
from lp.registry.errors import PrivatePersonLinkageError
from lp.registry.interfaces.accesspolicy import (
IAccessArtifactGrantSource,
IAccessArtifactSource,
- )
+)
from lp.registry.interfaces.distroseries import IDistroSeries
from lp.registry.interfaces.person import (
IPerson,
IPersonSet,
validate_public_person,
- )
+)
from lp.registry.interfaces.pocket import PackagePublishingPocket
from lp.registry.interfaces.product import IProduct
-from lp.registry.interfaces.role import (
- IHasOwner,
- IPersonRoles,
- )
+from lp.registry.interfaces.role import IHasOwner, IPersonRoles
from lp.registry.model.accesspolicy import (
AccessPolicyGrant,
reconcile_access_for_artifacts,
- )
+)
from lp.registry.model.distroseries import DistroSeries
from lp.registry.model.person import Person
from lp.registry.model.series import ACTIVE_STATUSES
@@ -141,16 +120,10 @@ from lp.services.config import config
from lp.services.crypto.interfaces import IEncryptedContainer
from lp.services.crypto.model import NaClEncryptedContainerBase
from lp.services.database.bulk import load_related
-from lp.services.database.constants import (
- DEFAULT,
- UTC_NOW,
- )
+from lp.services.database.constants import DEFAULT, UTC_NOW
from lp.services.database.decoratedresultset import DecoratedResultSet
from lp.services.database.enumcol import DBEnum
-from lp.services.database.interfaces import (
- IMasterStore,
- IStore,
- )
+from lp.services.database.interfaces import IMasterStore, IStore
from lp.services.database.stormexpr import (
Array,
ArrayAgg,
@@ -158,19 +131,13 @@ from lp.services.database.stormexpr import (
Greatest,
IsDistinctFrom,
NullsLast,
- )
+)
from lp.services.features import getFeatureFlag
from lp.services.job.interfaces.job import JobStatus
from lp.services.job.model.job import Job
-from lp.services.librarian.model import (
- LibraryFileAlias,
- LibraryFileContent,
- )
+from lp.services.librarian.model import LibraryFileAlias, LibraryFileContent
from lp.services.openid.adapters.openid import CurrentOpenIDEndPoint
-from lp.services.propertycache import (
- cachedproperty,
- get_property_cache,
- )
+from lp.services.propertycache import cachedproperty, get_property_cache
from lp.services.webapp.authorization import precache_permission_for_objects
from lp.services.webapp.interfaces import ILaunchBag
from lp.services.webapp.publisher import canonical_url
@@ -178,6 +145,7 @@ from lp.services.webhooks.interfaces import IWebhookSet
from lp.services.webhooks.model import WebhookTargetMixin
from lp.snappy.adapters.buildarch import determine_architectures_to_build
from lp.snappy.interfaces.snap import (
+ SNAP_PRIVATE_FEATURE_FLAG,
BadMacaroon,
BadSnapSearchContext,
BadSnapSource,
@@ -193,7 +161,6 @@ from lp.snappy.interfaces.snap import (
MissingSnapcraftYaml,
NoSourceForSnap,
NoSuchSnap,
- SNAP_PRIVATE_FEATURE_FLAG,
SnapAuthorizationBadGeneratedMacaroon,
SnapBuildAlreadyPending,
SnapBuildArchiveOwnerMismatch,
@@ -202,15 +169,9 @@ from lp.snappy.interfaces.snap import (
SnapNotOwner,
SnapPrivacyMismatch,
SnapPrivateFeatureDisabled,
- )
-from lp.snappy.interfaces.snapbase import (
- ISnapBaseSet,
- NoSuchSnapBase,
- )
-from lp.snappy.interfaces.snapbuild import (
- ISnapBuild,
- ISnapBuildSet,
- )
+)
+from lp.snappy.interfaces.snapbase import ISnapBaseSet, NoSuchSnapBase
+from lp.snappy.interfaces.snapbuild import ISnapBuild, ISnapBuildSet
from lp.snappy.interfaces.snapjob import ISnapRequestBuildsJobSource
from lp.snappy.interfaces.snappyseries import ISnappyDistroSeriesSet
from lp.snappy.interfaces.snapstoreclient import ISnapStoreClient
@@ -219,10 +180,7 @@ from lp.snappy.model.snapjob import SnapJob
from lp.snappy.model.snapsubscription import SnapSubscription
from lp.soyuz.interfaces.archive import ArchiveDisabled
from lp.soyuz.interfaces.distroarchseries import IDistroArchSeries
-from lp.soyuz.model.archive import (
- Archive,
- get_enabled_archive_filter,
- )
+from lp.soyuz.model.archive import Archive, get_enabled_archive_filter
from lp.soyuz.model.distroarchseries import DistroArchSeries
@@ -289,7 +247,7 @@ class SnapBuildRequest:
JobStatus.COMPLETED: SnapBuildRequestStatus.COMPLETED,
JobStatus.FAILED: SnapBuildRequestStatus.FAILED,
JobStatus.SUSPENDED: SnapBuildRequestStatus.PENDING,
- }
+ }
return status_map[self._job.job.status]
@property
@@ -332,17 +290,19 @@ class SnapBuildRequest:
class Snap(Storm, WebhookTargetMixin):
"""See `ISnap`."""
- __storm_table__ = 'Snap'
+ __storm_table__ = "Snap"
id = Int(primary=True)
date_created = DateTime(
- name='date_created', tzinfo=pytz.UTC, allow_none=False)
+ name="date_created", tzinfo=pytz.UTC, allow_none=False
+ )
date_last_modified = DateTime(
- name='date_last_modified', tzinfo=pytz.UTC, allow_none=False)
+ name="date_last_modified", tzinfo=pytz.UTC, allow_none=False
+ )
- registrant_id = Int(name='registrant', allow_none=False)
- registrant = Reference(registrant_id, 'Person.id')
+ registrant_id = Int(name="registrant", allow_none=False)
+ registrant = Reference(registrant_id, "Person.id")
def _validate_owner(self, attr, value):
if not self.private:
@@ -350,97 +310,122 @@ class Snap(Storm, WebhookTargetMixin):
validate_public_person(self, attr, value)
except PrivatePersonLinkageError:
raise SnapPrivacyMismatch(
- "A public snap cannot have a private owner.")
+ "A public snap cannot have a private owner."
+ )
return value
- owner_id = Int(name='owner', allow_none=False, validator=_validate_owner)
- owner = Reference(owner_id, 'Person.id')
+ owner_id = Int(name="owner", allow_none=False, validator=_validate_owner)
+ owner = Reference(owner_id, "Person.id")
- project_id = Int(name='project', allow_none=True)
- project = Reference(project_id, 'Product.id')
+ project_id = Int(name="project", allow_none=True)
+ project = Reference(project_id, "Product.id")
- distro_series_id = Int(name='distro_series', allow_none=True)
- distro_series = Reference(distro_series_id, 'DistroSeries.id')
+ distro_series_id = Int(name="distro_series", allow_none=True)
+ distro_series = Reference(distro_series_id, "DistroSeries.id")
- name = Unicode(name='name', allow_none=False)
+ name = Unicode(name="name", allow_none=False)
- description = Unicode(name='description', allow_none=True)
+ description = Unicode(name="description", allow_none=True)
def _validate_branch(self, attr, value):
if not self.private and value is not None:
if IStore(Branch).get(Branch, value).private:
raise SnapPrivacyMismatch(
- "A public snap cannot have a private branch.")
+ "A public snap cannot have a private branch."
+ )
return value
- branch_id = Int(name='branch', allow_none=True, validator=_validate_branch)
- branch = Reference(branch_id, 'Branch.id')
+ branch_id = Int(name="branch", allow_none=True, validator=_validate_branch)
+ branch = Reference(branch_id, "Branch.id")
def _validate_git_repository(self, attr, value):
if not self.private and value is not None:
if IStore(GitRepository).get(GitRepository, value).private:
raise SnapPrivacyMismatch(
- "A public snap cannot have a private repository.")
+ "A public snap cannot have a private repository."
+ )
return value
git_repository_id = Int(
- name='git_repository', allow_none=True,
- validator=_validate_git_repository)
- git_repository = Reference(git_repository_id, 'GitRepository.id')
+ name="git_repository",
+ allow_none=True,
+ validator=_validate_git_repository,
+ )
+ git_repository = Reference(git_repository_id, "GitRepository.id")
- git_repository_url = Unicode(name='git_repository_url', allow_none=True)
+ git_repository_url = Unicode(name="git_repository_url", allow_none=True)
- git_path = Unicode(name='git_path', allow_none=True)
+ git_path = Unicode(name="git_path", allow_none=True)
- auto_build = Bool(name='auto_build', allow_none=False)
+ auto_build = Bool(name="auto_build", allow_none=False)
- auto_build_archive_id = Int(name='auto_build_archive', allow_none=True)
- auto_build_archive = Reference(auto_build_archive_id, 'Archive.id')
+ auto_build_archive_id = Int(name="auto_build_archive", allow_none=True)
+ auto_build_archive = Reference(auto_build_archive_id, "Archive.id")
auto_build_pocket = DBEnum(enum=PackagePublishingPocket, allow_none=True)
- auto_build_channels = JSON('auto_build_channels', allow_none=True)
+ auto_build_channels = JSON("auto_build_channels", allow_none=True)
- is_stale = Bool(name='is_stale', allow_none=False)
+ is_stale = Bool(name="is_stale", allow_none=False)
- require_virtualized = Bool(name='require_virtualized')
+ require_virtualized = Bool(name="require_virtualized")
- _private = Bool(name='private')
+ _private = Bool(name="private")
def _valid_information_type(self, attr, value):
if not getUtility(ISnapSet).isValidInformationType(
- value, self.owner, self.branch, self.git_ref):
+ value, self.owner, self.branch, self.git_ref
+ ):
raise SnapPrivacyMismatch
return value
_information_type = DBEnum(
- enum=InformationType, default=InformationType.PUBLIC,
+ enum=InformationType,
+ default=InformationType.PUBLIC,
name="information_type",
- validator=_valid_information_type)
+ validator=_valid_information_type,
+ )
- allow_internet = Bool(name='allow_internet', allow_none=False)
+ allow_internet = Bool(name="allow_internet", allow_none=False)
- build_source_tarball = Bool(name='build_source_tarball', allow_none=False)
+ build_source_tarball = Bool(name="build_source_tarball", allow_none=False)
- store_upload = Bool(name='store_upload', allow_none=False)
+ store_upload = Bool(name="store_upload", allow_none=False)
- store_series_id = Int(name='store_series', allow_none=True)
- store_series = Reference(store_series_id, 'SnappySeries.id')
+ store_series_id = Int(name="store_series", allow_none=True)
+ store_series = Reference(store_series_id, "SnappySeries.id")
- store_name = Unicode(name='store_name', allow_none=True)
+ store_name = Unicode(name="store_name", allow_none=True)
- store_secrets = JSON('store_secrets', allow_none=True)
+ store_secrets = JSON("store_secrets", allow_none=True)
- _store_channels = JSON('store_channels', allow_none=True)
+ _store_channels = JSON("store_channels", allow_none=True)
- def __init__(self, registrant, owner, distro_series, name,
- description=None, branch=None, git_ref=None, auto_build=False,
- auto_build_archive=None, auto_build_pocket=None,
- auto_build_channels=None, require_virtualized=True,
- date_created=DEFAULT, information_type=InformationType.PUBLIC,
- allow_internet=True, build_source_tarball=False,
- store_upload=False, store_series=None, store_name=None,
- store_secrets=None, store_channels=None, project=None):
+ def __init__(
+ self,
+ registrant,
+ owner,
+ distro_series,
+ name,
+ description=None,
+ branch=None,
+ git_ref=None,
+ auto_build=False,
+ auto_build_archive=None,
+ auto_build_pocket=None,
+ auto_build_channels=None,
+ require_virtualized=True,
+ date_created=DEFAULT,
+ information_type=InformationType.PUBLIC,
+ allow_internet=True,
+ build_source_tarball=False,
+ store_upload=False,
+ store_series=None,
+ store_name=None,
+ store_secrets=None,
+ store_channels=None,
+ project=None,
+ ):
"""Construct a `Snap`."""
super().__init__()
@@ -481,8 +466,11 @@ class Snap(Storm, WebhookTargetMixin):
@property
def information_type(self):
if self._information_type is None:
- return (InformationType.PROPRIETARY if self._private
- else InformationType.PUBLIC)
+ return (
+ InformationType.PROPRIETARY
+ if self._private
+ else InformationType.PUBLIC
+ )
return self._information_type
@information_type.setter
@@ -506,7 +494,8 @@ class Snap(Storm, WebhookTargetMixin):
def _api_git_path(self, value):
if self.git_repository is None and self.git_repository_url is None:
raise BadSnapSource(
- "git_path may only be set on a Git-based snap.")
+ "git_path may only be set on a Git-based snap."
+ )
if value is None:
raise BadSnapSource("git_path may not be set to None.")
self.git_path = value
@@ -518,7 +507,8 @@ class Snap(Storm, WebhookTargetMixin):
return self.git_repository.getRefByPath(self.git_path)
elif self.git_repository_url is not None:
return getUtility(IGitRefRemoteSet).new(
- self.git_repository_url, self.git_path)
+ self.git_repository_url, self.git_path
+ )
else:
return None
@@ -560,32 +550,42 @@ class Snap(Storm, WebhookTargetMixin):
self.project = pillar
else:
raise ValueError(
- 'The pillar of a Snap must be an IProduct instance.')
+ "The pillar of a Snap must be an IProduct instance."
+ )
@property
def available_processors(self):
"""See `ISnap`."""
clauses = [Processor.id == DistroArchSeries.processor_id]
if self.distro_series is not None:
- clauses.append(DistroArchSeries.id.is_in(
- self.distro_series.enabled_architectures.get_select_expr(
- DistroArchSeries.id)))
+ clauses.append(
+ DistroArchSeries.id.is_in(
+ self.distro_series.enabled_architectures.get_select_expr(
+ DistroArchSeries.id
+ )
+ )
+ )
else:
# We don't know the series until we've looked at snapcraft.yaml
# to dispatch a build, so enabled architectures for any active
# series will do.
- clauses.extend([
- DistroArchSeries.enabled,
- DistroArchSeries.distroseriesID == DistroSeries.id,
- DistroSeries.status.is_in(ACTIVE_STATUSES),
- ])
+ clauses.extend(
+ [
+ DistroArchSeries.enabled,
+ DistroArchSeries.distroseriesID == DistroSeries.id,
+ DistroSeries.status.is_in(ACTIVE_STATUSES),
+ ]
+ )
return Store.of(self).find(Processor, *clauses).config(distinct=True)
def _getProcessors(self):
- return list(Store.of(self).find(
- Processor,
- Processor.id == SnapArch.processor_id,
- SnapArch.snap == self))
+ return list(
+ Store.of(self).find(
+ Processor,
+ Processor.id == SnapArch.processor_id,
+ SnapArch.snap == self,
+ )
+ )
def setProcessors(self, processors, check_permissions=False, user=None):
"""See `ISnap`."""
@@ -594,21 +594,25 @@ class Snap(Storm, WebhookTargetMixin):
if user is not None:
roles = IPersonRoles(user)
authz = lambda perm: getAdapter(self, IAuthorization, perm)
- if authz('launchpad.Admin').checkAuthenticated(roles):
+ if authz("launchpad.Admin").checkAuthenticated(roles):
can_modify = lambda proc: True
- elif authz('launchpad.Edit').checkAuthenticated(roles):
+ elif authz("launchpad.Edit").checkAuthenticated(roles):
can_modify = lambda proc: not proc.restricted
if can_modify is None:
raise Unauthorized(
- 'Permission launchpad.Admin or launchpad.Edit required '
- 'on %s.' % self)
+ "Permission launchpad.Admin or launchpad.Edit required "
+ "on %s." % self
+ )
else:
can_modify = lambda proc: True
- enablements = dict(Store.of(self).find(
- (Processor, SnapArch),
- Processor.id == SnapArch.processor_id,
- SnapArch.snap == self))
+ enablements = dict(
+ Store.of(self).find(
+ (Processor, SnapArch),
+ Processor.id == SnapArch.processor_id,
+ SnapArch.snap == self,
+ )
+ )
for proc in enablements:
if proc not in processors:
if not can_modify(proc):
@@ -637,32 +641,37 @@ class Snap(Storm, WebhookTargetMixin):
and das.processor in self.processors
and (
das.processor.supports_virtualized
- or not self.require_virtualized)
- and (snap_base is None or das.processor in snap_base.processors))
+ or not self.require_virtualized
+ )
+ and (snap_base is None or das.processor in snap_base.processors)
+ )
def _isArchitectureAllowed(self, das, pocket, snap_base=None):
- return (
- das.getChroot(pocket=pocket) is not None
- and self._isBuildableArchitectureAllowed(das, snap_base=snap_base))
+ return das.getChroot(
+ pocket=pocket
+ ) is not None and self._isBuildableArchitectureAllowed(
+ das, snap_base=snap_base
+ )
def getAllowedArchitectures(
- self,
- distro_series: t.Optional[IDistroSeries] = None,
- snap_base = None
+ self, distro_series: t.Optional[IDistroSeries] = None, snap_base=None
) -> t.List[IDistroArchSeries]:
"""See `ISnap`."""
if distro_series is None:
distro_series = self.distro_series
return [
- das for das in distro_series.buildable_architectures
- if self._isBuildableArchitectureAllowed(das, snap_base=snap_base)]
+ das
+ for das in distro_series.buildable_architectures
+ if self._isBuildableArchitectureAllowed(das, snap_base=snap_base)
+ ]
@property
def store_distro_series(self):
if self.store_series is None:
return None
return getUtility(ISnappyDistroSeriesSet).getByBothSeries(
- self.store_series, self.distro_series)
+ self.store_series, self.distro_series
+ )
@store_distro_series.setter
def store_distro_series(self, value):
@@ -685,13 +694,20 @@ class Snap(Storm, WebhookTargetMixin):
if self.pillar is None:
return set(FREE_INFORMATION_TYPES)
required_grant = BRANCH_POLICY_REQUIRED_GRANTS[
- self.project.branch_sharing_policy]
- if (required_grant is not None
- and not getUtility(IService, 'sharing').checkPillarAccess(
- [self.project], required_grant, self.owner)
- and (user is None
- or not getUtility(IService, 'sharing').checkPillarAccess(
- [self.project], required_grant, user))):
+ self.project.branch_sharing_policy
+ ]
+ if (
+ required_grant is not None
+ and not getUtility(IService, "sharing").checkPillarAccess(
+ [self.project], required_grant, self.owner
+ )
+ and (
+ user is None
+ or not getUtility(IService, "sharing").checkPillarAccess(
+ [self.project], required_grant, user
+ )
+ )
+ ):
return []
return BRANCH_POLICY_ALLOWED_TYPES[self.project.branch_sharing_policy]
@@ -699,26 +715,33 @@ class Snap(Storm, WebhookTargetMixin):
def extractSSOCaveats(macaroon):
locations = [
urlsplit(root).netloc
- for root in CurrentOpenIDEndPoint.getAllRootURLs()]
+ for root in CurrentOpenIDEndPoint.getAllRootURLs()
+ ]
return [
- c for c in macaroon.third_party_caveats()
- if c.location in locations]
+ c
+ for c in macaroon.third_party_caveats()
+ if c.location in locations
+ ]
def beginAuthorization(self):
"""See `ISnap`."""
if self.store_series is None:
raise CannotAuthorizeStoreUploads(
"Cannot authorize uploads of a snap package with no store "
- "series.")
+ "series."
+ )
if self.store_name is None:
raise CannotAuthorizeStoreUploads(
"Cannot authorize uploads of a snap package with no store "
- "name.")
+ "name."
+ )
snap_store_client = getUtility(ISnapStoreClient)
root_macaroon_raw = snap_store_client.requestPackageUploadPermission(
- self.store_series, self.store_name)
+ self.store_series, self.store_name
+ )
sso_caveats = self.extractSSOCaveats(
- Macaroon.deserialize(root_macaroon_raw))
+ Macaroon.deserialize(root_macaroon_raw)
+ )
# We must have exactly one SSO caveat; more than one should never be
# required and could be an attempt to substitute weaker caveats. We
# might as well OOPS here, even though the cause of this is probably
@@ -726,15 +749,18 @@ class Snap(Storm, WebhookTargetMixin):
# and it should show up in our OOPS reports.
if not sso_caveats:
raise SnapAuthorizationBadGeneratedMacaroon(
- "Macaroon has no SSO caveats")
+ "Macaroon has no SSO caveats"
+ )
elif len(sso_caveats) > 1:
raise SnapAuthorizationBadGeneratedMacaroon(
- "Macaroon has multiple SSO caveats")
- self.store_secrets = {'root': root_macaroon_raw}
+ "Macaroon has multiple SSO caveats"
+ )
+ self.store_secrets = {"root": root_macaroon_raw}
return sso_caveats[0].caveat_id
- def completeAuthorization(self, root_macaroon=None,
- discharge_macaroon=None):
+ def completeAuthorization(
+ self, root_macaroon=None, discharge_macaroon=None
+ ):
"""See `ISnap`."""
if root_macaroon is not None:
try:
@@ -746,7 +772,8 @@ class Snap(Storm, WebhookTargetMixin):
if self.store_secrets is None or "root" not in self.store_secrets:
raise CannotAuthorizeStoreUploads(
"beginAuthorization must be called before "
- "completeAuthorization.")
+ "completeAuthorization."
+ )
if discharge_macaroon is not None:
try:
Macaroon.deserialize(discharge_macaroon)
@@ -754,9 +781,11 @@ class Snap(Storm, WebhookTargetMixin):
raise BadMacaroon("discharge_macaroon is invalid.")
container = getUtility(IEncryptedContainer, "snap-store-secrets")
if container.can_encrypt:
- self.store_secrets["discharge_encrypted"] = (
- removeSecurityProxy(container.encrypt(
- discharge_macaroon.encode("UTF-8"))))
+ self.store_secrets[
+ "discharge_encrypted"
+ ] = removeSecurityProxy(
+ container.encrypt(discharge_macaroon.encode("UTF-8"))
+ )
self.store_secrets.pop("discharge", None)
else:
self.store_secrets["discharge"] = discharge_macaroon
@@ -767,17 +796,21 @@ class Snap(Storm, WebhookTargetMixin):
@property
def can_upload_to_store(self):
- if (config.snappy.store_upload_url is None or
- config.snappy.store_url is None or
- self.store_series is None or
- self.store_name is None or
- self.store_secrets is None or
- "root" not in self.store_secrets):
+ if (
+ config.snappy.store_upload_url is None
+ or config.snappy.store_url is None
+ or self.store_series is None
+ or self.store_name is None
+ or self.store_secrets is None
+ or "root" not in self.store_secrets
+ ):
return False
root_macaroon = Macaroon.deserialize(self.store_secrets["root"])
- if (self.extractSSOCaveats(root_macaroon) and
- "discharge" not in self.store_secrets and
- "discharge_encrypted" not in self.store_secrets):
+ if (
+ self.extractSSOCaveats(root_macaroon)
+ and "discharge" not in self.store_secrets
+ and "discharge_encrypted" not in self.store_secrets
+ ):
return False
return True
@@ -785,8 +818,9 @@ class Snap(Storm, WebhookTargetMixin):
"""May `requester` request builds of this snap from `archive`?"""
if not requester.inTeam(self.owner):
raise SnapNotOwner(
- "%s cannot create snap package builds owned by %s." %
- (requester.displayname, self.owner.displayname))
+ "%s cannot create snap package builds owned by %s."
+ % (requester.displayname, self.owner.displayname)
+ )
if not archive.enabled:
raise ArchiveDisabled(archive.displayname)
if archive.private and self.owner != archive.owner:
@@ -802,12 +836,13 @@ class Snap(Storm, WebhookTargetMixin):
snap_base=None,
channels=None,
build_request=None,
- target_architectures: t.Optional[t.List[str]] = None
+ target_architectures: t.Optional[t.List[str]] = None,
) -> ISnapBuild:
"""See `ISnap`."""
self._checkRequestBuild(requester, archive)
if not self._isArchitectureAllowed(
- distro_arch_series, pocket, snap_base=snap_base):
+ distro_arch_series, pocket, snap_base=snap_base
+ ):
raise SnapBuildDisallowedArchitecture(distro_arch_series, pocket)
if target_architectures:
@@ -815,7 +850,8 @@ class Snap(Storm, WebhookTargetMixin):
if not channels:
channels_clause = Or(
- SnapBuild.channels == None, SnapBuild.channels == {})
+ SnapBuild.channels == None, SnapBuild.channels == {}
+ )
else:
channels_clause = SnapBuild.channels == channels
pending = IStore(self).find(
@@ -826,26 +862,39 @@ class Snap(Storm, WebhookTargetMixin):
SnapBuild.pocket == pocket,
SnapBuild.target_architectures == target_architectures,
channels_clause,
- SnapBuild.status == BuildStatus.NEEDSBUILD)
+ SnapBuild.status == BuildStatus.NEEDSBUILD,
+ )
if pending.any() is not None:
raise SnapBuildAlreadyPending
build = getUtility(ISnapBuildSet).new(
- requester, self, archive, distro_arch_series, pocket,
- snap_base=snap_base, channels=channels,
+ requester,
+ self,
+ archive,
+ distro_arch_series,
+ pocket,
+ snap_base=snap_base,
+ channels=channels,
build_request=build_request,
- target_architectures=target_architectures)
+ target_architectures=target_architectures,
+ )
build.queueBuild()
notify(ObjectCreatedEvent(build, user=requester))
return build
- def requestBuilds(self, requester, archive, pocket, channels=None,
- architectures=None):
+ def requestBuilds(
+ self, requester, archive, pocket, channels=None, architectures=None
+ ):
"""See `ISnap`."""
self._checkRequestBuild(requester, archive)
job = getUtility(ISnapRequestBuildsJobSource).create(
- self, requester, archive, pocket, channels,
- architectures=architectures)
+ self,
+ requester,
+ archive,
+ pocket,
+ channels,
+ architectures=architectures,
+ )
return self.getBuildRequest(job.job_id)
@staticmethod
@@ -880,7 +929,8 @@ class Snap(Storm, WebhookTargetMixin):
elif self.distro_series is None:
# A base is mandatory if there's no configured distro series.
raise NoSuchSnapBase(
- snap_base_name if snap_base_name is not None else "<default>")
+ snap_base_name if snap_base_name is not None else "<default>"
+ )
else:
return self.distro_series
@@ -894,10 +944,18 @@ class Snap(Storm, WebhookTargetMixin):
else:
return channels
- def requestBuildsFromJob(self, requester, archive, pocket,
- channels=None, architectures=None,
- allow_failures=False, fetch_snapcraft_yaml=True,
- build_request=None, logger=None):
+ def requestBuildsFromJob(
+ self,
+ requester,
+ archive,
+ pocket,
+ channels=None,
+ architectures=None,
+ allow_failures=False,
+ fetch_snapcraft_yaml=True,
+ build_request=None,
+ logger=None,
+ ):
"""See `ISnap`."""
if not fetch_snapcraft_yaml and self.distro_series is None:
# Slightly misleading, but requestAutoBuilds is the only place
@@ -907,7 +965,8 @@ class Snap(Storm, WebhookTargetMixin):
if fetch_snapcraft_yaml:
try:
snapcraft_data = removeSecurityProxy(
- getUtility(ISnapSet).getSnapcraftYaml(self))
+ getUtility(ISnapSet).getSnapcraftYaml(self)
+ )
except CannotFetchSnapcraftYaml as e:
if not e.unsupported_remote:
raise
@@ -934,14 +993,21 @@ class Snap(Storm, WebhookTargetMixin):
# the same order as in BinaryPackageBuildSet.createForSource, to
# minimise confusion.
supported_arches = OrderedDict(
- (das.architecturetag, das) for das in sorted(
+ (das.architecturetag, das)
+ for das in sorted(
self.getAllowedArchitectures(
- distro_series, snap_base=snap_base),
- key=attrgetter("processor.id"))
- if (architectures is None or
- das.architecturetag in architectures))
+ distro_series, snap_base=snap_base
+ ),
+ key=attrgetter("processor.id"),
+ )
+ if (
+ architectures is None
+ or das.architecturetag in architectures
+ )
+ )
architectures_to_build = determine_architectures_to_build(
- snapcraft_data, list(supported_arches.keys()))
+ snapcraft_data, list(supported_arches.keys())
+ )
except Exception as e:
if not allow_failures:
raise
@@ -960,15 +1026,22 @@ class Snap(Storm, WebhookTargetMixin):
arch_channels = None
try:
build = self.requestBuild(
- requester, archive, supported_arches[arch], pocket,
- snap_base=snap_base, channels=arch_channels,
+ requester,
+ archive,
+ supported_arches[arch],
+ pocket,
+ snap_base=snap_base,
+ channels=arch_channels,
build_request=build_request,
- target_architectures=build_instance.target_architectures
+ target_architectures=build_instance.target_architectures,
)
if logger is not None:
logger.debug(
" - %s/%s/%s: Build requested.",
- self.owner.name, self.name, arch)
+ self.owner.name,
+ self.name,
+ arch,
+ )
builds.append(build)
except SnapBuildAlreadyPending:
pass
@@ -977,12 +1050,13 @@ class Snap(Storm, WebhookTargetMixin):
raise
elif logger is not None:
logger.exception(
- " - %s/%s/%s: %s",
- self.owner.name, self.name, arch, e)
+ " - %s/%s/%s: %s", self.owner.name, self.name, arch, e
+ )
return builds
- def requestAutoBuilds(self, allow_failures=False,
- fetch_snapcraft_yaml=False, logger=None):
+ def requestAutoBuilds(
+ self, allow_failures=False, fetch_snapcraft_yaml=False, logger=None
+ ):
"""See `ISnap`."""
if self.auto_build_archive is None:
raise CannotRequestAutoBuilds("auto_build_archive")
@@ -992,16 +1066,24 @@ class Snap(Storm, WebhookTargetMixin):
raise IncompatibleArguments(
"Cannot use requestAutoBuilds for a snap package without "
"inferring from snapcraft.yaml or distro_series being set. "
- "Consider using requestBuilds instead.")
+ "Consider using requestBuilds instead."
+ )
self.is_stale = False
if logger is not None:
logger.debug(
"Scheduling builds of snap package %s/%s",
- self.owner.name, self.name)
+ self.owner.name,
+ self.name,
+ )
return self.requestBuildsFromJob(
- self.owner, self.auto_build_archive, self.auto_build_pocket,
- channels=self.auto_build_channels, allow_failures=allow_failures,
- fetch_snapcraft_yaml=fetch_snapcraft_yaml, logger=logger)
+ self.owner,
+ self.auto_build_archive,
+ self.auto_build_pocket,
+ channels=self.auto_build_channels,
+ allow_failures=allow_failures,
+ fetch_snapcraft_yaml=fetch_snapcraft_yaml,
+ logger=logger,
+ )
def getBuildRequest(self, job_id):
"""See `ISnap`."""
@@ -1013,9 +1095,11 @@ class Snap(Storm, WebhookTargetMixin):
job_source = getUtility(ISnapRequestBuildsJobSource)
# The returned jobs are ordered by descending ID.
jobs = job_source.findBySnap(
- self, statuses=(JobStatus.WAITING, JobStatus.RUNNING))
+ self, statuses=(JobStatus.WAITING, JobStatus.RUNNING)
+ )
return DecoratedResultSet(
- jobs, result_decorator=SnapBuildRequest.fromJob)
+ jobs, result_decorator=SnapBuildRequest.fromJob
+ )
@property
def failed_build_requests(self):
@@ -1024,7 +1108,8 @@ class Snap(Storm, WebhookTargetMixin):
# The returned jobs are ordered by descending ID.
jobs = job_source.findBySnap(self, statuses=(JobStatus.FAILED,))
return DecoratedResultSet(
- jobs, result_decorator=SnapBuildRequest.fromJob)
+ jobs, result_decorator=SnapBuildRequest.fromJob
+ )
def _getBuilds(self, filter_term, order_by):
"""The actual query to get the builds."""
@@ -1033,9 +1118,11 @@ class Snap(Storm, WebhookTargetMixin):
SnapBuild.archive_id == Archive.id,
Archive._enabled,
get_enabled_archive_filter(
- getUtility(ILaunchBag).user, include_public=True,
- include_subscribed=True)
- ]
+ getUtility(ILaunchBag).user,
+ include_public=True,
+ include_subscribed=True,
+ ),
+ ]
if filter_term is not None:
query_args.append(filter_term)
result = Store.of(self).find(SnapBuild, *query_args)
@@ -1044,7 +1131,7 @@ class Snap(Storm, WebhookTargetMixin):
def eager_load(rows):
getUtility(ISnapBuildSet).preloadBuildsData(rows)
getUtility(IBuildQueueSet).preloadForBuildFarmJobs(rows)
- load_related(Builder, rows, ['builder_id'])
+ load_related(Builder, rows, ["builder_id"])
return DecoratedResultSet(result, pre_iter_hook=eager_load)
@@ -1083,7 +1170,7 @@ class Snap(Storm, WebhookTargetMixin):
"when_complete_estimate": build.estimate,
"build_log_url": build.log_url,
"build_log_size": build_log_size,
- }
+ }
return result
def getBuildSummaries(self, request_ids=None, build_ids=None, user=None):
@@ -1101,7 +1188,8 @@ class Snap(Storm, WebhookTargetMixin):
# go through Snap._getBuilds which checks visibility. This
# saves an Archive query per build in the security adapter.
all_build_ids.extend(
- [removeSecurityProxy(build).id for build in builds])
+ [removeSecurityProxy(build).id for build in builds]
+ )
else:
requests = []
@@ -1109,66 +1197,77 @@ class Snap(Storm, WebhookTargetMixin):
all_build_ids.extend(build_ids)
all_build_summaries = self.getBuildSummariesForSnapBuildIds(
- all_build_ids)
+ all_build_ids
+ )
for request in requests:
build_summaries = []
for build in sorted(
- builds_by_request[request.id], key=attrgetter("id"),
- reverse=True):
+ builds_by_request[request.id],
+ key=attrgetter("id"),
+ reverse=True,
+ ):
if build.id in all_build_summaries:
# Include enough information for
# snap.update_build_statuses.js to populate new build
# rows.
build_summary = {
"self_link": canonical_url(
- build, path_only_if_possible=True),
+ build, path_only_if_possible=True
+ ),
"id": build.id,
"distro_arch_series_link": canonical_url(
build.distro_arch_series,
- path_only_if_possible=True),
+ path_only_if_possible=True,
+ ),
"architecture_tag": (
- build.distro_arch_series.architecturetag),
+ build.distro_arch_series.architecturetag
+ ),
"archive_link": ArchiveFormatterAPI(
- build.archive).link(None),
- }
+ build.archive
+ ).link(None),
+ }
build_summary.update(all_build_summaries[build.id])
build_summaries.append(build_summary)
result["requests"][request.id] = {
"status": request.status.name,
"error_message": request.error_message,
"builds": build_summaries,
- }
+ }
- for build_id in (build_ids or []):
+ for build_id in build_ids or []:
if build_id in all_build_summaries:
result["builds"][build_id] = all_build_summaries[build_id]
return result
def getBuildByStoreRevision(self, store_upload_revision, user=None):
- build = Store.of(self).find(
- SnapBuild,
- SnapBuild.snap == self,
- SnapBuild._store_upload_revision == store_upload_revision,
- SnapBuild.archive == Archive.id,
- Archive._enabled,
- get_enabled_archive_filter(
- user,
- include_public=True,
- include_subscribed=True)
- ).one()
+ build = (
+ Store.of(self)
+ .find(
+ SnapBuild,
+ SnapBuild.snap == self,
+ SnapBuild._store_upload_revision == store_upload_revision,
+ SnapBuild.archive == Archive.id,
+ Archive._enabled,
+ get_enabled_archive_filter(
+ user, include_public=True, include_subscribed=True
+ ),
+ )
+ .one()
+ )
return build
@property
def builds(self):
"""See `ISnap`."""
order_by = (
- NullsLast(Desc(Greatest(
- SnapBuild.date_started,
- SnapBuild.date_finished))),
+ NullsLast(
+ Desc(Greatest(SnapBuild.date_started, SnapBuild.date_finished))
+ ),
Desc(SnapBuild.date_created),
- Desc(SnapBuild.id))
+ Desc(SnapBuild.id),
+ )
return self._getBuilds(None, order_by)
@property
@@ -1179,23 +1278,24 @@ class Snap(Storm, WebhookTargetMixin):
BuildStatus.BUILDING,
BuildStatus.UPLOADING,
BuildStatus.CANCELLING,
- ]
+ ]
@property
def completed_builds(self):
"""See `ISnap`."""
- filter_term = (Not(SnapBuild.status.is_in(self._pending_states)))
+ filter_term = Not(SnapBuild.status.is_in(self._pending_states))
order_by = (
- NullsLast(Desc(Greatest(
- SnapBuild.date_started,
- SnapBuild.date_finished))),
- Desc(SnapBuild.id))
+ NullsLast(
+ Desc(Greatest(SnapBuild.date_started, SnapBuild.date_finished))
+ ),
+ Desc(SnapBuild.id),
+ )
return self._getBuilds(filter_term, order_by)
@property
def pending_builds(self):
"""See `ISnap`."""
- filter_term = (SnapBuild.status.is_in(self._pending_states))
+ filter_term = SnapBuild.status.is_in(self._pending_states)
# We want to order by date_created but this is the same as ordering
# by id (since id increases monotonically) and is less expensive.
order_by = Desc(SnapBuild.id)
@@ -1204,14 +1304,16 @@ class Snap(Storm, WebhookTargetMixin):
@property
def subscriptions(self):
return Store.of(self).find(
- SnapSubscription, SnapSubscription.snap == self)
+ SnapSubscription, SnapSubscription.snap == self
+ )
@property
def subscribers(self):
return Store.of(self).find(
Person,
SnapSubscription.person_id == Person.id,
- SnapSubscription.snap == self)
+ SnapSubscription.snap == self,
+ )
def visibleByUser(self, user):
"""See `ISnap`."""
@@ -1221,9 +1323,8 @@ class Snap(Storm, WebhookTargetMixin):
return False
store = IStore(self)
return not store.find(
- Snap,
- Snap.id == self.id,
- get_snap_privacy_filter(user)).is_empty()
+ Snap, Snap.id == self.id, get_snap_privacy_filter(user)
+ ).is_empty()
def hasSubscription(self, person):
"""See `ISnap`."""
@@ -1235,51 +1336,64 @@ class Snap(Storm, WebhookTargetMixin):
"""
if person is None:
return None
- return Store.of(self).find(
- SnapSubscription,
- SnapSubscription.person == person,
- SnapSubscription.snap == self).one()
+ return (
+ Store.of(self)
+ .find(
+ SnapSubscription,
+ SnapSubscription.person == person,
+ SnapSubscription.snap == self,
+ )
+ .one()
+ )
def userCanBeSubscribed(self, person):
"""Checks if the given person can subscribe to this snap recipe."""
return not (
- self.private and
- person.is_team and
- person.anyone_can_join())
+ self.private and person.is_team and person.anyone_can_join()
+ )
def subscribe(self, person, subscribed_by, ignore_permissions=False):
"""See `ISnap`."""
if not self.userCanBeSubscribed(person):
raise SubscriptionPrivacyViolation(
"Open and delegated teams cannot be subscribed to private "
- "snap recipes.")
+ "snap recipes."
+ )
subscription = self.getSubscription(person)
if subscription is None:
subscription = SnapSubscription(
- person=person, snap=self, subscribed_by=subscribed_by)
+ person=person, snap=self, subscribed_by=subscribed_by
+ )
Store.of(subscription).flush()
service = getUtility(IService, "sharing")
snaps = service.getVisibleArtifacts(
- person, snaps=[self], ignore_permissions=True)["snaps"]
+ person, snaps=[self], ignore_permissions=True
+ )["snaps"]
if not snaps:
service.ensureAccessGrants(
- [person], subscribed_by, snaps=[self],
- ignore_permissions=ignore_permissions)
+ [person],
+ subscribed_by,
+ snaps=[self],
+ ignore_permissions=ignore_permissions,
+ )
def unsubscribe(self, person, unsubscribed_by, ignore_permissions=False):
"""See `ISnap`."""
subscription = self.getSubscription(person)
if subscription is None:
return
- if (not ignore_permissions
- and not subscription.canBeUnsubscribedByUser(unsubscribed_by)):
+ if (
+ not ignore_permissions
+ and not subscription.canBeUnsubscribedByUser(unsubscribed_by)
+ ):
raise UserCannotUnsubscribePerson(
- '%s does not have permission to unsubscribe %s.' % (
- unsubscribed_by.displayname,
- person.displayname))
+ "%s does not have permission to unsubscribe %s."
+ % (unsubscribed_by.displayname, person.displayname)
+ )
artifact = getUtility(IAccessArtifactSource).find([self])
getUtility(IAccessArtifactGrantSource).revokeByArtifact(
- artifact, [person])
+ artifact, [person]
+ )
store = Store.of(subscription)
store.remove(subscription)
IStore(self).flush()
@@ -1305,7 +1419,8 @@ class Snap(Storm, WebhookTargetMixin):
def _deleteSnapSubscriptions(self):
subscriptions = Store.of(self).find(
- SnapSubscription, SnapSubscription.snap == self)
+ SnapSubscription, SnapSubscription.snap == self
+ )
subscriptions.remove()
def destroySelf(self):
@@ -1318,92 +1433,135 @@ class Snap(Storm, WebhookTargetMixin):
buildqueue_records = store.find(
BuildQueue,
BuildQueue._build_farm_job_id == SnapBuild.build_farm_job_id,
- SnapBuild.snap == self)
+ SnapBuild.snap == self,
+ )
for buildqueue_record in buildqueue_records:
buildqueue_record.destroySelf()
- build_farm_job_ids = list(store.find(
- SnapBuild.build_farm_job_id, SnapBuild.snap == self))
+ build_farm_job_ids = list(
+ store.find(SnapBuild.build_farm_job_id, SnapBuild.snap == self)
+ )
# XXX cjwatson 2016-02-27 bug=322972: Requires manual SQL due to
# lack of support for DELETE FROM ... USING ... in Storm.
- store.execute("""
+ store.execute(
+ """
DELETE FROM SnapFile
USING SnapBuild
WHERE
SnapFile.snapbuild = SnapBuild.id AND
SnapBuild.snap = ?
- """, (self.id,))
- store.execute("""
+ """,
+ (self.id,),
+ )
+ store.execute(
+ """
DELETE FROM SnapBuildJob
USING SnapBuild
WHERE
SnapBuildJob.snapbuild = SnapBuild.id AND
SnapBuild.snap = ?
- """, (self.id,))
+ """,
+ (self.id,),
+ )
store.find(SnapBuild, SnapBuild.snap == self).remove()
affected_jobs = Select(
- [SnapJob.job_id], And(SnapJob.job == Job.id, SnapJob.snap == self))
+ [SnapJob.job_id], And(SnapJob.job == Job.id, SnapJob.snap == self)
+ )
store.find(Job, Job.id.is_in(affected_jobs)).remove()
getUtility(IWebhookSet).delete(self.webhooks)
self._deleteAccessGrants()
self._deleteSnapSubscriptions()
store.remove(self)
store.find(
- BuildFarmJob, BuildFarmJob.id.is_in(build_farm_job_ids)).remove()
+ BuildFarmJob, BuildFarmJob.id.is_in(build_farm_job_ids)
+ ).remove()
class SnapArch(Storm):
"""Link table to back `Snap.processors`."""
- __storm_table__ = 'SnapArch'
- __storm_primary__ = ('snap_id', 'processor_id')
+ __storm_table__ = "SnapArch"
+ __storm_primary__ = ("snap_id", "processor_id")
- snap_id = Int(name='snap', allow_none=False)
- snap = Reference(snap_id, 'Snap.id')
+ snap_id = Int(name="snap", allow_none=False)
+ snap = Reference(snap_id, "Snap.id")
- processor_id = Int(name='processor', allow_none=False)
- processor = Reference(processor_id, 'Processor.id')
+ processor_id = Int(name="processor", allow_none=False)
+ processor = Reference(processor_id, "Processor.id")
@implementer(ISnapSet)
class SnapSet:
"""See `ISnapSet`."""
- def new(self, registrant, owner, distro_series, name, description=None,
- branch=None, git_repository=None, git_repository_url=None,
- git_path=None, git_ref=None, auto_build=False,
- auto_build_archive=None, auto_build_pocket=None,
- auto_build_channels=None, require_virtualized=True,
- processors=None, date_created=DEFAULT,
- information_type=InformationType.PUBLIC, allow_internet=True,
- build_source_tarball=False, store_upload=False,
- store_series=None, store_name=None, store_secrets=None,
- store_channels=None, project=None):
+ def new(
+ self,
+ registrant,
+ owner,
+ distro_series,
+ name,
+ description=None,
+ branch=None,
+ git_repository=None,
+ git_repository_url=None,
+ git_path=None,
+ git_ref=None,
+ auto_build=False,
+ auto_build_archive=None,
+ auto_build_pocket=None,
+ auto_build_channels=None,
+ require_virtualized=True,
+ processors=None,
+ date_created=DEFAULT,
+ information_type=InformationType.PUBLIC,
+ allow_internet=True,
+ build_source_tarball=False,
+ store_upload=False,
+ store_series=None,
+ store_name=None,
+ store_secrets=None,
+ store_channels=None,
+ project=None,
+ ):
"""See `ISnapSet`."""
if not registrant.inTeam(owner):
if owner.is_team:
raise SnapNotOwner(
- "%s is not a member of %s." %
- (registrant.displayname, owner.displayname))
+ "%s is not a member of %s."
+ % (registrant.displayname, owner.displayname)
+ )
else:
raise SnapNotOwner(
- "%s cannot create snap packages owned by %s." %
- (registrant.displayname, owner.displayname))
+ "%s cannot create snap packages owned by %s."
+ % (registrant.displayname, owner.displayname)
+ )
- if sum([git_repository is not None, git_repository_url is not None,
- git_ref is not None]) > 1:
+ if (
+ sum(
+ [
+ git_repository is not None,
+ git_repository_url is not None,
+ git_ref is not None,
+ ]
+ )
+ > 1
+ ):
raise IncompatibleArguments(
"You cannot specify more than one of 'git_repository', "
- "'git_repository_url', and 'git_ref'.")
- if ((git_repository is None and git_repository_url is None) !=
- (git_path is None)):
+ "'git_repository_url', and 'git_ref'."
+ )
+ if (git_repository is None and git_repository_url is None) != (
+ git_path is None
+ ):
raise IncompatibleArguments(
"You must specify both or neither of "
- "'git_repository'/'git_repository_url' and 'git_path'.")
+ "'git_repository'/'git_repository_url' and 'git_path'."
+ )
if git_repository is not None:
git_ref = git_repository.getRefByPath(git_path)
elif git_repository_url is not None:
git_ref = getUtility(IGitRefRemoteSet).new(
- git_repository_url, git_path)
+ git_repository_url, git_path
+ )
if branch is None and git_ref is None:
raise NoSourceForSnap
if self.exists(owner, name):
@@ -1415,22 +1573,35 @@ class SnapSet:
# creation and to ensure that everything relevant is in the Storm
# cache.
if not self.isValidInformationType(
- information_type, owner, branch, git_ref):
+ information_type, owner, branch, git_ref
+ ):
raise SnapPrivacyMismatch
store = IMasterStore(Snap)
snap = Snap(
- registrant, owner, distro_series, name, description=description,
- branch=branch, git_ref=git_ref, auto_build=auto_build,
+ registrant,
+ owner,
+ distro_series,
+ name,
+ description=description,
+ branch=branch,
+ git_ref=git_ref,
+ auto_build=auto_build,
auto_build_archive=auto_build_archive,
auto_build_pocket=auto_build_pocket,
auto_build_channels=auto_build_channels,
- require_virtualized=require_virtualized, date_created=date_created,
- information_type=information_type, allow_internet=allow_internet,
+ require_virtualized=require_virtualized,
+ date_created=date_created,
+ information_type=information_type,
+ allow_internet=allow_internet,
build_source_tarball=build_source_tarball,
- store_upload=store_upload, store_series=store_series,
- store_name=store_name, store_secrets=store_secrets,
- store_channels=store_channels, project=project)
+ store_upload=store_upload,
+ store_series=store_series,
+ store_name=store_name,
+ store_secrets=store_secrets,
+ store_channels=store_channels,
+ project=project,
+ )
store.add(snap)
snap._reconcileAccess()
@@ -1439,7 +1610,8 @@ class SnapSet:
if processors is None:
processors = [
- p for p in snap.available_processors if p.build_by_default]
+ p for p in snap.available_processors if p.build_by_default
+ ]
snap.setProcessors(processors)
return snap
@@ -1448,8 +1620,9 @@ class SnapSet:
"""See `ISnapSet`."""
return BRANCH_POLICY_ALLOWED_TYPES[project.branch_sharing_policy]
- def isValidInformationType(self, information_type, owner, branch=None,
- git_ref=None):
+ def isValidInformationType(
+ self, information_type, owner, branch=None, git_ref=None
+ ):
private = information_type not in PUBLIC_INFORMATION_TYPES
if private:
# If appropriately enabled via feature flag.
@@ -1469,8 +1642,11 @@ class SnapSet:
return True
def _getByName(self, owner, name):
- return IStore(Snap).find(
- Snap, Snap.owner == owner, Snap.name == name).one()
+ return (
+ IStore(Snap)
+ .find(Snap, Snap.owner == owner, Snap.name == name)
+ .one()
+ )
def exists(self, owner, name):
"""See `ISnapSet`."""
@@ -1495,8 +1671,9 @@ class SnapSet:
raise NotImplementedError("Unknown pillar for snap: %s" % pillar)
return IStore(Snap).find(Snap, *conditions).one()
- def _getSnapsFromCollection(self, collection, owner=None,
- visible_by_user=None):
+ def _getSnapsFromCollection(
+ self, collection, owner=None, visible_by_user=None
+ ):
if IBranchCollection.providedBy(collection):
id_column = Snap.branch_id
ids = collection.getBranchIds()
@@ -1522,12 +1699,15 @@ class SnapSet:
def findByPerson(self, person, visible_by_user=None):
"""See `ISnapSet`."""
+
def _getSnaps(collection):
collection = collection.visibleByUser(visible_by_user)
owned = self._getSnapsFromCollection(
- collection.ownedBy(person), visible_by_user=visible_by_user)
+ collection.ownedBy(person), visible_by_user=visible_by_user
+ )
packaged = self._getSnapsFromCollection(
- collection, owner=person, visible_by_user=visible_by_user)
+ collection, owner=person, visible_by_user=visible_by_user
+ )
return owned.union(packaged)
bzr_collection = removeSecurityProxy(getUtility(IAllBranches))
@@ -1535,37 +1715,46 @@ class SnapSet:
git_collection = removeSecurityProxy(getUtility(IAllGitRepositories))
git_snaps = _getSnaps(git_collection)
git_url_snaps = IStore(Snap).find(
- Snap, Snap.owner == person, Snap.git_repository_url != None)
+ Snap, Snap.owner == person, Snap.git_repository_url != None
+ )
return bzr_snaps.union(git_snaps).union(git_url_snaps)
def findByProject(self, project, visible_by_user=None):
"""See `ISnapSet`."""
+
def _getSnaps(collection):
return self._getSnapsFromCollection(
collection.visibleByUser(visible_by_user),
- visible_by_user=visible_by_user)
+ visible_by_user=visible_by_user,
+ )
snaps_for_project = IStore(Snap).find(
Snap,
Snap.project == project,
- get_snap_privacy_filter(visible_by_user))
+ get_snap_privacy_filter(visible_by_user),
+ )
bzr_collection = removeSecurityProxy(IBranchCollection(project))
git_collection = removeSecurityProxy(IGitCollection(project))
- return snaps_for_project.union(
- _getSnaps(bzr_collection)).union(_getSnaps(git_collection))
+ return snaps_for_project.union(_getSnaps(bzr_collection)).union(
+ _getSnaps(git_collection)
+ )
- def findByBranch(self, branch, visible_by_user=None,
- check_permissions=True):
+ def findByBranch(
+ self, branch, visible_by_user=None, check_permissions=True
+ ):
"""See `ISnapSet`."""
clauses = [Snap.branch == branch]
if check_permissions:
clauses.append(get_snap_privacy_filter(visible_by_user))
- return IStore(Snap).find(
- Snap,
- *clauses)
+ return IStore(Snap).find(Snap, *clauses)
- def findByGitRepository(self, repository, paths=None,
- visible_by_user=None, check_permissions=True):
+ def findByGitRepository(
+ self,
+ repository,
+ paths=None,
+ visible_by_user=None,
+ check_permissions=True,
+ ):
"""See `ISnapSet`."""
clauses = [Snap.git_repository == repository]
if paths is not None:
@@ -1578,20 +1767,24 @@ class SnapSet:
"""See `ISnapSet`."""
return IStore(Snap).find(
Snap,
- Snap.git_repository == ref.repository, Snap.git_path == ref.path,
- get_snap_privacy_filter(visible_by_user))
+