From a4c578e019bdb24a91abf68fdd5b83c3f0fddf82 Mon Sep 17 00:00:00 2001 From: Jiri Kyjovsky Date: Tue, 11 Jul 2023 17:07:33 +0200 Subject: [PATCH] frontend: migrate API projects namespace to flask-restx --- .../coprs/views/apiv3_ns/__init__.py | 11 +++ .../coprs/views/apiv3_ns/apiv3_builds.py | 2 - .../coprs/views/apiv3_ns/apiv3_projects.py | 33 +++++-- .../coprs/views/apiv3_ns/schema.py | 97 ++++++++++++++++++- 4 files changed, 134 insertions(+), 9 deletions(-) diff --git a/frontend/coprs_frontend/coprs/views/apiv3_ns/__init__.py b/frontend/coprs_frontend/coprs/views/apiv3_ns/__init__.py index d15e9f307..6c82769db 100644 --- a/frontend/coprs_frontend/coprs/views/apiv3_ns/__init__.py +++ b/frontend/coprs_frontend/coprs/views/apiv3_ns/__init__.py @@ -1,4 +1,6 @@ import json +from http import HTTPStatus + import flask import wtforms import sqlalchemy @@ -374,3 +376,12 @@ def rename_fields_helper(input_dict, replace): for value in values: output.add(new_key, value) return output + + +def responser(result, status: HTTPStatus = HTTPStatus.OK): + """ + Make API response with status + """ + resp = flask.jsonify(result) + resp.status_code = status.value + return resp diff --git a/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_builds.py b/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_builds.py index 83a352ece..3cfd5e051 100644 --- a/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_builds.py +++ b/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_builds.py @@ -38,8 +38,6 @@ from .json2form import get_form_compatible_data - - apiv3_builds_ns = Namespace("build", description="Builds") api.add_namespace(apiv3_builds_ns) diff --git a/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py b/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py index 1b4b8b389..9b36fc2a8 100644 --- a/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py +++ b/frontend/coprs_frontend/coprs/views/apiv3_ns/apiv3_projects.py @@ -1,10 +1,16 @@ +from http import HTTPStatus + import flask + +from flask_restx import Namespace, Resource + from coprs.views.apiv3_ns import (query_params, get_copr, pagination, Paginator, GET, POST, PUT, DELETE, set_defaults) from coprs.views.apiv3_ns.json2form import get_form_compatible_data, get_input_dict from coprs import db, models, forms, db_session_scope from coprs.views.misc import api_login_required -from coprs.views.apiv3_ns import apiv3_ns, rename_fields_helper +from coprs.views.apiv3_ns import apiv3_ns, rename_fields_helper, api, responser +from coprs.views.apiv3_ns.schema import fullname_params, project_model, project_parser from coprs.logic.actions_logic import ActionsLogic from coprs.logic.coprs_logic import CoprsLogic, CoprChrootsLogic, MockChrootsLogic from coprs.logic.complex_logic import ComplexLogic @@ -16,6 +22,10 @@ from . import editable_copr +apiv3_projects_ns = Namespace("project", description="Projects") +api.add_namespace(apiv3_projects_ns) + + def to_dict(copr): return { "id": copr.id, @@ -77,11 +87,22 @@ def owner2tuple(ownername): return user, group -@apiv3_ns.route("/project", methods=GET) -@query_params() -def get_project(ownername, projectname): - copr = get_copr(ownername, projectname) - return flask.jsonify(to_dict(copr)) +@apiv3_projects_ns.route("") +class Project(Resource): + parser = project_parser() + + @apiv3_projects_ns.doc(fullname_params) + @apiv3_projects_ns.marshall_with(project_model) + @apiv3_projects_ns.expect(parser) + @apiv3_projects_ns.response(HTTPStatus.OK.value, "OK, Project data follows...") + def get(self): + """ + Get a project + Get details for a single Copr project. + """ + args = self.parser.parse_args() + copr = get_copr(args.ownername, args.projectname) + return responser(to_dict(copr)) @apiv3_ns.route("/project/list", methods=GET) diff --git a/frontend/coprs_frontend/coprs/views/apiv3_ns/schema.py b/frontend/coprs_frontend/coprs/views/apiv3_ns/schema.py index 1c3c83947..72c081cbb 100644 --- a/frontend/coprs_frontend/coprs/views/apiv3_ns/schema.py +++ b/frontend/coprs_frontend/coprs/views/apiv3_ns/schema.py @@ -43,6 +43,11 @@ example="@copr", ) +fullname_field = String( + description="Full name of the project", + example="@copr/pull-requests", +) + projectname_field = String( description="Name of the project", example="copr-dev", @@ -281,6 +286,56 @@ example="DESC", ) +homepage_field = Url( + description="Homepage URL of Copr project", + example="https://github.com/fedora-copr", +) + +contact_field = String( + description="Contact email", + example="pretty_user@fancydomain.uwu", +) + +description_field = String( + description="Description of Copr project", +) + +instructions_field = String( + description="Instructions how to install and use Copr project", +) + +persistent_field = Boolean( + description="Build and project is immune against deletion", +) + +unlisted_on_hp_field = Boolean( + description="Don't list Copr project on home page", +) + +auto_prune_field = Boolean( + description="Automatically delete builds in this project", +) + +build_enable_net_field = Boolean( + description="Enable networking for the builds", +) + +appstream_field = Boolean( + description="Enable Appstream for this project", +) + +packit_forge_projects_allowed_field = String( + description="Whitespace separated list of forge projects that will be " + "allowed to build in the project via Packit", + example="github.com/fedora-copr/copr github.com/another/project", +) + +follow_fedora_branching_field = Boolean( + description="If chroots for the new branch should be auto-enabled and populated from " + "rawhide ones", +) + + pagination_schema = { "limit_field": limit_field, "offset_field": offset_field, @@ -404,6 +459,33 @@ package_model = api.model("Package", package_schema) +project_schema = { + "id": id_field, + "name": projectname_field, + "ownername": ownername_field, + "full_name": fullname_field, + "homepage": homepage_field, + "contact": contact_field, + "description": description_field, + "instructions": instructions_field, + "devel_mode": Boolean, + "persistent": persistent_field, + "unlisted_on_hp": unlisted_on_hp_field, + "auto_prune": auto_prune_field, + "chroot_repos": Raw, + "additional_repos": additional_repos_field, + "enable_net": build_enable_net_field, + "bootstrap": String, + "isolation": isolation_field, + "module_hotfixes": module_hotfixes_field, + "appstream": appstream_field, + "packit_forge_projects_allowed": packit_forge_projects_allowed_field, + "follow_fedora_branching": follow_fedora_branching_field, +} + +project_model = api.model("Project", project_schema) + + def clone(field): """ Return a copy of a field @@ -412,9 +494,13 @@ def clone(field): return field.__class__(**kwargs) -add_package_params = { +fullname_params = { "ownername": ownername_field.description, "projectname": projectname_field.description, +} + +add_package_params = { + **fullname_params, "package_name": packagename_field.description, "source_type_text": source_type_field.description, } @@ -551,3 +637,12 @@ def project_chroot_parser(): arg.required = True parser.add_argument(arg) return parser + + +def project_parser(): + # pylint: disable=missing-function-docstring + parser = RequestParser() + parser.add_argument(field2arg("ownername", ownername_field, required=True)) + parser.add_argument(field2arg("projectname", projectname_field, required=True)) + + return parser