Enable Python DPs use Metadata in automatic mode (#4336)

Data Processors running on import are applied before the activity
is added to RideCache and metrics are computed, this behavior is
by design, likely to optimize resource usage on bulk import.
So activityMetrics API is not available; a new getTag API was
added for this case and setTag/delTag/hasTag changed to work
in this context too.
When the Python fix is executed on activities already in the cache
either via Edit menu, Filters or other Python Fix changes are
notified via the corresponding RideItem.
Fixes #4095
[publish binaries]
This commit is contained in:
Alejandro Martinez
2023-03-05 16:26:25 -03:00
committed by GitHub
parent 42c43c7689
commit 67136b3cdf
11 changed files with 178 additions and 102 deletions

View File

@@ -202,7 +202,7 @@ void PythonConsole::keyPressEvent(QKeyEvent *e)
bool readOnly = pythonHost->readOnly();
QList<RideFile *> editedRideFiles;
python->cancelled = false;
python->runline(ScriptContext(context, nullptr, true, readOnly, &editedRideFiles), line);
python->runline(ScriptContext(context, nullptr, nullptr, true, readOnly, &editedRideFiles), line);
// finish up commands on edited rides
foreach (RideFile *f, editedRideFiles) {

View File

@@ -1,5 +1,6 @@
#include "FixPyDataProcessor.h"
#include "FixPyRunner.h"
#include "Athlete.h"
// Config widget used by the Preferences/Options config panes
class FixPyDataProcessorConfig : public DataProcessorConfig
@@ -24,7 +25,17 @@ bool FixPyDataProcessor::postProcess(RideFile *rideFile, DataProcessorConfig *se
QString errText;
bool useNewThread = op != "PYTHON";
Context* context = (rideFile) ? rideFile->context : nullptr;
FixPyRunner pyRunner(context, rideFile, useNewThread);
RideItem* rideItem = nullptr;
if (context && rideFile) {
// get RideItem from RideFile for Python functions using it
foreach(RideItem *item, context->athlete->rideCache->rides()) {
if (item->dateTime == rideFile->startTime()) {
rideItem = item;
break;
}
}
}
FixPyRunner pyRunner(context, rideFile, rideItem, useNewThread);
return pyRunner.run(pyScript->source, pyScript->iniKey, errText) == 0;
}

View File

@@ -5,8 +5,8 @@
#include "PythonEmbed.h"
#include "RideFileCommand.h"
FixPyRunner::FixPyRunner(Context *context, RideFile *rideFile, bool useNewThread)
: context(context), rideFile(rideFile), useNewThread(useNewThread)
FixPyRunner::FixPyRunner(Context *context, RideFile *rideFile, RideItem *rideItem, bool useNewThread)
: context(context), rideFile(rideFile), rideItem(rideItem), useNewThread(useNewThread)
{
}
@@ -34,6 +34,7 @@ int FixPyRunner::run(QString source, QString scriptKey, QString &errText)
FixPyRunParams params;
params.context = context;
params.rideFile = rideFile;
params.rideItem = rideItem;
params.script = QString(line);
if (useNewThread) {
@@ -75,7 +76,7 @@ void FixPyRunner::execScript(FixPyRunParams *params)
QList<RideFile *> editedRideFiles;
python->canvas = NULL;
python->chart = NULL;
python->runline(ScriptContext(params->context, params->rideFile, false,
python->runline(ScriptContext(params->context, params->rideFile, params->rideItem, false,
false, &editedRideFiles), params->script);
// finish up commands on edited rides

View File

@@ -11,6 +11,7 @@ struct FixPyRunParams
{
Context *context;
RideFile *rideFile;
RideItem *rideItem;
QString script;
};
@@ -19,7 +20,7 @@ class FixPyRunner : public QObject
Q_OBJECT
public:
FixPyRunner(Context *context = nullptr, RideFile *rideFile = nullptr, bool useNewThread = true);
FixPyRunner(Context *context = nullptr, RideFile *rideFile = nullptr, RideItem *rideItem = nullptr, bool useNewThread = true);
int run(QString source, QString scriptKey, QString &errText);
static void execScript(FixPyRunParams *params);
@@ -27,6 +28,7 @@ public:
private:
Context *context;
RideFile *rideFile;
RideItem *rideItem;
bool useNewThread;
};

View File

@@ -43,9 +43,9 @@ class ScriptContext {
interactiveShell(interactiveShell), readOnly(true), editedRideFiles(NULL) {}
// read/write ctor
ScriptContext(Context *context, RideFile *rideFile, bool interactiveShell,
ScriptContext(Context *context, RideFile *rideFile, RideItem *item, bool interactiveShell,
bool readOnly, QList<RideFile *> *editedRideFiles)
: context(context), item(NULL), rideFile(rideFile), metrics(NULL), spec(),
: context(context), item(item), rideFile(rideFile), metrics(NULL), spec(),
interactiveShell(interactiveShell), readOnly(readOnly), editedRideFiles(editedRideFiles) {}
// default ctor

View File

@@ -1630,11 +1630,7 @@ Bindings::setTag(QString name, QString value, PyObject *activity) const
Context *context = python->contexts.value(threadid()).context;
if (context == nullptr) return false;
RideItem* m = fromDateTime(activity);
if (m == nullptr) m = const_cast<RideItem*>(context->currentRideItem());
if (m == nullptr) return false;
RideFile *f = m->ride();
RideFile *f = selectRideFile(activity);
if (f == nullptr) return false;
name = name.replace("_"," ");
@@ -1656,10 +1652,16 @@ Bindings::setTag(QString name, QString value, PyObject *activity) const
f->setTag(name, value);
}
// rideFile is now dirty!
m->setDirty(true);
// get refresh done, coz overrides state has changed
m->notifyRideMetadataChanged();
// Notify changes if activity is already in rideCache
RideItem* m = fromDateTime(activity);
if (m == nullptr) m = python->contexts.value(threadid()).item;
if (m == nullptr) m = context->rideItem();
if (m && m->dateTime == f->startTime()) {
// rideFile is now dirty!
m->setDirty(true);
// get refresh done, coz overrides state has changed
m->notifyRideMetadataChanged();
}
return true;
}
@@ -1673,23 +1675,25 @@ Bindings::delTag(QString name, PyObject *activity) const
Context *context = python->contexts.value(threadid()).context;
if (context == nullptr) return false;
RideItem* m = fromDateTime(activity);
if (m == nullptr) m = const_cast<RideItem*>(context->currentRideItem());
if (m == nullptr) return false;
RideFile *f = m->ride();
RideFile *f = selectRideFile(activity);
if (f == nullptr) return false;
RideItem* m = fromDateTime(activity);
if (m == nullptr) m = python->contexts.value(threadid()).item;
if (m == nullptr) m = context->rideItem();
name = name.replace("_"," ");
if (GlobalContext::context()->specialFields.isMetric(name)) {
if (f->metricOverrides.remove(GlobalContext::context()->specialFields.metricSymbol(name))) {
// rideFile is now dirty!
m->setDirty(true);
// get refresh done, coz overrides state has changed
m->notifyRideMetadataChanged();
// Notify changes if activity is already in rideCache
if (m && m->dateTime == f->startTime()) {
// rideFile is now dirty!
m->setDirty(true);
// get refresh done, coz overrides state has changed
m->notifyRideMetadataChanged();
}
return true;
}
return false;
@@ -1698,11 +1702,13 @@ Bindings::delTag(QString name, PyObject *activity) const
if (f->removeTag(name)) {
// rideFile is now dirty!
m->setDirty(true);
// get refresh done, coz overrides state has changed
m->notifyRideMetadataChanged();
// Notify changes if activity is already in rideCache
if (m && m->dateTime == f->startTime()) {
// rideFile is now dirty!
m->setDirty(true);
// get refresh done, coz overrides state has changed
m->notifyRideMetadataChanged();
}
return true;
}
return false;
@@ -1715,15 +1721,31 @@ Bindings::hasTag(QString name, PyObject *activity) const
Context *context = python->contexts.value(threadid()).context;
if (context == nullptr) return false;
RideItem* m = fromDateTime(activity);
if (m == nullptr) m = const_cast<RideItem*>(context->currentRideItem());
if (m == nullptr) return false;
RideFile *f = selectRideFile(activity);
if (f == nullptr) return false;
name = name.replace("_"," ");
if (GlobalContext::context()->specialFields.isMetric(name)) {
return m->overrides_.contains(GlobalContext::context()->specialFields.metricSymbol(name));
return f->metricOverrides.contains(GlobalContext::context()->specialFields.metricSymbol(name));
} else {
return m->hasText(name);
return f->tags().contains(name);
}
}
QString
Bindings::getTag(QString name, PyObject *activity) const
{
Context *context = python->contexts.value(threadid()).context;
if (context == nullptr) return QString();
RideFile *f = selectRideFile(activity);
if (f == nullptr) return QString();
name = name.replace("_"," ");
if (GlobalContext::context()->specialFields.isMetric(name)) {
return f->metricOverrides[GlobalContext::context()->specialFields.metricSymbol(name)]["value"];
} else {
return f->getTag(name, "");
}
}

View File

@@ -119,6 +119,7 @@ class Bindings {
bool setTag(QString name, QString value, PyObject *activity = NULL) const;
bool delTag(QString name, PyObject *activity = NULL) const;
bool hasTag(QString name, PyObject *activity = NULL) const;
QString getTag(QString name, PyObject *activity = NULL) const;
// working with charts
bool configChart(QString title, int type, bool animate, int pos, bool stack, int orientation) const;

View File

@@ -390,6 +390,7 @@ public:
bool setTag(QString name, QString value, PyObject *activity = NULL) const;
bool delTag(QString name, PyObject *activity = NULL) const;
bool hasTag(QString name, PyObject *activity = NULL) const;
QString getTag(QString name, PyObject *activity = NULL) const;
// working with qt charts
bool configChart(QString title, int type, bool animate, int legpos, bool stack, int orientation) const;

View File

@@ -139,76 +139,78 @@
#define sipName_labels &sipStrings_goldencheetah[410]
#define sipNameNr_legpos 688
#define sipName_legpos &sipStrings_goldencheetah[688]
#define sipNameNr_hasTag 695
#define sipName_hasTag &sipStrings_goldencheetah[695]
#define sipNameNr_delTag 702
#define sipName_delTag &sipStrings_goldencheetah[702]
#define sipNameNr_setTag 709
#define sipName_setTag &sipStrings_goldencheetah[709]
#define sipNameNr_metric 716
#define sipName_metric &sipStrings_goldencheetah[716]
#define sipNameNr_getTag 695
#define sipName_getTag &sipStrings_goldencheetah[695]
#define sipNameNr_hasTag 702
#define sipName_hasTag &sipStrings_goldencheetah[702]
#define sipNameNr_delTag 709
#define sipName_delTag &sipStrings_goldencheetah[709]
#define sipNameNr_setTag 716
#define sipName_setTag &sipStrings_goldencheetah[716]
#define sipNameNr_metric 723
#define sipName_metric &sipStrings_goldencheetah[723]
#define sipNameNr_series 565
#define sipName_series &sipStrings_goldencheetah[565]
#define sipNameNr_season 723
#define sipName_season &sipStrings_goldencheetah[723]
#define sipNameNr_filter 730
#define sipName_filter &sipStrings_goldencheetah[730]
#define sipNameNr_result 737
#define sipName_result &sipStrings_goldencheetah[737]
#define sipNameNr_remove 744
#define sipName_remove &sipStrings_goldencheetah[744]
#define sipNameNr_append 751
#define sipName_append &sipStrings_goldencheetah[751]
#define sipNameNr_align 758
#define sipName_align &sipStrings_goldencheetah[758]
#define sipNameNr_season 730
#define sipName_season &sipStrings_goldencheetah[730]
#define sipNameNr_filter 737
#define sipName_filter &sipStrings_goldencheetah[737]
#define sipNameNr_result 744
#define sipName_result &sipStrings_goldencheetah[744]
#define sipNameNr_remove 751
#define sipName_remove &sipStrings_goldencheetah[751]
#define sipNameNr_append 758
#define sipName_append &sipStrings_goldencheetah[758]
#define sipNameNr_align 765
#define sipName_align &sipStrings_goldencheetah[765]
#define sipNameNr_color 400
#define sipName_color &sipStrings_goldencheetah[400]
#define sipNameNr_yname 764
#define sipName_yname &sipStrings_goldencheetah[764]
#define sipNameNr_xname 770
#define sipName_xname &sipStrings_goldencheetah[770]
#define sipNameNr_stack 776
#define sipName_stack &sipStrings_goldencheetah[776]
#define sipNameNr_title 782
#define sipName_title &sipStrings_goldencheetah[782]
#define sipNameNr_index 788
#define sipName_index &sipStrings_goldencheetah[788]
#define sipNameNr_group 794
#define sipName_group &sipStrings_goldencheetah[794]
#define sipNameNr_xdata 800
#define sipName_xdata &sipStrings_goldencheetah[800]
#define sipNameNr_sport 806
#define sipName_sport &sipStrings_goldencheetah[806]
#define sipNameNr_value 812
#define sipName_value &sipStrings_goldencheetah[812]
#define sipNameNr_build 818
#define sipName_build &sipStrings_goldencheetah[818]
#define sipNameNr_fill 824
#define sipName_fill &sipStrings_goldencheetah[824]
#define sipNameNr_line 829
#define sipName_line &sipStrings_goldencheetah[829]
#define sipNameNr_join 834
#define sipName_join &sipStrings_goldencheetah[834]
#define sipNameNr_name 765
#define sipName_name &sipStrings_goldencheetah[765]
#define sipNameNr_type 839
#define sipName_type &sipStrings_goldencheetah[839]
#define sipNameNr_date 844
#define sipName_date &sipStrings_goldencheetah[844]
#define sipNameNr_log 849
#define sipName_log &sipStrings_goldencheetah[849]
#define sipNameNr_yname 771
#define sipName_yname &sipStrings_goldencheetah[771]
#define sipNameNr_xname 777
#define sipName_xname &sipStrings_goldencheetah[777]
#define sipNameNr_stack 783
#define sipName_stack &sipStrings_goldencheetah[783]
#define sipNameNr_title 789
#define sipName_title &sipStrings_goldencheetah[789]
#define sipNameNr_index 795
#define sipName_index &sipStrings_goldencheetah[795]
#define sipNameNr_group 801
#define sipName_group &sipStrings_goldencheetah[801]
#define sipNameNr_xdata 807
#define sipName_xdata &sipStrings_goldencheetah[807]
#define sipNameNr_sport 813
#define sipName_sport &sipStrings_goldencheetah[813]
#define sipNameNr_value 819
#define sipName_value &sipStrings_goldencheetah[819]
#define sipNameNr_build 825
#define sipName_build &sipStrings_goldencheetah[825]
#define sipNameNr_fill 831
#define sipName_fill &sipStrings_goldencheetah[831]
#define sipNameNr_line 836
#define sipName_line &sipStrings_goldencheetah[836]
#define sipNameNr_join 841
#define sipName_join &sipStrings_goldencheetah[841]
#define sipNameNr_name 772
#define sipName_name &sipStrings_goldencheetah[772]
#define sipNameNr_type 846
#define sipName_type &sipStrings_goldencheetah[846]
#define sipNameNr_date 851
#define sipName_date &sipStrings_goldencheetah[851]
#define sipNameNr_log 856
#define sipName_log &sipStrings_goldencheetah[856]
#define sipNameNr_max 120
#define sipName_max &sipStrings_goldencheetah[120]
#define sipNameNr_min 853
#define sipName_min &sipStrings_goldencheetah[853]
#define sipNameNr_all 857
#define sipName_all &sipStrings_goldencheetah[857]
#define sipNameNr_url 861
#define sipName_url &sipStrings_goldencheetah[861]
#define sipNameNr_s2 865
#define sipName_s2 &sipStrings_goldencheetah[865]
#define sipNameNr_s1 868
#define sipName_s1 &sipStrings_goldencheetah[868]
#define sipNameNr_min 860
#define sipName_min &sipStrings_goldencheetah[860]
#define sipNameNr_all 864
#define sipName_all &sipStrings_goldencheetah[864]
#define sipNameNr_url 868
#define sipName_url &sipStrings_goldencheetah[868]
#define sipNameNr_s2 872
#define sipName_s2 &sipStrings_goldencheetah[872]
#define sipNameNr_s1 875
#define sipName_s1 &sipStrings_goldencheetah[875]
#define sipMalloc sipAPI_goldencheetah->api_malloc
#define sipFree sipAPI_goldencheetah->api_free

View File

@@ -1182,6 +1182,40 @@ static PyObject *meth_Bindings_hasTag(PyObject *sipSelf, PyObject *sipArgs, PyOb
}
extern "C" {static PyObject *meth_Bindings_getTag(PyObject *, PyObject *, PyObject *);}
static PyObject *meth_Bindings_getTag(PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds)
{
PyObject *sipParseErr = NULL;
{
::QString* a0;
int a0State = 0;
PyObject * a1 = 0;
const ::Bindings *sipCpp;
static const char *sipKwdList[] = {
sipName_name,
sipName_activity,
};
if (sipParseKwdArgs(&sipParseErr, sipArgs, sipKwds, sipKwdList, NULL, "BJ1|P0", &sipSelf, sipType_Bindings, &sipCpp, sipType_QString,&a0, &a0State, &a1))
{
::QString*sipRes;
sipRes = new ::QString(sipCpp->getTag(*a0,a1));
sipReleaseType(a0,sipType_QString,a0State);
return sipConvertFromNewType(sipRes,sipType_QString,NULL);
}
}
/* Raise an exception if the arguments couldn't be parsed. */
sipNoMethod(sipParseErr, sipName_Bindings, sipName_getTag, NULL);
return NULL;
}
extern "C" {static PyObject *meth_Bindings_configChart(PyObject *, PyObject *, PyObject *);}
static PyObject *meth_Bindings_configChart(PyObject *sipSelf, PyObject *sipArgs, PyObject *sipKwds)
{
@@ -1482,6 +1516,7 @@ static PyMethodDef methods_Bindings[] = {
{SIP_MLNAME_CAST(sipName_delTag), (PyCFunction)meth_Bindings_delTag, METH_VARARGS|METH_KEYWORDS, NULL},
{SIP_MLNAME_CAST(sipName_deleteActivitySample), (PyCFunction)meth_Bindings_deleteActivitySample, METH_VARARGS|METH_KEYWORDS, NULL},
{SIP_MLNAME_CAST(sipName_deleteSeries), (PyCFunction)meth_Bindings_deleteSeries, METH_VARARGS|METH_KEYWORDS, NULL},
{SIP_MLNAME_CAST(sipName_getTag), (PyCFunction)meth_Bindings_getTag, METH_VARARGS|METH_KEYWORDS, NULL},
{SIP_MLNAME_CAST(sipName_hasTag), (PyCFunction)meth_Bindings_hasTag, METH_VARARGS|METH_KEYWORDS, NULL},
{SIP_MLNAME_CAST(sipName_intervalType), (PyCFunction)meth_Bindings_intervalType, METH_VARARGS|METH_KEYWORDS, NULL},
{SIP_MLNAME_CAST(sipName_metrics), (PyCFunction)meth_Bindings_metrics, METH_VARARGS|METH_KEYWORDS, NULL},
@@ -1522,7 +1557,7 @@ sipClassTypeDef sipTypeDef_goldencheetah_Bindings = {
{
sipNameNr_Bindings,
{0, 0, 1},
39, methods_Bindings,
40, methods_Bindings,
0, 0,
0, 0,
{0, 0, 0, 0, 0, 0, 0, 0, 0, 0},

View File

@@ -80,6 +80,7 @@ const char sipStrings_goldencheetah[] = {
's', 'y', 'm', 'b', 'o', 'l', 0,
'c', 'o', 'l', 'o', 'r', 's', 0,
'l', 'e', 'g', 'p', 'o', 's', 0,
'g', 'e', 't', 'T', 'a', 'g', 0,
'h', 'a', 's', 'T', 'a', 'g', 0,
'd', 'e', 'l', 'T', 'a', 'g', 0,
's', 'e', 't', 'T', 'a', 'g', 0,