I am attempting to use Field/FieldMap/FieldMappings objects to add fields to a new feature class. In testing, it appears that the approach below is much faster than looping over a set of field settings and using the "AddField" geoprocessing tool, particularly for a large number of fields. Here I'm just testing one field.
Creating the geodatabase, the feature class, the domain (and the code) all work fine. So does adding the field (with some of the properties). What does not work is assigning the domain to the field--line 23 where I am assigning the domain using the "domain" property appears to have no effect. Additionally, trying to set a "defaultValue" throws an exception. Both of these properties appear to be writable according to the Field Object documentation:
Field—ArcGIS Pro | Documentation
Anyone know what's going on here, or what a workaround might be?
fldr = r"full\folder\path"
gdb = arcpy.management.CreateFileGDB(fldr, "TEST")
fc = arcpy.management.CreateFeatureclass("memory", "TEST_FC_MEMORY", "POLYGON")
arcpy.management.CreateDomain(in_workspace=gdb, domain_name="TEST_DOM",
domain_description="TEST", field_type="TEXT",
domain_type="CODED")
arcpy.management.AddCodedValueToDomain(in_workspace=gdb, domain_name="TEST_DOM",
code=1, code_description="A")
# Create Field, FieldMap, FieldMappings.
new_fld = arcpy.Field()
field_mapping = arcpy.FieldMap()
field_mappings = arcpy.FieldMappings()
new_fld.name = "TEST_FIELD"
new_fld.type = "String"
new_fld.length = 10
new_fld.aliasName = "ALIAS!"
new_fld.isNullable = False
# This doesn't seem to have any effect.
new_fld.domain = "TEST_DOM"
# This appears to be fine, but of course has no effect.
new_fld.madeUpAttribute = "X"
# Exception: NameError: The attribute 'defaultValue' is not
# supported on this instance of Field.
# new_fld.defaultValue = "X"
field_mapping.outputField = new_fld
field_mappings.addFieldMap(field_mapping)
arcpy.conversion.FeatureClassToFeatureClass(in_features=fc, out_path=gdb,
out_name="TEST_FC", field_mapping=field_mappings)
Feature class is created, domain is created with test code/description, and field is added (without the domain being assigned).
What you're doing is assigning the domain to the field object that's part of the field mapping. It doesn't actually change the table in the geodatabase. Following your code sample, you'll create the feature class, then create the domain. When you are adding your fields, specify the domain using the field_domain parameter. If you want to add the domain separately after you create the fields, you can instead use AssignDomainToField().
That's correct, that is what I'm doing. The Field Mappings object also contains the names of the fields, the lengths of the fields, the types of the fields, etc. (in this example, it's just one field, but it could be more). I am not changing any fields (they don't exist yet), I am creating a new FieldMappings object to be used during the creation of a new Feature Class. Those other properties are correctly transferred from the FieldMappings object to the new Feature Class with FeatureClassToFeatureClass and the field_mappings= parameter. Why wouldn't a writeable "domain" attribute function the same way?
I am explicitly trying to get away from AddField (the GP tool you have linked). As I stated, it is much slower than using field maps as above. For example, when adding the same 100 fields using two different methods:
AddFields (with the "s") was mentioned below by @DavidSolari, which was helpful, but doesn't work for me without access to Precision, Scale, and Nullability.
So, ideally, I would be able to get the writeable ".domain" attribute to work, along with the "defaultValue" property. Maybe I need to modify these during a second pass over the feature class? As it is, I'm getting that exception listed above thrown when trying to simply access "defaultValue". I presume it's usable without error somehow...
At this point, it has become a curiosity thing, more than an optimization thing. I appreciate your time, and thanks for the input!
I'm surprised you can even add fields like that. I never knew about this trick. I'll have to remember that if I need a solution like this.
If you want to keep this method of adding the fields, you could always use AssignDomainToField() to add the domains after you export the feature class.
I suppose it is basically the same thing as manipulating fields as one normally would using FieldMappings in FeatureClassToFeatureClass (arcpy or the GUI in Pro)--but in this case, there aren't any fields to start with, just a blank feature class in memory. A bit unintuitive, but it's fast to process! Especially if you don't need to worry about defaults or domains, apparently.
As you said, I'll have to just implement the standard AssignDomainToField() and AssignDefaultToField() geoprocessing tools after the initial FieldMapping setup. It's frustrating to not be able to access those properties of the Field object in a writeable way during setup though.
Thanks again for all the input!
I've found for 99% of cases you'll want to use Add Fields to add multiple fields at once. You do lose some configuration options but for simple tasks it'll do what you want.
"AddFields" has its place, however the inability to set Precision, Scale, and Nullability is a deal breaker for my purposes. If iterating back over the fields to change those values is possible, then that could work. In looking at "AlterField," it would appear that only Nullability can be modified using that tool, not Scale or Precision.
So then I'm back to FieldMappings. I appreciate the input anyway.
For sure, if you're doing more complex schema definitions then Add Fields is unsuitable.
One other trick you can look into is creating a template feature class/table and then feeding that into the CreateFeatureclass/CreateTable tool. I believe that should preserve nullability, scale etc. but make sure you run your own tests.
Other than that, Blake's suggestion to just add the domains in a second pass using the GP tool should work just fine!
I'll certainly take a look, but that's essentially what I'm doing with the FieldMappings--creating a template set of fields in memory, and then using FeatureClassToFeatureClass's field_mappings as the template.
If I was constantly generating the same outputs over and over, using the template parameter in CreateFeatureclass would indeed be a good solution, but this is sort of for one-off scenarios (in which case I shouldn't get bogged down in over-optimization anyway...)
Again, thanks for the input!
I tested several methods of adding fields to a blank feature class in an attempt to overcome the sluggish process that is "AddField" in a for loop. Posting all the code might make this unreadable, so here is a summary. All tests result in the same output**--field names are the same, domains are the same, etc. In this example, I am adding 500 fields to a newly created feature class. There is no data involved here, this is schema setup only.
Methods & Times:
So, in my experiments anyway, using a FieldMappings object and working in memory for as long as possible is the absolute fastest. BUT, simply adding fields to a memory feature class and using that as a template during the export to file is probably the simplest solution in terms of lines of code and maintenance.
Results from a more reasonable 40 fields, in the same order as above:
So even when generating a handful of feature classes with am more modest number of fields, it might best to avoid using AddField in a for loop for a feature class that is already saved to file.
**AddFields does not allow the user to specify Scale and Precision, and I didn’t see a quick way to modify those attributes after the fact. In a FGDB, it is irrelevant, but this might matter in an enterprise environment.
Here's an example of the randomly generated fields for three of the five output methods (all three should be the same).