On line 28 of your snippet, you're callingÂ
feature.setAttribute()
, but it's missing the required second argument, which is for the attribute's value.
This is unrelated to the error you're seeing, but you should consider moving theÂ
CSVList
 instantiation out ofÂ
__init__
 and make it the first line inÂ
input()
 instead, to ensure that it's defined even in your handled exception case.
Try
for i, v in enumerate(CSVlist):
feature.setAttribute('_list_CSVs{%d}' % i, v)
 you're trying to format a tuple that is part text, hence the error
In the line 30 ("close" method), you are going to output a "feature", but the local variable called "feature" is not defined in the "close" method. It causes a syntax error.
Perhaps your intention is to output "feature" within the "input" method?
Thanks everyone for your answers so far. I'll try out your suggestions.
Â
Part of the problem is my lack of knowledge about Python syntax. For example, I thought the "v" was a second pointer for nested arrays because that was the scenario I saw in most of the topics I found.
Â
Apart from places like this and StackExchange which are answering specific problems, I haven't found any general Python help on the net that's particularly user friendly.
Â
Â
Â
No Python errors now I've implemented all the suggestions, but I'm not getting a list. I've connected the output of the PythonCaller to a ListExploder but in the ListExploder properties it says there are "No List Attributes Available".
Â
Â
No Python errors now I've implemented all the suggestions, but I'm not getting a list. I've connected the output of the PythonCaller to a ListExploder but in the ListExploder properties it says there are "No List Attributes Available".
Â
Workbench doesn't expose attribute/list names that you defined in the script. You have to expose the list name ("_list_CSVs{}" in this case) manually through the "Attributes to Expose" parameter in the PythonCaller.
Â
I took the liberty of "fixing up" your code based on the suggestions here. This works for me using FME 2016:
import fmeobjects
import ftplib
class GetCSVs(object):
    def input(self, feature):
        CSVlist = u]
        ftp = ftplib.FTP("REDACTED")
        ftp.login("REDACTED", "REDACTED")
        ftp.cwd("/pub")
        try:
            CSVlist = ftp.nlst()
        except ftplib.error_perm, resp:
            if str(resp) == "550 No files found":
                print "No files found"
            else:
                raise
        ftp.quit()
        for i, v in enumerate(CSVlist):
            feature.setAttribute('_list_CSVs{%d}' % i, v)
        self.pyoutput(feature)
I took the liberty of "fixing up" your code based on the suggestions here. This works for me using FME 2016:
import fmeobjects
import ftplib
class GetCSVs(object):
    def input(self, feature):
        CSVlist = u]
        ftp = ftplib.FTP("REDACTED")
        ftp.login("REDACTED", "REDACTED")
        ftp.cwd("/pub")
        try:
            CSVlist = ftp.nlst()
        except ftplib.error_perm, resp:
            if str(resp) == "550 No files found":
                print "No files found"
            else:
                raise
        ftp.quit()
        for i, v in enumerate(CSVlist):
            feature.setAttribute('_list_CSVs{%d}' % i, v)
        self.pyoutput(feature)
Â
Thanks.  This is pretty much where I'm at except I hadn't removed the other template code you get in the PythonCaller by default.  I've now done that.
Â
Workbench doesn't expose attribute/list names that you defined in the script. You have to expose the list name ("_list_CSVs{}" in this case) manually through the "Attributes to Expose" parameter in the PythonCaller.
Â
Hooray! Thanks Takashi :-)
Â
I got so close - I exposed "_list_CSVs" not "_list_CSVs{}".
Â
Â
Â
Thanks.  This is pretty much where I'm at except I hadn't removed the other template code you get in the PythonCaller by default.  I've now done that.
Â
Hi, if your final goal is to create features each of which has a single filename attribute, you can replace the last three lines of @david_r's script example with this code snippet. If you do so, the ListExploder will not  be necessary any longer.
Â
        for v in CSVlist:
            feature.setAttribute('_CSV', v)
            self.pyoutput(feature)
Hi, if your final goal is to create features each of which has a single filename attribute, you can replace the last three lines of @david_r's script example with this code snippet. If you do so, the ListExploder will not  be necessary any longer.
Â
        for v in CSVlist:
            feature.setAttribute('_CSV', v)
            self.pyoutput(feature)
