Calling the FileHold API with Python
I was asked today if Python could interface to the FileHold API. Pretty sure they meant the language and not the snake, but I was not 100% sure of the answer. I have never used Python before, but I do know it works on the internet so likely could make HTTP protocol requests and that is the minimum required
A quick search on the interwebs brought up a nice page from geeksforgeeks.org on making SOAP / XML web service calls and things seemed more promising. Sure enough, it demonstrated building an XML payload and directly feeding an HTTP request; effective if not a little painful. The good news is in part two of the post where it introduces a tool called Zeep (not the Canadian nuclear reactor). This appears to be a SOAP web service client for Python that simply needs a service’s WSDL to get going.
SOAP (Simple Object Access Protocol) is one of the earliest internet enabled mechanisms for making expressive calls to remote services. It uses XML (eXtensible Markup Language) for its message format and is typically transmitted over HTTP (Hypertext Transfer Protocol), the main protocol used with web servers. Early versions came into being in the late 90’s and it was a well establish standard mechanism when the first version of FileHold was released.
WSDL (Web Services Description Language) came soon after SOAP as a way to describe a service. You can typically give a development tool a link to your service, and it can extract the WSDL file which allows it to prepare the environment for making straightforward calls to the server. If your service is MyWebService.svc, then /MyWebService.svc?WSDL tells you how it works.
A quick surf over to the Zeep documentation has this encouraging statement in the introduction: “Zeep inspects the WSDL document and generates the corresponding code to use the services and types in the document. This provides an easy-to-use programmatic interface to a SOAP server.” Yeah! It might be fun to have a go, in a geeky way, for those of you more technically inclined and follow along to see if I can figure this thing out.
Step 1: web search for Python
It is pretty easy to find the Windows installer and kick it off in a virtual machine. Unfortunately, an error: “Installation forbidden by system policy.” Must be something dumb… run as administrator, of course. Doh! Setup completed successfully and it says “type py”, so…
Just like that, I seem to have learned Python. Time to move on to something more serious.
Step 2: web search for Zeep
Reading down the Zeep home page, it seems it will just install magically with pip. I remember seeing that was something that was a part of the default Python installation, so…
pip install zeep
Once it is installed, the page has a simple test to validate a WSDL. Simple for most humans, tough for me as I went down a rabbit hole of almost certain unnecessary effort.
py -mzeep <wsdl>
For <wsdl>, try http://<myserver>/filehold/fh/userrolemanager/sessionmanager.asmx?WSDL
Don’t skip the ?WSDL as I did and go rabbit hunting.
You can also confirm your SOAP client is setup correctly with their very simple web service.
Step 3: try authenticating
With that confirmation, it is time to try a real request to the FileHold API. The most fundamental one that every project will need to start with is authentication. The UserRoleManager is the place where the SessionManager service is located and that is where the StartSession method is located. It only needs three parameters for the simplest authentication.
Doh! I went to add the call to StartSession and realized I did not know how to specify the third parameter, the client type, so I gave the integer 4 a try. In the application server, that is the enumeration value for a CustomClient client type parameter. As you can see, that is a fail. In Visual Studio this is pretty magical at this sort of thing and Powershell is not too bad either, but apparently there is no magic for Zeep. At least, this magic is not implemented in Zeep version 3.8.
I took a look at the WDSL and it has no integers for the enumeration.
It lists the enumeration names, so let’s try again with ‘CustomClient’. You can find the full list of possible client types in the API reference, though this is the only one you will ever need for a custom application.
Yay! That looks like a session ID and a quick look at the user activity report confirms it.
Step 4: make an authenticated call
The last piece of the basic puzzle is to make a call to the application server that requires authentication. When we call an authenticated service we want a cookie called FHLSID to be in the HTTP header set to our session ID. Time for another web search… and StackOverflow to the rescue. It seems the client object provides session access including the headers, so now let’s think of a service and method that will always work regardless of configuration.
Flipping through the API reference tells us that GetCurrentUser in the UserGroupManager service seems like a good candidate. It is also part of the UserRoleManager application, but unlike the SessionManager service, the UserGroupManager requires authentication otherwise we will get a HTTP 401 error. If we string the whole thing together and clean up the names a bit, it looks like this.
I cut off a bit of the user object in the screenshot, but I think you get the point. As an added bonus I learned about F‑strings to automatically evaluate the contents of the curly braces. It also looks like there might be other ways to include the session cookie, but I think I will leave the fine tuning to the Python experts out there. I didn’t even need to go to the trouble of creating a file to store this. It just runs from the Python command line a lot like it could in Powershell.
Please do not send me questions about Python. You have just been exposed to everything I know.
For your copy paste pleasure, here is my journey in text, with a couple more clean ups.
from zeep import Client
userid = 'sysadm'
password = '12345'
base_url = 'http://rtest14/fh/filehold'
sessionManager = Client(f'{base_url}/UserRoleManager/SessionManager.asmx?WSDL')
sessionId = sessionManager.service.StartSession(userid,password,'CustomClient')
userGroupManager = Client(f'{base_url}/UserRoleManager/UserGroupManager.asmx?WSDL')
userGroupManager.transport.session.headers.update({'Cookie': f'FHLSID={sessionId}'})
print(userGroupManager.service.GetCurrentUser())
Russ Beinder is the Chief Technology Officer at FileHold. He is an entrepreneur, a seasoned business analyst, computer technologist and a certified Project Management Professional (PMP). For over 35 years he has used computer technology to help organizations solve business problems.