Einzelnen Beitrag anzeigen

Benutzerbild von himitsu
himitsu

Registriert seit: 11. Okt 2003
Ort: Elbflorenz
43.168 Beiträge
 
Delphi 12 Athens
 
#10

AW: Deployment von Python4Delphi Programm

  Alt 9. Okt 2023, 15:26
Nee, wir nutzen immer das selbstmitgebrachte Python.

Da wir nicht kontrollieren können ob und welche Python-Version auf Kundensystemen installiert sind,
haben wir so unser eigenes Python, mit welchem wir bei uns auch Testen konnten.
Wäre es z.B. als 64 Bit installiert, dann kann es im Delphi unter 32 Bit nicht benutzt werden und es knallt.

Sowie eben (noch) die etwas ältere Version, welche aber noch mit den meisten Scripten kompatibel ist. (sie Scripte sind bei uns in der Datenbank gespeichert, mit einer Synchronisation von/zu uns)



Ansonsten nutzen wir einen Fork vom https://github.com/pyscripter/python4delphi
da sind die Änderungen für "unsere" automatische Versions-Suche im Embedded-Verzeichnis.
Und wir könnten gezielt die Updates übernehmen (oder auch nicht)

PS: Da der Entwickler bösartig AnsiString für seine Parameter benutzt hat, die das Python aber eigentlich mit UTF-8 nutzt,
mußte ich da bissl rumtricksen, damit Unicode/Umlaute nicht kaputt gehn.

