Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Tagging #49

Merged
merged 9 commits into from
Feb 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions schema.graphql
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,12 @@ schema {
mutation: SchemaMutation
}

union AddTagResponse = AddTagSuccess | InvalidInputError | EnvironmentNotFoundError

type AddTagSuccess implements Success {
message: String!
}

type BuilderError implements Error {
message: String!
}
Expand Down Expand Up @@ -31,6 +37,7 @@ type Environment {
type: Type!
packages: [Package!]!
state: State
tags: [String!]!
requested: DateTime
buildStart: DateTime
buildDone: DateTime
Expand All @@ -48,6 +55,7 @@ input EnvironmentInput {
path: String!
description: String!
packages: [PackageInput!]!
tags: [String!] = null
}

type EnvironmentNotFoundError implements Error {
Expand Down Expand Up @@ -86,6 +94,7 @@ type PackageMultiVersion {
type SchemaMutation {
createEnvironment(env: EnvironmentInput!): CreateResponse!
deleteEnvironment(name: String!, path: String!): DeleteResponse!
addTag(name: String!, path: String!, tag: String!): AddTagResponse!
createFromModule(file: Upload!, modulePath: String!, environmentPath: String!): CreateResponse!
updateFromModule(file: Upload!, modulePath: String!, environmentPath: String!): UpdateResponse!
}
Expand Down
15 changes: 12 additions & 3 deletions softpack_core/artifacts.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
builder_out = "builder.out"
module_file = "module"
readme_file = "README.md"
meta_file = "meta.yml"
built_by_softpack_file = ".built_by_softpack"
built_by_softpack = Type.softpack.value
generated_from_module_file = ".generated_from_module"
Expand Down Expand Up @@ -154,6 +155,11 @@
map(lambda p: Package.from_name(p), info.packages)
)

meta = Box()

Check warning on line 158 in softpack_core/artifacts.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/artifacts.py#L158

Added line #L158 was not covered by tests
if Artifacts.meta_file in self.obj:
meta = Box.from_yaml(self.obj[Artifacts.meta_file].data)
info["tags"] = getattr(meta, "tags", [])

Check warning on line 161 in softpack_core/artifacts.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/artifacts.py#L160-L161

Added lines #L160 - L161 were not covered by tests

return info

def __iter__(self) -> Iterator["Artifacts.Object"]:
Expand Down Expand Up @@ -322,18 +328,21 @@

return itertools.chain.from_iterable(map(self.environments, folders))

def get(self, path: Path, name: str) -> Optional[pygit2.Tree]:
def get(self, path: Path, name: str) -> Optional[Object]:
"""Return the environment at the specified name and path.

Args:
path: the path containing the environment folder
name: the name of the environment folder

Returns:
pygit2.Tree: a pygit2.Tree or None
Object: an Object or None
"""
try:
return self.tree(str(self.environments_folder(str(path), name)))
return self.Object(

Check warning on line 342 in softpack_core/artifacts.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/artifacts.py#L342

Added line #L342 was not covered by tests
Path(path, name),
self.tree(str(self.environments_folder(str(path), name))),
)
except KeyError:
return None

Expand Down
115 changes: 108 additions & 7 deletions softpack_core/schemas/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from dataclasses import dataclass
from pathlib import Path
from traceback import format_exception_only
from typing import Iterable, List, Optional, Tuple, Union, cast
from typing import List, Optional, Tuple, Union, cast

import httpx
import starlette.datastructures
Expand Down Expand Up @@ -52,6 +52,11 @@
"""Environment successfully updated."""


@strawberry.type
class AddTagSuccess(Success):
"""Successfully added tag to environment."""


@strawberry.type
class DeleteEnvironmentSuccess(Success):
"""Environment successfully deleted."""
Expand All @@ -74,6 +79,7 @@

path: str
name: str
message: str = "No environment with this path and name found."


@strawberry.type
Expand Down Expand Up @@ -109,6 +115,15 @@
],
)

AddTagResponse = strawberry.union(
"AddTagResponse",
[
AddTagSuccess,
InvalidInputError,
EnvironmentNotFoundError,
],
)

DeleteResponse = strawberry.union(
"DeleteResponse",
[
Expand All @@ -126,6 +141,32 @@
)


def validate_tag(tag: str) -> Union[None, InvalidInputError]:
"""If the given tag is invalid, return an error describing why, else None.

Tags must be composed solely of alphanumerics, dots, underscores,
dashes, and spaces, and not contain runs of multiple spaces or
leading/trailing whitespace.
"""
if tag != tag.strip():
return InvalidInputError(

Check warning on line 152 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L152

Added line #L152 was not covered by tests
message="Tags must not contain leading or trailing whitespace"
)

if re.fullmatch(r"[a-zA-Z0-9 ._-]+", tag) is None:
return InvalidInputError(

Check warning on line 157 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L157

Added line #L157 was not covered by tests
message="Tags must contain only alphanumerics, dots, "
"underscores, dashes, and spaces"
)

if re.search(r"\s\s", tag) is not None:
return InvalidInputError(

Check warning on line 163 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L163

Added line #L163 was not covered by tests
message="Tags must not contain runs of multiple spaces"
)

return None

Check warning on line 167 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L167

Added line #L167 was not covered by tests


@strawberry.input
class PackageInput(Package):
"""A Strawberry input model representing a package."""
Expand All @@ -146,6 +187,7 @@
path: str
description: str
packages: list[PackageInput]
tags: Optional[list[str]] = None

def validate(self) -> Union[None, InvalidInputError]:
"""Validate all values.
Expand All @@ -156,7 +198,11 @@
Returns:
None if good, or InvalidInputError if not all values supplied.
"""
if any(len(value) == 0 for value in vars(self).values()):
if any(
len(value) == 0
for key, value in vars(self).items()
if key != "tags"
):
return InvalidInputError(message="all fields must be filled in")

if not re.fullmatch("^[a-zA-Z0-9_-][a-zA-Z0-9_.-]*$", self.name):
Expand All @@ -178,6 +224,10 @@
"alphanumerics, dash, and underscore"
)

