Deployment Issues
Home Up

 

Related 'Python for Delphi' Links on this site:

bullettutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bulletcode - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullettutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bulletdiscussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bulletanimated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bulletexample and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page

Deploying Python for Delphi applications

When deploying delphi application that uses the python for delphi components, you need to supply python21.dll  (or whatever version you are working with) plus any .py files.  Simple advice, but the devil is in the detail - as I found out!! 

Nevertheless, I managed to successfully deploy a delphi app to hundreds of users and they didn't even know there was python inside !

-Andy Bulka
February 2002.

Quick tips

Tips:

  1. Supply a version of python2x.dll with your delphi exe.  Put it in the same folder or in the windows system folder.
  2. Supply any python classes/module files (.py or .pyc) plus any dlls (.pyd) that is used.
  3. Ensure your python path is set to point to your .py files.  If using Delphi 6 & Python 21 or higher, you can use the advanced technique below of using a special API call provided by the python for delphi components.  If using earlier versions of Delphi (5 and below) and Python (2.0 and below) then use the simpler more direct technique of talking to the python interpreter via text script.
  4. Beware that the DOS environment variable PYTHONPATH may intefere with your path, if it points to a different version of the python libraries than the one you supply with your exe. Similarly, an existing registry entry for python may also intefere with your desired path.  For a discussion on this click here.
  5. You may need to set the lastKnownDll boolean property of your python engine component to false, so that the python for delphi components actively search for the dll specified in the UseDLL property of your python engine component, rather than using the last successfully found python dll. 
    UPDATE: Apparently in more recent versions of the python for delphi components you don't need to set it since it will load the version you have compiled for.
  6. When compiling for specific versions of the python dll, you need to create a define in the delphi project options.  Here is a viewlet demo of this technique that you can watch.  Here is a discussion on this issue.

Does the python for delphi demo25 rely on python 2.1?

Andy: It seems that  Demo25 does indeed rely on both delphi 6 and python 2.1

Morgan:  Demo25 relies on Python2.1 indirectly, as it calls the function VarIsInstanceOf, which relies on version 2.1, but if you comment out this call (and VarIsSubclassOf), it should work without any problem!

Andy: It seems that to use, say python21.dll on a system that only has the python2.0 development environment installed, one need not fully install python 2.1, all you need to do is put python21.dll into winnt\system32 or  into the folder you are developing the delphi application in.

Morgan: Yes, that's right!

Andy:  You can put the Lib folder anywhere you like, and have Delphi point to it e.g. SysModule.path.append('..blah..\Lib'). 

Morgan:  That's a solution, but the Python Dll gets its path from the Windows registry:

HKEY_LOCAL_MACHINE\Software\Python\PythonCore\2.1\PythonPath        

Of course, each version of Python has its own entry.

Andys Quick Tip

When you supply your python classes with your delphi app, they will probably use some standard python classes.  You will need to supply these .py files too.  Either ship/supply the whole Lib folder or supply just what actually has been used.  How to determine this?  I used trial and error, - leveraging Py2Exe style technology would be a better solution, so that a tool identifies for you the dependant files so you know which to ship.