Agreed. It's probably also slightly faster like that.
Â
Â
Hi, if your final goal is to create features each of which has a single filename attribute, you can replace the last three lines of @david_r's script example with this code snippet. If you do so, the ListExploder will not  be necessary any longer.
Â
        for v in CSVlist:
            feature.setAttribute('_CSV', v)
            self.pyoutput(feature)
Awesome.  Thanks everyone.
Â
Incidentally, what is the {%d} bit in the list version?
Â
Â
Â
Thanks. This is pretty much where I'm at except I hadn't removed the other template code you get in the PythonCaller by default. I've now done that.
Â
In fact, a list element can be treated with the same manner as a non-list attribute. The difference from a non-list attribute is that the name of a list element is qualified by the index (0-based sequential number) which is surrounded by curly bracket. Here, the {%d} indicates the index part, and the %d will be replaced with the value of i (i.e. 0, 1, 2 ...) at run-time. It's the behavior of the % operator. See the Python documentations to learn more ;)
Â
Agreed. It's probably also slightly faster like that.
Â
Â
It's just string formatting, it's explained in great detail here:
https://pyformat.info/
In some ways this is actually better than the proposed download/upload of a folder:
Â
https://knowledge.safe.com/questions/32041/download-and-upload-folder-from-ftp.htmlÂ
I've added a Tester after the PythonCaller which tests if the CSV filename ends in ".csv":
Â
@UpperCase(@Right(@Value(CSVFileName),4)) Like .CSV
Â
So if the FTP directory has a mix of files, and the filename/extension values allow you to distinguish different types of file, you can process these in different ways, or ignore files you don't want to download.
Â
The directory listing from the code I've used in the PythonCaller will include sub-directory names, so one of the things my Tester does is ignore sub-directories. Only filenames are sent to the FTPCaller.
Â
In some ways this is actually better than the proposed download/upload of a folder:
Â
https://knowledge.safe.com/questions/32041/download-and-upload-folder-from-ftp.htmlÂ
I've added a Tester after the PythonCaller which tests if the CSV filename ends in ".csv":
Â
@UpperCase(@Right(@Value(CSVFileName),4)) Like .CSV
Â
So if the FTP directory has a mix of files, and the filename/extension values allow you to distinguish different types of file, you can process these in different ways, or ignore files you don't want to download.
Â
The directory listing from the code I've used in the PythonCaller will include sub-directory names, so one of the things my Tester does is ignore sub-directories.  Only filenames are sent to the FTPCaller.
Â
For what it's worth, if you replace the 11th line of David's script example with this statement, it performs filtering filenames by extension.
Â
            CSVlist = tf for f in ftp.nlst() if ft-4:].lower()=='.csv']
See theÂ
List Comprehensions to learn more about the syntax.
Â
For what it's worth, if you replace the 11th line of David's script example with this statement, it performs filtering filenames by extension.
Â
            CSVlist = >f for f in ftp.nlst() if fV-4:].lower()=='.csv']
