-
-
Notifications
You must be signed in to change notification settings - Fork 745
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add pants-plugins/api_spec
to streamline regenerating openapi.yaml
#5857
Merged
Merged
Changes from all commits
Commits
Show all changes
18 commits
Select commit
Hold shift + click to select a range
48ad8e0
add api-spec generation/validation to pants
cognifloyd 38c4555
add note about missing x-api-model validation of openapi spec
cognifloyd cb42c19
resolve lint issues in pants-plugins
cognifloyd 6bcbb5d
make api_spec generate Resource targets
cognifloyd 19faf3b
pants plugins updates
cognifloyd 21d522c
add tests for pants-plugins/api_spec generate
cognifloyd eefc40a
add tests for pants-plugins/api_spec validate
cognifloyd 7786bcb
update changelog entry
cognifloyd ec2d43d
add description of api_spec plugin to pants-plugins/README.md
cognifloyd cee6263
move openapi.yaml header into st2-generate-api-schema
cognifloyd 2591646
./pants tailor ::
cognifloyd 385c8b4
add dependent rules to api_spec plugin
cognifloyd ead223b
fix tests for pants-plugins/api_spec
cognifloyd 2ea3acd
./pants fmt lint pants-plugins/::
cognifloyd 43e265b
fix SPEC_HEADER
cognifloyd 5ff65ac
correct Makefile
cognifloyd 41d04dd
update pants-plugins/api_spec copyright to 2023
cognifloyd dade25a
address PR feedback
cognifloyd File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
python_sources() | ||
|
||
python_tests( | ||
name="tests", | ||
) |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
# Copyright 2023 The StackStorm Authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
|
||
from api_spec.rules import rules as api_spec_rules | ||
from api_spec.target_types import APISpec | ||
|
||
|
||
def rules(): | ||
return [*api_spec_rules()] | ||
|
||
|
||
def target_types(): | ||
return [APISpec] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,259 @@ | ||
# Copyright 2023 The StackStorm Authors. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# http://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
from dataclasses import dataclass | ||
|
||
from pants.backend.python.target_types import EntryPoint | ||
from pants.backend.python.util_rules import pex, pex_from_targets | ||
from pants.backend.python.util_rules.pex import ( | ||
VenvPex, | ||
VenvPexProcess, | ||
) | ||
from pants.backend.python.util_rules.pex_from_targets import PexFromTargetsRequest | ||
from pants.core.goals.fmt import FmtResult, FmtTargetsRequest | ||
from pants.core.goals.lint import LintResult, LintResults, LintTargetsRequest | ||
from pants.core.target_types import FileSourceField, ResourceSourceField | ||
from pants.core.util_rules.source_files import SourceFiles, SourceFilesRequest | ||
from pants.engine.addresses import Address | ||
from pants.engine.fs import ( | ||
CreateDigest, | ||
Digest, | ||
FileContent, | ||
MergeDigests, | ||
Snapshot, | ||
) | ||
from pants.engine.process import FallibleProcessResult, ProcessResult | ||
from pants.engine.rules import Get, MultiGet, collect_rules, rule | ||
from pants.engine.target import ( | ||
FieldSet, | ||
SourcesField, | ||
TransitiveTargets, | ||
TransitiveTargetsRequest, | ||
) | ||
from pants.engine.unions import UnionRule | ||
from pants.util.logging import LogLevel | ||
|
||
from api_spec.target_types import APISpecSourceField | ||
|
||
|
||
# these constants are also used in the tests | ||
CMD_SOURCE_ROOT = "st2common" | ||
CMD_DIR = "st2common/st2common/cmd" | ||
CMD_MODULE = "st2common.cmd" | ||
GENERATE_CMD = "generate_api_spec" | ||
VALIDATE_CMD = "validate_api_spec" | ||
|
||
|
||
@dataclass(frozen=True) | ||
class APISpecFieldSet(FieldSet): | ||
required_fields = (APISpecSourceField,) | ||
|
||
source: APISpecSourceField | ||
|
||
|
||
class GenerateAPISpecViaFmtTargetsRequest(FmtTargetsRequest): | ||
field_set_type = APISpecFieldSet | ||
name = GENERATE_CMD | ||
|
||
|
||
class ValidateAPISpecRequest(LintTargetsRequest): | ||
field_set_type = APISpecFieldSet | ||
name = VALIDATE_CMD | ||
|
||
|
||
@rule( | ||
desc="Update openapi.yaml with st2-generate-api-spec", | ||
level=LogLevel.DEBUG, | ||
) | ||
async def generate_api_spec_via_fmt( | ||
request: GenerateAPISpecViaFmtTargetsRequest, | ||
) -> FmtResult: | ||
# There will only be one target+field_set, but we iterate | ||
# to satisfy how fmt expects that there could be more than one. | ||
# If there is more than one, they will all get the same contents. | ||
|
||
# Find all the dependencies of our target | ||
transitive_targets = await Get( | ||
TransitiveTargets, | ||
TransitiveTargetsRequest( | ||
[field_set.address for field_set in request.field_sets] | ||
), | ||
) | ||
|
||
dependency_files_get = Get( | ||
SourceFiles, | ||
SourceFilesRequest( | ||
sources_fields=[ | ||
tgt.get(SourcesField) for tgt in transitive_targets.dependencies | ||
], | ||
for_sources_types=(FileSourceField, ResourceSourceField), | ||
), | ||
) | ||
|
||
source_files_get = Get( | ||
SourceFiles, | ||
SourceFilesRequest(field_set.source for field_set in request.field_sets), | ||
) | ||
|
||
# actually generate it with an external script. | ||
# Generation cannot be inlined here because it needs to import the st2 code. | ||
pex_get = Get( | ||
VenvPex, | ||
PexFromTargetsRequest( | ||
[ | ||
Address( | ||
CMD_DIR, | ||
target_name="cmd", | ||
relative_file_path=f"{GENERATE_CMD}.py", | ||
), | ||
], | ||
output_filename=f"{GENERATE_CMD}.pex", | ||
internal_only=True, | ||
main=EntryPoint.parse(f"{CMD_MODULE}.{GENERATE_CMD}:main"), | ||
), | ||
) | ||
|
||
pex, dependency_files, source_files = await MultiGet( | ||
pex_get, dependency_files_get, source_files_get | ||
) | ||
|
||
# If we were given an input digest from a previous formatter for the source files, then we | ||
# should use that input digest instead of the one we read from the filesystem. | ||
source_files_snapshot = ( | ||
source_files.snapshot if request.snapshot is None else request.snapshot | ||
) | ||
|
||
input_digest = await Get( | ||
Digest, | ||
MergeDigests((dependency_files.snapshot.digest, source_files_snapshot.digest)), | ||
) | ||
|
||
result = await Get( | ||
ProcessResult, | ||
VenvPexProcess( | ||
pex, | ||
argv=( | ||
"--config-file", | ||
"conf/st2.dev.conf", | ||
), | ||
input_digest=input_digest, | ||
description="Regenerating openapi.yaml api spec", | ||
level=LogLevel.DEBUG, | ||
), | ||
) | ||
|
||
contents = [ | ||
FileContent( | ||
f"{field_set.address.spec_path}/{field_set.source.value}", | ||
result.stdout, | ||
) | ||
for field_set in request.field_sets | ||
] | ||
|
||
output_digest = await Get(Digest, CreateDigest(contents)) | ||
output_snapshot = await Get(Snapshot, Digest, output_digest) | ||
# TODO: Drop result.stdout since we already wrote it to a file? | ||
return FmtResult.create(request, result, output_snapshot, strip_chroot_path=True) | ||
|
||
|
||
@rule( | ||
desc="Validate openapi.yaml with st2-validate-api-spec", | ||
level=LogLevel.DEBUG, | ||
) | ||
async def validate_api_spec( | ||
request: ValidateAPISpecRequest, | ||
) -> LintResults: | ||
# There will only be one target+field_set, but we iterate | ||
# to satisfy how lint expects that there could be more than one. | ||
# If there is more than one, they will all get the same contents. | ||
|
||
# Find all the dependencies of our target | ||
transitive_targets = await Get( | ||
TransitiveTargets, | ||
TransitiveTargetsRequest( | ||
[field_set.address for field_set in request.field_sets] | ||
), | ||
) | ||
|
||
dependency_files_get = Get( | ||
SourceFiles, | ||
SourceFilesRequest( | ||
sources_fields=[ | ||
tgt.get(SourcesField) for tgt in transitive_targets.dependencies | ||
], | ||
for_sources_types=(FileSourceField, ResourceSourceField), | ||
), | ||
) | ||
|
||
source_files_get = Get( | ||
SourceFiles, | ||
SourceFilesRequest(field_set.source for field_set in request.field_sets), | ||
) | ||
|
||
# actually validate it with an external script. | ||
# Validation cannot be inlined here because it needs to import the st2 code. | ||
pex_get = Get( | ||
VenvPex, | ||
PexFromTargetsRequest( | ||
[ | ||
Address( | ||
CMD_DIR, | ||
target_name="cmd", | ||
relative_file_path=f"{VALIDATE_CMD}.py", | ||
), | ||
], | ||
output_filename=f"{VALIDATE_CMD}.pex", | ||
internal_only=True, | ||
main=EntryPoint.parse(f"{CMD_MODULE}.{VALIDATE_CMD}:main"), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The same thing applies here. We are using |
||
), | ||
) | ||
|
||
pex, dependency_files, source_files = await MultiGet( | ||
pex_get, dependency_files_get, source_files_get | ||
) | ||
|
||
input_digest = await Get( | ||
Digest, | ||
MergeDigests((dependency_files.snapshot.digest, source_files.snapshot.digest)), | ||
) | ||
|
||
process_result = await Get( | ||
FallibleProcessResult, | ||
VenvPexProcess( | ||
pex, | ||
argv=( | ||
"--config-file", | ||
"conf/st2.dev.conf", | ||
# TODO: Uncomment these as part of a project to fix the (many) issues it identifies. | ||
# We can uncomment --validate-defs (and possibly --verbose) once the spec defs are valid. | ||
# "--validate-defs", # check for x-api-model in definitions | ||
cognifloyd marked this conversation as resolved.
Show resolved
Hide resolved
|
||
# "--verbose", # show model definitions on failure (only applies to --validate-defs) | ||
), | ||
input_digest=input_digest, | ||
description="Validating openapi.yaml api spec", | ||
level=LogLevel.DEBUG, | ||
), | ||
) | ||
|
||
result = LintResult.from_fallible_process_result(process_result) | ||
return LintResults([result], linter_name=request.name) | ||
|
||
|
||
def rules(): | ||
return [ | ||
*collect_rules(), | ||
UnionRule(FmtTargetsRequest, GenerateAPISpecViaFmtTargetsRequest), | ||
UnionRule(LintTargetsRequest, ValidateAPISpecRequest), | ||
*pex.rules(), | ||
*pex_from_targets.rules(), | ||
] |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We are using
st2common.cmd.generate_api_spec:main
directly instead of using thest2-generate-api-spec
script because pants can't run python with a-
in the name (because pants relies on importing from the target script, and python cannot import from files with a-
in the name).Eventually I'll get around to plumbing a change in pants to make that possible (it already works with the underlying pex, but pants hasn't learned about that arg yet). In the meantime, we just use the entrypoint itself instead of the script.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we in the wrong here for having a python script named
st2-generate-api-spec
in the first place?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a command with
-
is fairly common, so I don't think that's a problem.Pex (used by pants) already had everything it needed to read files without importing them, but that didn't have a user facing API. So, when I asked about it, John (pex maintainer) quickly added an
--executable
option, so now pex supports it. But, pants hasn't learned about that pex option yet, so it doesn't use it.I started adding the pants bits here, but haven't had time to dive farther than this: pantsbuild/pants@main...cognifloyd:pants:pex-exe-arg