Auszug:
Delphi-Quellcode:
type
  TXXXXXXPython = class(TObject)
  private
    FPythonEngine: TPythonEngine;
    FPythonInputOutput: TPythonInputOutput;
    FPythonModule: TPythonModule;
    FPythonDelphiWrapper: TPyDelphiWrapper;
    FPythonDelphiVar: TPythonDelphiVar;
    FPythonOutput: RawByteString;
    FInitScript: String;
    FUseInstalledPython: Boolean;
    FDLLFilename: string; //leer sucht installiertes Python (Achtung: muß 32 Bit sein, egal ob Installiert oder Embedded)
    //
    function GetPythonOutput: string;
    function GetParamCount(Args: PPyObject): Integer;
    function GetParam(Args: PPyObject; Index: Integer; IsOptional: Boolean=False): Variant;
    function CreateResult(Value: Variant): PPyObject;
    function GetVariable(Name: string): Variant; // [DCC Hinweis] H2219 Das private-Symbol 'GetVariable' wurde deklariert, aber nie verwendet -> aktuell auskommentiert, siehe TXXXXXXPython.Engine_Initialize
    procedure SetVariable(Name: string; Value: Variant);
    //
    procedure InputOutputSendData(Sender: TObject; const Data: AnsiString);
    procedure ExecuteInitScript;

    procedure FindPythonDLL;

  public class var
    _GetValueFromEdit: function(FrameOrForm: TComponent; EditName: string; UseParamsIfEmpty: Boolean=False): Variant; // XXXXXXUtils.GetValueFromEdit für Py_GetValueFromEdit
    _FillQueryParamsWithFormValues: procedure(Query: TCimQuery2; _Self: TComponent; DebugInfo: string; AddPercent: Boolean=False; pOverride: Boolean=False); // XXXXXXUtils._FillQueryParamsWithFormValues für Py_DBExecute/Py_DBSelect
  public
    RuntimeForm: TForm; // TfrmToDesign
    OwnerModul: TForm; // TModulForm usw. (für GetValueFromEdit und FillQueryParamsWithFormValues)

    constructor Create;
    destructor Destroy; override;

    procedure Engine_Initialize;
    procedure Engine_Finalize(doLog: Boolean = True);
    //
    function CheckExecute(Script: string; RaiseError: Boolean = True): Boolean;
    function Execute (Script: string): string;
    //
    function CheckEval (Script: string; RaiseError: Boolean = True): Boolean;
    function Eval (Script: string): Variant;
    //
    property InitScript: string read FInitScript write FInitScript;
    property Output: string read GetPythonOutput;

    property UseInstalledPython: Boolean read FUseInstalledPython write FUseInstalledPython;
    property Engine: TPythonEngine read FPythonEngine;

  private
    // Beschreibng der Parameter
    // siehe in Implementation als Kommentar an den Funktionen
    // bzw. siehe TXXXXXXPython.Engine_Initialize als Kommentar an Python (TODO: schauen wie das ins CodeComplete des SynEdit gelangt)

    function Method_Test (PSelf, Args: PPyObject): PPyObject; cdecl;

    function Py_GetValueFromEdit(PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_GetSetting (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_SetSetting (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_GetBoolSetting (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_SetBoolSetting (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_GetIntSetting (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_SetIntSetting (PSelf, Args: PPyObject): PPyObject; cdecl;

    function Py_DBExecute (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_DBSelect (PSelf, Args: PPyObject): PPyObject; cdecl;

    function Py_DMSFileByDokID (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_DMSFileByRecNo (PSelf, Args: PPyObject): PPyObject; cdecl;
    function Py_DMSFileImport (PSelf, Args: PPyObject): PPyObject; cdecl;
  end;

function EscapePythonString(S: string): string;

procedure TXXXXXXPython.Engine_Initialize;
begin
  Engine_Finalize(False);

  FPythonOutput := '';

  FPythonInputOutput := TPythonInputOutput.Create(nil);
  FPythonInputOutput.UnicodeIO := False;
  FPythonInputOutput.OnSendData := InputOutputSendData;

  // Non-RawOutput könnte zu Problemen führen,
  // da 'unvollständige' Implementierung
  FPythonInputOutput.RawOutput := True;
  FPythonInputOutput.DelayWrites := False;

  //https://github.com/pyscripter/python4delphi/wiki/FindingPython
  FPythonEngine := TPythonEngine.Create(nil);
  FPythonEngine.AutoLoad := False;
  FPythonEngine.FatalAbort := False;
  FPythonEngine.FatalMsgDlg := False;

  FPythonEngine.UseLastKnownVersion := UseInstalledPython;
  //
  if FDLLFilename <> 'then begin
    FPythonEngine.DllName := ExtractFileName(FDLLFilename);
    FPythonEngine.DllPath := ExtractFilePath(FDLLFilename);
    FPythonEngine.SetPythonHome(FPythonEngine.DllPath);
    //FPythonEngine.RegVersion := '3.9';
  end;
  //
  FPythonEngine.IO := FPythonInputOutput;

  FPythonModule := TPythonModule.Create(nil);
  FPythonModule.Engine := FPythonEngine;
  FPythonModule.ModuleName := 'XXXXXX';
  FPythonModule.DocString.Text := 'our XXXXXX-Modul: Functions and Variables inside XXXXXX-Client';

  FPythonDelphiWrapper := TPyDelphiWrapper.Create(nil);
  FPythonDelphiWrapper.Engine := FPythonEngine;
  FPythonDelphiWrapper.Module := FPythonModule;

  // TODO: Ich weiß nicht warum, aber bei André funktioniert es, und bei mir nicht,
  // weder im Embedded (DLLName) noch im installierten Python (UseLastKnownVersion).
  // -> 'VariableXyz' object has no attribute 'Value'
  // DemoCode im Scipt-Memo auskommentert (#)

  //erledigt! gefixt: https://en.delphipraxis.net/topic/4323-pythondelphivar-thows-attributeerror-that-object-has-no-attribute-value-python-391-32bit/
  FPythonDelphiVar := TPythonDelphiVar.Create(nil);
  FPythonDelphiVar.Engine := FPythonEngine;
  FPythonDelphiVar.VarName := 'VariableXyz';
  //FPythonDelphiVar.Module := '__main__'; // Python-MainModule: Variable global "VariableXyz.Value" oder als "__main__.VariableXyz.Value"
 { FPythonDelphiVar.OnChange        := PythonDelphiVarOnChange;
  FPythonDelphiVar.OnGetData        := PythonDelphiVarOnGetData;
  FPythonDelphiVar.OnSetData        := PythonDelphiVarOnSetData; }


  FPythonEngine.LoadDll;
  if not(FPythonEngine.IsHandleValid) then
    raise Exception.Create('PythonEngine.LoadDll: ' + SysErrorMessage(GetLastError));

  if not(FPythonEngine.Initialized) then begin
    TPythonEngineAccess(FPythonEngine).Initialize;
    if not(FPythonEngine.Initialized) then
      raise Exception.Create('PythonEngine.Initialize: ' + SysErrorMessage(GetLastError));
  end;

  {$REGION 'PythonModule.OnInitialization'}

  FPythonModule.AddDelphiMethod('Method_Test', Method_Test, 'Method_Test(a, b)');

  FPythonModule.AddDelphiMethod('GetValueFromEdit', Py_GetValueFromEdit, 'GetValueFromEdit(EditName): Variant');
//FPythonModule.AddDelphiMethod('GetControlValue', Py_GetControlValue, 'GetControlValue(EditName): Variant');
//FPythonModule.AddDelphiMethod('SetControlValue', Py_SetControlValue, 'SetControlValue(EditName, Value)');

  FPythonModule.AddDelphiMethod('GetSetting', Py_GetSetting, 'GetSetting(Name): String');
  FPythonModule.AddDelphiMethod('SetSetting', Py_SetSetting, 'SetSetting(Name, StrValue)');
  FPythonModule.AddDelphiMethod('GetBoolSetting', Py_GetBoolSetting, 'GetBoolSetting(Name): Boolean');
  FPythonModule.AddDelphiMethod('SetBoolSetting', Py_SetBoolSetting, 'SetBoolSetting(Name, BoolValue)');
  FPythonModule.AddDelphiMethod('GetIntSetting', Py_GetIntSetting, 'GetIntSetting(Name): Integer');
  FPythonModule.AddDelphiMethod('SetIntSetting', Py_SetIntSetting, 'SetIntSetting(Name, IntValue)');

  FPythonModule.AddDelphiMethod('DBExecute', Py_DBExecute, 'DBExecute(InsertOrUpdateStatement)');
  FPythonModule.AddDelphiMethod('DBSelect', Py_DBSelect, 'DBSelect(SelectStatement): FirstStringBooleanOrInteger');

  FPythonModule.AddDelphiMethod('DMSFileByDokID', Py_DMSFileByDokID, 'DMSFileByDokID(pd_id): String');
  FPythonModule.AddDelphiMethod('DMSFileByRecNo', Py_DMSFileByRecNo, 'DMSFileByRecNo(TableName, RecId, [DokType], [OnlyStandard]): String');
  FPythonModule.AddDelphiMethod('DMSFileImport', Py_DMSFileImport, 'DMSFileImport(FileName, TableName, DBRID, [DokType], [DokIdent], [Caption]): Integer');

  FPythonModule.Initialize;
  FPythonDelphiWrapper.Initialize;

  if Assigned(FPythonDelphiVar) then
    FPythonDelphiVar.Initialize;

  //FPythonDelphiVar.Value := 20; //kann man vom Code setzen

  FPythonModule.SetVarFromVariant('Var123', 123456);
  SetVariable('Var456', 987654);

  //v := GetVariable('Var456'); //es geht
  //v := GetVariable('VariableXyz'); //es geht nicht

  FPythonEngine.PyObject_SetAttrString(FPythonEngine.GetMainModule, 'Var456', FPythonEngine.VariantAsPyObject(987654));

  {$ENDREGION}

  //FPythonEngine.ExecString('weiterer Python-Code für Initialisierung'); // Standard-Module importieren, Variablen setzen, ...
  //DoLogEvent('Python-Init: %dms', [Integer(TThread.GetTickCount - PythonRuntime)]);
end;

function TXXXXXXPython.Execute(Script: string): string;
var
  Temp: RawByteString;
begin
  Engine_Initialize;
  try
    ExecuteInitScript;
    Temp := UTF8Encode(Script);
    SetCodePage(Temp, CP_ACP, False); // damit Delphi das UTF-8 nicht nach ANSI konvertiert, da die Parameter als ANSI deklariert wurden
    FPythonEngine.ExecString(AnsiString(Temp)); //FPythonEngine.ExecString(FPythonEngine.EncodeString(Script)); -> EncodeString ist defekt
    Result := Self.Output;
  finally
    Engine_Finalize;
  end;
end;
Delphi-Quellcode:
  Python := TProdatPython.Create;
  try
    //if not Python.CheckExecute(Script, False) then raise ....
    edPythonResult.Text := Python.Execute(Script);
  finally
    Python.Free;
  end;
Delphi-Quellcode:
  TDM1.ScreenCursorSwitchToHoureGlass(True);
  Python := TXXXXXPython.Create;
  try
    {$IFDEF EUREKALOG}  // Eurekalog deaktiveren > Grund: 5 Fehler und das war es
    ELActive := IsEurekaLogActive;
    SetEurekaLogState(False);
    {$ENDIF}

    Python.RuntimeForm := nil;
    Python.OwnerModul := FindModule(TModulForm, True);
    ...

    if Python.CheckExecute(Script, False) then
      try
        {Res :=} Python.Execute(Script);
      finally
        // auch bei einem Fehler den bereits vorhandenen Output anzeigen (inkl. Fehlerposition, Stacktrace und Fehlermeldung)
        edPythonResult.Text := TrimRight(TRegEx.Replace(Python.Output, '^( *\r?\n)*', '', [roSingleLine])); // das Resultat des Scriptes von StdOut
      end
    else
      edPythonResult.Text := Format('Error: Skript kann nicht ausgeführt werden%s%s', [sLineBreak + sLineBreak, Python.Output]);
  finally
    {$IFDEF EUREKALOG}
    SetEurekaLogState(ELActive);
    {$ENDIF}
    Python.Free;
    TDM1.ScreenCursorSwitchToHoureGlass(False);
  end;
Garbage Collector ... Delphianer erzeugen keinen Müll, also brauchen sie auch keinen Müllsucher.
my Delphi wish list : BugReports/FeatureRequests

Geändert von himitsu ( 9. Okt 2023 um 15:44 Uhr)
  Mit Zitat antworten Zitat