NOTE: If there are any dependant .pyd files (actually DLL's) then these need to be shipped too.

NOTE:  When supplying the python classes with your delphi exe, you can put the Lib folder anywhere you like, and point to it using the python list variant SysModule.path (accessible and modifiable from Delphi).

The current folder of the Delphi EXE is automatically added to the SysModule.path (by PythonEngine, I think), so you can put Strings.py or whatever in the current folder of your Delphi application, if you like, though its probably better to follow the python standards and keep this stuff in a Lib folder.

The 'DllName' property of the pythonEngine

Andy:  Changing the DllName property of the pythonEngine doesn't seem to affect things - even if I put python15.dll in the demo25 example, things still run ok! So how do I control / verify which python dll is being used and accessed? What is the DllName property really doing, if anything?

Morgan:  You forgot to disable the UseLastKnownVersion property in the TPythonEngine!!!  If it's true, it tries each version of Python, starting from the greatest version number, and uses what it finds. If nothing is found, then DllName is used!

So, if you have several versions of Python installed in your system, you can't rely on this property, and you should set it to False and define the DllName corresponding to the expected Python version, but don't forget to specify the properties APIVersion and RegVersion. If you have a doubt, look at the beginning of PythonEngine.pas, that defines all known values in an array (PYTHON_KNOWN_VERSIONS).

[ Note: You don't even need to set UseLastKnownVersion to false now, because the latest strategy is to find first the dll for which your code was compiled, and then try to upgrade if it's missing. ]

Compiling for different versions of python

Running python 2.2 from delphi 6

Andy:  I am getting an error when I start my dephi app 'Error 127: could not map symbol "PyUnicode_FromWideChar"  This is the first time I've tried running a python 2.2.1 app. I was using 2.1 ok, then to change to 2.2 I changed the AtomEngine component properties:
> UseLastKnownVersion false
> RegVersion 2.2
> DllName python22.dll

Morgan:  You don't need to do this for selecting a Python version! Keep UseLastKnownVersion to True, and adjust your defines:
{$DEFINE PYTHON22}
UseLastKnownVersion will first try to find the dll version you requested (2.2), and if this version is flagged compatible with upgrades, then it will
look for installed upgrades. That way it ensures that it will always try to get the dll for which your code was compiled with.

Andy: Ok it works now. :-) These two steps seem to be the key to getting it working and all I have to do. When I turn UseLastKnownVersion to false, I get into trouble I was having.

Furthermore, playing with
RegVersion 2.1 or 2.2
DllName python22.dll
doesn't seem to help either. Perhaps these properties should be retired as misleading? And perhaps UseLastKnownVersion should be retired and always set to true?

Andy's Quick Tip                    

To compile for python 2.2 just 

Keep 

UseLastKnownVersion to True

on your PythonEngine component and adjust your define:

{$DEFINE PYTHON22}

which you can do in the project options of Delphi 6.  
Just add PYTHON22 to the dialog box e.g.

 

Cleaning up my python path from within Delphi

Andy Bulka

I want to ship (with my .exe) a few python units in the 'MyLib' folder underneath where my delphi .EXE lives. The destination machine doesn't have python, so I need to supply all the relevant classes (by the way are there any dependency utils for ensuring I identify all the dependent class files?)

Anyway, so I want to set the sys.path appropriately to make sure my python class files are found. Of course I could put all the classes in the same folder as my EXE and this would solve the problem, cos I know the current folder of my EXE is automatically added to the sys.path.

But I want to keep my python classes in the 'MyLib' folder underneath where my delphi .EXE lives.

I know that when a python engine starts up inside delphi, the sys.path is set via the Windows registry:
HKEY_LOCAL_MACHINE\Software\Python\PythonCore\2.1\PythonPath

If I'm using Delphi 6 and python 2.1 then I can use the following cool delphi code to trim the path and set it to something I want.

SysModule.path := NewPythonList;
SysModule.path.append(ExtractFilePath( Application.ExeName ) + 'MyLib' );
SysModule.path.append('some other folder');        

NOTE:  Above relies on Jan 2002 version of python for Delphi components.
NOTE: Above will fail since the paths returned by Delphi have single slashes and python needs wither unix slashes or \\ slashes. See here for an algorithm to handle this.

MY QUESTION: Under delphi 4, I don't have access to the variant unit and the nice SysModule variable. Is the only alternative to send some python script text at the python interpreter to set sys.path ? e.g.

Memo1.Lines.Text := 'import sys' ;
PythonEngine1.ExecStrings( Memo1.Lines );        
apath := ExtractFilePath( Application.ExeName ) + 'MyLib' ;
Memo1.Lines.Text := 'sys.path.append("' + apath + '")' ;
PythonEngine1.ExecStrings( Memo1.Lines );        

**** above code works, ok by the way

