You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@allura.apache.org by br...@apache.org on 2022/09/22 14:50:31 UTC
[allura] 01/02: [#8467] DefOptScriptTask and other improvements to task submission in web ui
This is an automated email from the ASF dual-hosted git repository.
brondsem pushed a commit to branch db/8467
in repository https://gitbox.apache.org/repos/asf/allura.git
commit eefe2541595705db0ab8320456e665476dae3531
Author: Dave Brondsema <db...@slashdotmedia.com>
AuthorDate: Thu Sep 22 10:50:05 2022 -0400
[#8467] DefOptScriptTask and other improvements to task submission in web ui
---
Allura/allura/controllers/site_admin.py | 3 ++
Allura/allura/lib/helpers.py | 1 +
Allura/allura/scripts/scripttask.py | 69 ++++++++++++++++++++++--
Allura/allura/templates/site_admin_task_new.html | 7 +--
Allura/docs/api/scripts.rst | 25 +++++++++
Allura/docs/conf.py | 2 +-
6 files changed, 100 insertions(+), 7 deletions(-)
diff --git a/Allura/allura/controllers/site_admin.py b/Allura/allura/controllers/site_admin.py
index d7b2cf97a..d8372e44c 100644
--- a/Allura/allura/controllers/site_admin.py
+++ b/Allura/allura/controllers/site_admin.py
@@ -620,6 +620,9 @@ class TaskManagerController:
try:
task = v.TaskValidator.to_python(task_name)
doc = task.__doc__ or 'No doc string available'
+ doc = re.sub(r'^usage: ([^-][a-z_-]+ )?', # remove usage: and possible incorrect binary like "mod_wsgi"
+ 'Enter CLI formatted args above, like "args": ["--foo bar baz"]\n\n',
+ doc)
except Invalid as e:
error = str(e)
return dict(doc=doc, error=error)
diff --git a/Allura/allura/lib/helpers.py b/Allura/allura/lib/helpers.py
index 5ecb7862f..1df3497df 100644
--- a/Allura/allura/lib/helpers.py
+++ b/Allura/allura/lib/helpers.py
@@ -828,6 +828,7 @@ def datetimeformat(value, format='%Y-%m-%d %H:%M:%S'):
@contextmanager
def log_output(log):
+ # TODO: replace with contextlib.redirect_stdout and redirect_stderr?
class Writer:
def __init__(self, func):
diff --git a/Allura/allura/scripts/scripttask.py b/Allura/allura/scripts/scripttask.py
index f466610ea..0a1255c4c 100644
--- a/Allura/allura/scripts/scripttask.py
+++ b/Allura/allura/scripts/scripttask.py
@@ -32,12 +32,26 @@ To use, subclass ScriptTask and implement two methods::
'''Your main code goes here.'''
pass
+Or using the `defopt library <https://defopt.readthedocs.io/>`_ (must be installed),
+subclass `DefOptScriptTask` and implement one method::
+
+ class MyScript(DefOptScriptTask):
+ @classmethod
+ def execute(cls, *, limit: int = 10, dry_run: bool = False):
+ '''
+ Description/usage of this script
+
+ :param limit:
+ Explanation of parametes, if desired
+ '''
+ pass
+
To call as a script::
if __name__ == '__main__':
MyScript.main()
-To call as a task::
+To run as a background task::
# post the task with cmd-line-style args
MyScript.post('-p myproject --dry-run')
@@ -45,10 +59,10 @@ To call as a task::
"""
import argparse
+import contextlib
+import io
import logging
-import six
-
from allura.lib.decorators import task
from allura.lib.helpers import shlex_split
@@ -62,6 +76,7 @@ class MetaParserDocstring(type):
return cls.parser().format_help()
def __new__(meta, classname, bases, classDict):
+ # make it look like a task
return task(type.__new__(meta, classname, bases, classDict))
@@ -70,6 +85,8 @@ class ScriptTask(metaclass=MetaParserDocstring):
"""Base class for a command-line script that is also executable as a task."""
def __new__(cls, arg_string=''):
+ # when taskd calls SomeTaskClass(), then this runs. Not really the normal way to use __new__
+ # and can't use __init__ since we want to return a value
return cls._execute_task(arg_string)
@classmethod
@@ -94,3 +111,49 @@ class ScriptTask(metaclass=MetaParserDocstring):
def main(cls):
options = cls.parser().parse_args()
cls.execute(options)
+
+
+try:
+ import defopt
+except ModuleNotFoundError:
+ pass
+else:
+
+ class MetaDefOpt(type):
+ def __new__(meta, classname, bases, classDict):
+ return task(type.__new__(meta, classname, bases, classDict))
+
+ @property
+ def __doc__(cls):
+ with contextlib.redirect_stdout(io.StringIO()) as stderr:
+ try:
+ cls.main(argv=['--help'])
+ except SystemExit:
+ pass
+ return stderr.getvalue()
+
+
+ class DefOptScriptTask(metaclass=MetaDefOpt):
+ """Base class for a command-line script that is also executable as a task."""
+
+ def __new__(cls, arg_string=''):
+ # when taskd calls SomeTaskClass(), then this runs. Not really the normal way to use __new__
+ # and can't use __init__ since we want to return a value
+ return cls._execute_task(arg_string)
+
+ @classmethod
+ def _execute_task(cls, arg_string):
+ try:
+ return cls.main(argv=shlex_split(arg_string or ''))
+ except SystemExit:
+ raise Exception("Error parsing args: '%s'" % arg_string)
+
+ @classmethod
+ def main(cls, **extra_kwargs):
+ return defopt.run(cls.execute, no_negated_flags=True, **extra_kwargs)
+
+ @classmethod
+ def execute(cls, *args, **kwargs):
+ """User code goes here, using defopt kwargs with type annotations"""
+ raise NotImplementedError
+
diff --git a/Allura/allura/templates/site_admin_task_new.html b/Allura/allura/templates/site_admin_task_new.html
index ecd41daea..3ee645d41 100644
--- a/Allura/allura/templates/site_admin_task_new.html
+++ b/Allura/allura/templates/site_admin_task_new.html
@@ -71,8 +71,9 @@
<div>
<label>Task Name *</label>
<div class="input">
- <input name="task" value="{{form_values.get('task', '')}}" />
- <span class="note">Dotted python path to task callable</span>
+ <input name="task" value="{{form_values.get('task', '')}}"/>
+ <span class="note">Dotted python path to task callable
+ <br>e.g. allura.tasks.admin_tasks.install_app or allura.scripts.disable_users.DisableUsers</span>
</div>
{{error('task')}}
</div>
@@ -100,7 +101,7 @@
{{error('task_args')}}
</div>
- <input type="submit" /><br/>
+ <input type="submit" value="Run Task"/><br/>
<pre class="doc"></pre>
{{lib.csrf_token()}}
diff --git a/Allura/docs/api/scripts.rst b/Allura/docs/api/scripts.rst
new file mode 100644
index 000000000..c72abe658
--- /dev/null
+++ b/Allura/docs/api/scripts.rst
@@ -0,0 +1,25 @@
+.. Licensed to the Apache Software Foundation (ASF) under one
+ or more contributor license agreements. See the NOTICE file
+ distributed with this work for additional information
+ regarding copyright ownership. The ASF licenses this file
+ to you 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.
+
+.. _controllers_module:
+
+:mod:`allura.scripts`
+--------------------------------
+
+.. automodule:: allura.scripts
+
+ .. automodule:: allura.scripts.scripttask
diff --git a/Allura/docs/conf.py b/Allura/docs/conf.py
index 59be57ba9..7ed4b55f4 100644
--- a/Allura/docs/conf.py
+++ b/Allura/docs/conf.py
@@ -223,4 +223,4 @@ latex_documents = [
# Example configuration for intersphinx: refer to the Python standard library.
-intersphinx_mapping = {'http://docs.python.org/': None}
+intersphinx_mapping = {'https://docs.python.org/3/': None}