for tag in self.tags or []:
if (response := validate_tag(tag)) is not None:
return response

Check warning on line 229 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L229

Added line #L229 was not covered by tests

return None

@classmethod
Expand Down Expand Up @@ -255,6 +305,7 @@
type: Type
packages: list[Package]
state: Optional[State]
tags: list[str]
artifacts = Artifacts()

requested: Optional[datetime.datetime] = None
Expand All @@ -263,7 +314,7 @@
avg_wait_secs: Optional[float] = None

@classmethod
def iter(cls) -> Iterable["Environment"]:
def iter(cls) -> list["Environment"]:
"""Get an iterator over all Environment objects.

Returns:
Expand Down Expand Up @@ -323,6 +374,7 @@
state=spec.state,
readme=spec.get("readme", ""),
type=spec.get("type", ""),
tags=spec.tags,
)
except KeyError:
return None
Expand Down Expand Up @@ -443,7 +495,7 @@
name=env.name,
)

# Create folder with place-holder file
# Create folder with initial files
new_folder_path = Path(env.path, env.name)
try:
softpack_definition = dict(
Expand All @@ -453,13 +505,20 @@
for pkg in env.packages
],
)
ymlData = yaml.dump(softpack_definition)
definitionData = yaml.dump(softpack_definition)

Check warning on line 508 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L508

Added line #L508 was not covered by tests

meta = dict(tags=sorted(set(env.tags or [])))
metaData = yaml.dump(meta)

Check warning on line 511 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L510-L511

Added lines #L510 - L511 were not covered by tests

tree_oid = cls.artifacts.create_files(
new_folder_path,
[
(env_type, ""), # e.g. .built_by_softpack
(cls.artifacts.environments_file, ymlData), # softpack.yml
(
cls.artifacts.environments_file,
definitionData,
), # softpack.yml
(cls.artifacts.meta_file, metaData),
],
True,
)
Expand Down Expand Up @@ -491,11 +550,52 @@
return None

return EnvironmentNotFoundError(
message="No environment with this path and name found.",
path=str(path.parent),
name=path.name,
)

@classmethod
def add_tag(
cls, name: str, path: str, tag: str
) -> AddTagResponse: # type: ignore
"""Add a tag to an Environment.

Tags must be valid as defined by validate_tag().

Adding a tag that already exists is not an error.

Args:
name: the name of of environment
path: the path of the environment
tag: the tag to add

Returns:
A message confirming the success or failure of the operation.
"""
environment_path = Path(path, name)
response: Optional[Error] = cls.check_env_exists(environment_path)

Check warning on line 576 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L575-L576

Added lines #L575 - L576 were not covered by tests
if response is not None:
return response

Check warning on line 578 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L578

Added line #L578 was not covered by tests

if (response := validate_tag(tag)) is not None:
return response

Check warning on line 581 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L581

Added line #L581 was not covered by tests

tree = cls.artifacts.get(Path(path), name)

Check warning on line 583 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L583

Added line #L583 was not covered by tests
if tree is None:
return EnvironmentNotFoundError(path=path, name=name)
box = tree.spec()
tags = set(box.tags)

Check warning on line 587 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L585-L587

Added lines #L585 - L587 were not covered by tests
if tag in tags:
return AddTagSuccess(message="Tag already present")
tags.add(tag)

Check warning on line 590 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L589-L590

Added lines #L589 - L590 were not covered by tests

metadata = yaml.dump({"tags": sorted(tags)})
tree_oid = cls.artifacts.create_file(

Check warning on line 593 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L592-L593

Added lines #L592 - L593 were not covered by tests
environment_path, cls.artifacts.meta_file, metadata, overwrite=True
)
cls.artifacts.commit_and_push(tree_oid, "create environment folder")
return AddTagSuccess(message="Tag successfully added")

Check warning on line 597 in softpack_core/schemas/environment.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/schemas/environment.py#L596-L597

Added lines #L596 - L597 were not covered by tests

@classmethod
def delete(cls, name: str, path: str) -> DeleteResponse: # type: ignore
"""Delete an Environment.
Expand Down Expand Up @@ -749,6 +849,7 @@

createEnvironment: CreateResponse = Environment.create # type: ignore
deleteEnvironment: DeleteResponse = Environment.delete # type: ignore
addTag: AddTagResponse = Environment.add_tag # type: ignore
# writeArtifact: WriteArtifactResponse = ( # type: ignore
# Environment.write_artifact
# )
Expand Down
6 changes: 5 additions & 1 deletion softpack_core/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,4 +125,8 @@
response.status_code = 500
message = "Failed to trigger all resends"

return {"message": message, "successes": successes, "failures": failures}
return {

Check warning on line 128 in softpack_core/service.py

View check run for this annotation

Codecov / codecov/patch

softpack_core/service.py#L128

Added line #L128 was not covered by tests
"message": message,
"successes": successes,
"failures": failures,
}
4 changes: 2 additions & 2 deletions tests/integration/test_builderupload.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,5 @@ def test_builder_upload(testable_env_input):
tree = Environment.artifacts.get(env_parent, env_name)
assert tree is not None

assert tree[softpackYaml].data == softpackYamlContents
assert tree[spackLock].data == spackLockContents
assert tree.get(softpackYaml).data == softpackYamlContents
assert tree.get(spackLock).data == spackLockContents
Loading
Loading