Andy tip: Algorithm to clear out your syspath and set it to exactly what you want.  Will work with any version of delphi or python:
function EnsurePathHasDoubleSlashes( path : string ):string;
begin
	result := StringReplace(path, '\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
	result := StringReplace(result, '\\\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
end;     
// Sets the path to the app EXE path plus the 'path' underneath
// the current EXE path. Returns false if 'path' does not exist.     
function PyClearSysPathAndSetSimple(folder : string; engine: TAtomPythonEngine) : boolean;
var
	cmds, currExePathInclSlash, libPath, currPath : string;
begin
	result := false;
	currExePathInclSlash := ExtractFilePath( Application.ExeName );                    
	libPath := currExePathInclSlash + folder;
	currPath := copy(currExePathInclSlash,1,length(currExePathInclSlash)-1);                    
	if not DirectoryExists( libPath ) then begin
		showmessage('no DirectoryExists( libPath ) !!! ' + libPath);
		exit;
	end;                    
	libPath := EnsurePathHasDoubleSlashes(libPath);
	currPath := EnsurePathHasDoubleSlashes(currPath);                    
	cmds := 'import sys' + #13 +
	'while sys.path:' + #13 +
	' sys.path.pop()'+ #13 +
	'sys.path.append("' + currPath + '")' + #13 +
	'sys.path.append("' + libPath + '")' + #13 +
	'';
	PyExe(cmds,engine);

	result := true;
end;                    

Morgans' reply:

You have another solution by using Python APIs:

Here are some sys APIs:

function PySys_GetObject(s:PChar):PPyObject; cdecl;
function PySys_SetObject(s:PChar;ob:PPyObject):integer; cdecl;
procedure PySys_SetPath(path:PChar); cdecl;
procedure PySys_SetArgv( argc: Integer; argv: PPChar); cdecl;        

if you do:

PySys_SetPath('c:\mylib');

it will be the same than doing in Python:

import sys
sys.path = 'c:\\mylib';

if you want to know the path content, simply use PySys_GetObject('path') and it will return a borrowed reference to the Python path string object.  If you want to replace it, you can do:

PySys_SetObject('path',MyNewPathStringObject);        

But don't forget to decrement the reference count of MyNewPathStringObject.
In fact, if you look at the PySys_SetPath C code:

void
PySys_SetPath(char *path)
{
	PyObject *v;
	if ((v = makepathobject(path, DELIM)) == NULL)
		Py_FatalError("can't create sys.path");
	if (PySys_SetObject("path", v) != 0)
		Py_FatalError("can't assign sys.path");
	Py_DECREF(v);
}        
Andy's Quick Tip                    

When setting slashes for paths in Delphi to python, ensure you turn all \ into \\   Here is a utility that does this:

function EnsurePathHasDoubleSlashes( path : string ):string;
begin
	result := StringReplace(path, '\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
	result := StringReplace(result, '\\\', '\\', [ rfReplaceAll, rfIgnoreCase ]);
end;                    

Compiling delphi app to use specific versions of python dll.

I've was  playing with using different versions of python from delphi, using the latest release of your components.  Here are some scenarios:

python 2.1 By default it seems that python21.dll is used OK
python 2.0 when I want to use python20.dll, I simply set the PYTHON20 define in project options, set the engine property DllName to python20.dll and set the engine property UseLastKnownVersion to false Works OK.

[ Note: You don't even need to set UseLastKnownVersion to false now, because the latest strategy is to find first the dll for which your code was compiled, and then try to upgrade if it's missing. ]

python 2.2 I assume I use the same steps as 2.0 (haven't tried this).  
python 1.52 When I want to use python15.dll, and follow the same steps, compilation fails at PythonEngine.pas at function Traverse( proc: visitproc; ptr: Pointer) : integer; virtual; since visitproc is only defined in PYTHON20_OR_HIGHER, as the following snippet reveals: Issue fixed in latest release of python for delphi components. Feb 2002.

Note: Compiling issues. Set the DEFINE inside the project you are using. setting it in the python for delphi package options does not help since whilst the package is fine, the .exe you build uses .dcu's which are recompiled on demand, using your project options.  Thus you need to set the actual project options.  here is a viewlet demo of this technique.

-Andy Bulka.

How to get python for Delphi to stop looking at the PYTHONPATH environment variable and at any existing python path in the registry

Question: Do you know any way to stop python21 from
looking at PYTHONPATH environment variable, when loaded from delphi?

If I want a truly independent distibution, then supplying my own libs and python21.dll can be thwarted by the user's machine happening to have a
PYTHONPATH defined, and pointing to an old set of python libs (e.g. python20).  The wrong os.py gets loaded etc. etc. Even if the first thing I do in a python script executed by a freshly loaded python engine is clear the sys.path, somehow the older libs found in PYTHONPATH have influenced the equation. Only by removing/renaming the PYTHONPATH environment variable do I avoid the glitches.

I tried clearing the PYTHONPATH dos environment variable in delphi, prior to creating a form on which the atomengine lived - but it seems that python is
loaded into a fresh process space with a fresh set of environment variables (including the nasty PYTHONPATH ). Note that sometimes this solution DID work e.g. on a simple sample app (thus looked like a promising solution) - but didn't work for my big serous delphi app - not sure why not, perhaps cos of the
fresh process space with a fresh set of environment variables hypothesis...

I soon intend to write an article about deploying with python for delphi, including the issues & gotchas I have encountered, so any thoughts about this
rather serious gotcha would be appreciated and shared. It may apply to registry paths too, if you want to override any existing libs and supply your
own. I noticed in python 22 there was something about a flag to ignore the environment - perhaps that is also relevant?

cheers,
Andy Bulka
www.atug.com/andypatterns

P.S.

Can you suggest how to avoid picking up pythonpath? I scanned through the components source code and you only look in the registry for PythonPath, so python itself must be looking at the dos environment.

I have set the engine dll name manually in the property editor.  I have set UseLastKnown to false, and verified the correct dll is being loaded. The python21.dll is in the same directory as the exe, and the Lib and DLLs folders underneath the folder with the exe.

Even the solution of clearing this environment variable BEFORE the form/datamodule with the python engine is even created.helps in some apps, but for others doesn't work either.

Initial reply from Morgan

> Whilst I've got your attention - do you know any way to stop python21 from looking at PYTHONPATH environment variable, when loaded from delphi?

Morgan: Not especially!

> If I want a truly independent distibution, then supplying my own libs and python21.dll can be thwarted by the user's machine happening to have a PYTHONPATH defined, and pointing to an old set of python libs (e.g. python20). The wrong os.py gets loaded etc. etc. Even if the first thing I do in a
python script executed by a freshly loaded python engine is clear the sys.path, somehow the older libs found in PYTHONPATH have influenced the equation.
Only by removing/renaming the PYTHONPATH environment variable do I avoid the glitches.

Morgan: Ok.

> I tried clearing the PYTHONPATH dos environment variable in delphi, prior to creating a form on which the atomengine lived - but it seems that python is loaded into a fresh process space with a fresh set of environment variables (including the nasty PYTHONPATH ). Note that sometimes this solution DID work e.g. on a simple sample app (thus looked like a promising solution) - but didn't work for my big serous delphi app - not sure why not, perhaps cos of the
fresh process space with a fresh set of environment variables hypothesis...

Morgan: Ok. Anyway, you have to clear the registry also.  A Dll shares the same address space of the process it runs into.

>It may apply to registry paths too, if you want to override any existing libs and supply your own. I noticed in python 22 there was something about a flag to ignore the environment - perhaps that is also relevant?

Morgan: It doesn't work with Windows, only Unix.

I looked at Python source code and found out a way:

Simply add the following line of code in the OnBeforeLoad event of
TPythonEngine:

SetEnvironmentVariable('PYTHONHOME', 'c:\myHome');        

And it will generate the following path:

['D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25', 'c:\\myHome\\DLLs',
'c:\\myHome\\lib', 'c:\\myHome\\lib\\plat-win', 'c:\\myHome\\lib\\lib-tk',
'D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25']        

In fact, I should even add a special event for this...

Here are some comments extracted from Python source code:

All PC ports use this scheme to try to set up a module search path:        
from PC\readme.txt        
1) The script location; the current directory without script.
2) The PYTHONPATH variable, if set.
3) For Win32 platforms (NT/95), paths specified in the Registry.
4) Default directories lib, lib/win, lib/test, lib/tkinter;
these are searched relative to the environment variable
PYTHONHOME, if set, or relative to the executable and its
ancestors, if a landmark file (Lib/string.py) is found ,
or the current directory (not useful).
5) The directory containing the executable.        
from PC\getpathp.c        
/* We need to construct a path from the following parts.
(1) the PYTHONPATH environment variable, if set;
(2) for Win32, the machinepath and userpath, if set;
(3) the PYTHONPATH config macro, with the leading "."
of each component replaced with pythonhome, if set;
(4) the directory containing the executable (argv0_path).
The length calculation calculates #3 first.
Extra rules:
- If PYTHONHOME is set (in any way) item (2) is ignored.
- If registry values are used, (3) and (4) are ignored.
*/        