See theÂ
List Comprehensions to learn more about the syntax.
Â
I guess it depends if you just want CSVs or want to send different files along different routes in your workspace.  For example if there were several unzipped ShapeFiles in the FTP directory (e.g. Rivers, Roads, Towns) you could use a Tester/TestFilter to download the 6+ files that comprise the ShapeFile to different folder.
Â
If the destination folder had the same name as the ShapeFile, you could dispense with the Tester/TestFilter and use the ShapeFile name (minus extension via something like the @Left function) in the FTPCaller Target File parameter to download each ShapeFile to it's own folder e.g. C:\@Value(SHPName)\@Value(FTPFileName)
Â
I guess it depends if you just want CSVs or want to send different files along different routes in your workspace. For example if there were several unzipped ShapeFiles in the FTP directory (e.g. Rivers, Roads, Towns) you could use a Tester/TestFilter to download the 6+ files that comprise the ShapeFile to different folder.
Â
If the destination folder had the same name as the ShapeFile, you could dispense with the Tester/TestFilter and use the ShapeFile name (minus extension via something like the @Left function) in the FTPCaller Target File parameter to download each ShapeFile to it's own folder e.g. C:\\@Value(SHPName)\\@Value(FTPFileName)
Â
The statement fetches only filenames ending with '.csv' in case-insensitive, and stores them into the "CSVlist" list. It's just an example. There should be a lot of variations depending on the actual requirement, and of course the approach using the Tester or TestFilter is also a good solution. There always is more than one way ;)
Â
I guess it depends if you just want CSVs or want to send different files along different routes in your workspace. For example if there were several unzipped ShapeFiles in the FTP directory (e.g. Rivers, Roads, Towns) you could use a Tester/TestFilter to download the 6+ files that comprise the ShapeFile to different folder.
Â
If the destination folder had the same name as the ShapeFile, you could dispense with the Tester/TestFilter and use the ShapeFile name (minus extension via something like the @Left function) in the FTPCaller Target File parameter to download each ShapeFile to it's own folder e.g. C:\\@Value(SHPName)\\@Value(FTPFileName)
Â
The FTP Workspace is now called by a Workspace Runner. If the FTP directory is empty (no CSV files or any other type of file), nothing gets downloaded (obviously) and therefore no features get output from the PythonCaller. While this is not an issue for the FTP Workspace itself, I need to capture this scenario because it affects what happens after the Workspace Runner.
Â
There's another Workspace Runner which will fail if there are no CSV files in the input folder it reads. I want to skip that subsequent Workspace Runner if no CSV files have been downloaded.
Â
So I've added the code below to my FTP Python script:
Â
if not CSVlist:
Â
feature.setAttribute('FTPErr', 'No CSV files to download')
Â
self.pyoutput(feature)
Â
This ensures that if the FTP directory is empty, there will be 1 feature output from the PythonCaller which I can test and use a Terminator to terminate the translation.
Â
Do you have an example how to take a set of shapefiles download them and take them to the next level by the shapefile and its corresponding downloaded files. By Now I had to take the list to the FTPCaller and then put up a reader from the download dir.
I've implemented the Python code from @david_r which works fine. However, if I exchange ftplib.nlst() for ftplib.dir() to give me a full directory listing (I need to file modified date) FME gives me the error in the screenshot below. Spent all afternoon trying to get around this without success. Any ideas??
Â
Thanks
Â
James
Â
I've implemented the Python code from @david_r which works fine. However, if I exchange ftplib.nlst() for ftplib.dir() to give me a full directory listing (I need to file modified date) FME gives me the error in the screenshot below. Spent all afternoon trying to get around this without success. Any ideas??
Â
Thanks
Â
James
Â
According to the
documentation: "This method returns None" :-)
Â
Â
So basically, you cannot use this method as a replacement for nlst() without any further
Â
changes.
Â
Â
You can, however, specify a callback function for dir() which will then be called once for every line returned by the ftp server. There's a
short example here. You will then have to split up each line yourself to get the filenames, dates etc.
Â
According to the
documentation: "This method returns None" :-)
Â
Â
So basically, you cannot use this method as a replacement for nlst() without any further
Â
changes.
Â
Â
You can, however, specify a callback function for dir() which will then be called once for every line returned by the ftp server. There's a
short example here. You will then have to split up each line yourself to get the filenames, dates etc.
Â
Thanks for the prompt reply. Are you able to elaborate on that a bit as being new to Python I can't see how that example relates to the code example you posted in this thread.
Â
Thanks
Â
Â
Thanks for the prompt reply. Are you able to elaborate on that a bit as being new to Python I can't see how that example relates to the code example you posted in this thread.
Â
Thanks
Â
Â
Maybe you could tell me what you intend to accomplish first? That way we can hopefully avoid going down the wrong path.
Â
As you're implying, using callback functions is indeed a somewhat advanced topic if you're new to programming.
Â
According to the
documentation: "This method returns None" :-)
Â
Â
So basically, you cannot use this method as a replacement for nlst() without any further
Â
changes.
Â
Â
You can, however, specify a callback function for dir() which will then be called once for every line returned by the ftp server. There's a
short example here. You will then have to split up each line yourself to get the filenames, dates etc.
Â
I'm trying retrieve a file from a Unix server. There are hundreds of similarly named files but I just need to set up an FME workflow which processes the file produced on the current day, hence the reason I was trying to get a full directory listing with the file created / modified date. Once I'd used FME to run through a returned directory listing and find the correct file I was going to use the Python caller again to actually download the file with ftp and pull the file back into FME for some string replacement functions I need to run on it.
Â
Thanks
Â
James
Â
Â