Afterthought....

From: Morgan Martinet [morgan.martinet@altavista.net]

Hi again!

In my last mail I omitted a point: I had removed the registry entries of Python, and it worked fine. But have proper registry settings override the PYTHONHOME path, as they are placed first in the path list.

Anyway, I discovered that you can define your own Python settings in the current user registry, and they will override the default settings of local machine!

So, with regedit I defined the following entries:

Windows Registry Editor Version 5.00        
[HKEY_CURRENT_USER\Software\Python]
[HKEY_CURRENT_USER\Software\Python\PythonCore]
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1]
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath] @="d:\\default" 
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath\PythonWin] @="d:\\pythonwin"
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath\Win32] @="d:\\win32com"
[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath\win32com]        

And I get the following path from Python:

['D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25', 'd:\\pythonwin', 'd:\\win32com', 'D:\\Users\\Morgan\\PythonForDelphi\\Demos\\Demo25', 'd:\\default', 'C:\\Python21\\Pythonwin', 'C:\\Python21\\win32', 'C:\\Python21\\win32\\lib', 'C:\\Python21', 'C:\\Python21\\Lib\\plat-win', 'C:\\Python21\\Lib', 'C:\\Python21\\DLLs', 'C:\\Python21\\Lib\\lib-tk']

Hope this helps,

Andy still is not clear:

So you are saying that inserting d:\\default into PythonPath is also needed? in conjunction with SetEnvironmentVariable('PYTHONHOME', 'd:\default');

So in summary, the combination:

SetEnvironmentVariable('PYTHONHOME', 'c:\myHome');        

and

[HKEY_CURRENT_USER\Software\Python\PythonCore\2.1\PythonPath]@="d:\\default" 
        

( the latter hopefully should appear before any other refs to any other \lib.)

will fix the problem?

I'll play around some more with the new PYTHONHOME idea. But try point your PYTHONPATH to a python20 lib folder and load python21.dll via delphi, and load os and urllib etc. Weird things happen unless the correct versions are loaded, that's for sure. I'll see how the PYTHONHOME works...

Morgan's final word on this issue:

You don't even need to use: SetEnvironmentVariable('PYTHONHOME', 'c:\myHome');

Simply override the Python registry settings for the current user, as you can't do it for the local machine if the logged user has not admin rights.

Andy's final Solution:

I never did resolve this.  So I just added some code to the delphi app which checked to see if PYTHONPATH was defined.  If it was, the app refuses to run.  I just couldn't afford to take chances and didn't have the time to resolve this issue to my satisfaction.

Anybody else is welcome to email me so I can do a final summary.

Where does python for delphi look for the DLL at run time?

>Is there any way I can control WHERE it looks for the DLL at run time?
>or, failing that, at compile time?

Yes, simply set the property UseLastKnownVersion to False, and set the property DllName to the fully qualified path of your python21.dll. Don't forget to set the properties APIVersion to 1010 and RegVersion to 2.1

If you want to do it by code, you must also set the property AutoLoad to False, and set the previous properties in the OnCreate event of your form or datamodule, and finally call the method LoadDll.

Note that in the next release, I will add a property to specify the Dll's folder.

Hope this helps,

Morgan

Related 'Python for Delphi' Links on this site:

bullettutorial - Andy's Python Delphi Tutorial - Getting started with the basics.  Also use these techniques if you are using Delphi 5 or below, and Python 2.0 or below.   Includes info on getting these free components.
bulletcode - Here are Andy's Extensions  - a delphi unit that adds a few utility functions to Python for Delphi. 
bullettutorial - Here is a later tutorial using the Latest Techniques in Python for Delphi - use these if you have Delphi 6 and Python 2.1 or higher.
bulletdiscussion & tips - Here is a discussion and tips on python for delphi deployment issues.
bulletanimated slideshow tutorial  - Here is an animated visual viewlet demo of using the Python for Delphi components to build a Delphi app that talks to python.
bulletexample and screenshot of a Delphi GUI application which uses python code for all its business logic.

return to main Andy Patterns home page