arcsmith.ws
Workspace
Helpers for setting up geodatabase workspaces inside 'Tool.execute'.
- Create a file geodatabase with automatic parent folder creation
- Get a workspace path for intermediate outputs, switching between memory and a scratch
.gdb - Turn a file name or column header into a name a geodatabase will accept
Functions of the ws module
init_gdb- create a file geodatabase, reusing or overwriting an existing onetemp_space- get a workspace path for intermediate outputs (in-memory or scratch GDB)clean_name- convert a file name or column header into a geodatabase-legal dataset or field name
init_gdb
Creates a file geodatabase at the specified location, creating the parent folder if it does not already exist. If the geodatabase already exists, it is reused by default, or recreated when overwrite=True.
| Parameter | Type | Default | Description |
|---|---|---|---|
folder |
str or Path |
required | Folder in which to create the geodatabase. |
gdb_name |
str |
required | Name of the geodatabase. A trailing .gdb extension, if supplied, is stripped, so "glacier" and "glacier.gdb" behave identically. |
overwrite |
bool |
False |
What to do when the target .gdb already exists. False (default) reuses the existing geodatabase and returns its path (idempotent). True deletes and recreates it empty. |
Returns
str: absolute path to the geodatabase. This is the newly created .gdb unless an existing one was reused (overwrite=False).
Existing geodatabases
arcpy.env.overwriteOutput does not apply to geodatabase creation, so CreateFileGDB raises if the .gdb already exists. init_gdb handles this automatically: by default it reuses the existing workspace, and overwrite=True forces a fresh, empty one. Reuse hands back whatever schema is already on disk, so pass overwrite=True if a clean workspace matters.
Examples
# Create a geodatabase
gdb = arcsmith.ws.init_gdb(r"C:\Projects\Glacier", "glacier")
# C:\Projects\Glacier\glacier.gdb
# Create and set as the arcpy workspace in one line
arcpy.env.workspace = arcsmith.ws.init_gdb(folder, gdb_name)
# Force a fresh, empty geodatabase even if one already exists
gdb = arcsmith.ws.init_gdb(folder, "glacier", overwrite=True)
temp_space
Returns a workspace path for intermediate outputs, either the in-memory workspace or the session scratch geodatabase.
| Parameter | Type | Default | Description |
|---|---|---|---|
use_memory |
bool |
True |
If True, returns 'memory'. If False, returns arcpy.env.scratchGDB for on-disk inspection. |
Returns
str: 'memory' or the absolute path to the scratch geodatabase.
Development workflow
Set use_memory=False while building a tool to inspect intermediate outputs in ArcGIS Pro. Switch back to True (the default) for production runs; no other code changes needed.
memory limitations
The 'memory' workspace does not support everything an on-disk geodatabase does. Certain field types, attribute indexes, and a handful of tools cannot write to it. If a tool fails against 'memory', use use_memory=False for compatibility, not only for inspection.
Examples
# Production: intermediates go to memory (default)
ws = arcsmith.ws.temp_space()
tmp = f"{ws}/trails_temp"
# tmp = "memory/trails_temp"
# Development: intermediates written to scratch GDB for inspection
ws = arcsmith.ws.temp_space(use_memory=False)
tmp = f"{ws}/trails_temp"
# tmp = "C:\Users\...\scratch.gdb\trails_temp"
clean_name
Converts a source name into a name a geodatabase will accept. Geodatabase names are limited to letters, digits, and underscores, must begin with a letter, and cannot be one of the words a geodatabase refuses outright. File names and spreadsheet headers routinely break all three rules.
The refused words are a fixed list, unrelated to what happens to be sitting in a given workspace: the 28 words a file geodatabase reserves, which apply equally to a dataset name and a field name, plus the names a geodatabase manages itself such as objectid, which apply to fields alone. Which set applies is what kind selects.
| Parameter | Type | Default | Description |
|---|---|---|---|
name |
str |
required | File name, column header, or value read from a field, to convert. It is read as a name and nothing more: a path separator is illegal like any other punctuation and collapses to an underscore, so "north/12" becomes "north_12" rather than "12". Pass some_path.name to name an output after the file it came from. |
kind |
'dataset' or 'field' |
'dataset' |
What the name is destined to become, which sets the length limit and which words are refused. See the table below. Keyword-only. |
max_len |
int |
None |
Overrides the length limit implied by kind; must be at least 1. Default None (160 for 'dataset', 128 for 'field'). Longer names are truncated, and an underscore left exposed at the cut is trimmed. Pass 64 for a field if targeting a geodatabase held to the older field name limit. Keyword-only. |
case |
'keep', 'lower', or 'upper' |
'keep' |
Casing applied to the result, prefix included. 'keep' (default) preserves the source casing; 'lower' and 'upper' bring a set of names to one convention. Keyword-only. |
prefix |
str |
"x" |
Text prepended when the cleaned name would otherwise start with a digit, so "yr_" yields "yr_2024_Trails". The prefix is cleansed on the same terms as the name, so it cannot introduce an illegal character, and what survives that cleansing must begin with a letter. It is consulted only when the name needs it. Keyword-only. |
strip_ext |
bool |
True |
If True (default), remove a trailing file extension of up to six characters that begins with a letter. Pass False to keep it, turning "trails.shp" into "trails_shp". Keyword-only. |
What each kind enforces
kind |
Max length | Words refused as a name |
|---|---|---|
'dataset' |
160 | The 28 file geodatabase reserved words (see below) |
'field' |
128 | Those same 28, plus the names a geodatabase manages itself: objectid, oid, fid, shape, shape_length, shape_area, globalid |
The reserved words, which apply to feature class, table, and field names alike:
ADD ALTER AND BETWEEN BY COLUMN CREATE
DELETE DROP EXISTS FOR FROM GROUP IN
INSERT INTO IS LIKE NOT NULL OR
ORDER SELECT SET TABLE UPDATE VALUES WHERE
Returns
str: a name a geodatabase will accept.
Legal, not unique
The result is a legal name, not a unique one. Two different sources can clean to the same name, and no check is made against what already exists in the target workspace. Add a counter or a source-specific suffix when looping over a folder of files.
What gets changed
| Rule | Example |
|---|---|
| Accented characters fold to their ASCII base | Café to Cafe |
| Letters with no accented form are transliterated, not dropped | Ørsted to Orsted |
| Characters with no ASCII equivalent are removed | site山data to sitedata |
| Every run of illegal characters collapses to one underscore | a *** b to a_b |
| Leading and trailing underscores are trimmed | __trails__ to trails |
A name starting with a digit picks up prefix |
2024 Trails to x2024_Trails |
| A refused word picks up a trailing underscore | table to table_ |
How refused words are handled
A refused word keeps its wording and gains a trailing underscore, so "Shape" becomes "Shape_". That is what ArcGIS itself does when a reserved name arrives from a source that allowed it. Only a whole-name match counts, so "shape_area_calc" is left alone; stripping shape out of the middle of a name would destroy its meaning.
The list is short because a file geodatabase is proprietary rather than SQL backed. Words a broader SQL list would flag, date, user, view, union, and distinct among them, are legal here and are left untouched rather than renamed on suspicion.
Extensions
A trailing extension is stripped only when the text after the dot begins with a letter and runs no more than six characters. So .shp, .csv, and .sqlite are removed, "dem_v1.2" keeps its .2 and cleans to "dem_v1_2", and a longer extension stays put: "trails.geojson" cleans to "trails_geojson".
Pass strip_ext=False for spreadsheet and CSV headers. A header is not a file name, so a column called "temp.max" would otherwise lose its .max and clean to "temp".
Examples
from pathlib import Path
# A file name, cleaned
arcsmith.ws.clean_name("Trail Data 2024.shp")
# 'Trail_Data_2024'
arcsmith.ws.clean_name("Glacier NP (final).csv")
# 'Glacier_NP_final'
# A name that is already legal comes back untouched
arcsmith.ws.clean_name("already_clean")
# 'already_clean'
# A separator is punctuation, so a value read from a field keeps all of itself
arcsmith.ws.clean_name("GGOW_PC1993/01")
# 'GGOW_PC1993_01'
arcsmith.ws.clean_name("rings_GGOW_PC1993/01")
# 'rings_GGOW_PC1993_01'
# A name cannot start with a digit, so it picks up 'prefix'
arcsmith.ws.clean_name("2024 visitor counts.xlsx")
# 'x2024_visitor_counts'
arcsmith.ws.clean_name("2024 counts", prefix="yr_")
# 'yr_2024_counts'
# Source casing is kept unless 'case' asks otherwise, and the fold covers
# the prefix
arcsmith.ws.clean_name("Trail Miles", kind="field")
# 'Trail_Miles'
arcsmith.ws.clean_name("Trail Miles", kind="field", case="lower")
# 'trail_miles'
arcsmith.ws.clean_name("Trail Miles", kind="field", case="upper")
# 'TRAIL_MILES'
arcsmith.ws.clean_name("2024 counts", prefix="yr_", case="upper")
# 'YR_2024_COUNTS'
# Over the limit is truncated, and an underscore left at the cut is trimmed
arcsmith.ws.clean_name("Annual Visitor Summary", max_len=14)
# 'Annual_Visitor'
arcsmith.ws.clean_name("Annual Visitor Summary", max_len=7)
# 'Annual'
# Extensions come off by default
arcsmith.ws.clean_name("trails.shp")
# 'trails'
arcsmith.ws.clean_name("trails.shp", strip_ext=False)
# 'trails_shp'
arcsmith.ws.clean_name("dem_v1.2")
# 'dem_v1_2' a version fragment is not an extension
arcsmith.ws.clean_name("trails.geojson")
# 'trails_geojson' seven characters is past the ceiling
# Accents fold, and letters with no accented form are transliterated
arcsmith.ws.clean_name("Café Sites")
# 'Cafe_Sites'
arcsmith.ws.clean_name("Ørsted Wind Farm")
# 'Orsted_Wind_Farm'
# A refused word picks up an underscore; a word the geodatabase allows does not
arcsmith.ws.clean_name("table")
# 'table_'
arcsmith.ws.clean_name("date")
# 'date'
# System field names are legal as a feature class, refused as a field
arcsmith.ws.clean_name("objectid")
# 'objectid'
arcsmith.ws.clean_name("objectid", kind="field")
# 'objectid_'
arcsmith.ws.clean_name("Shape", kind="field")
# 'Shape_'
In practice:
# Name an output from the file it came from. The file name is what is passed,
# since the directory is not part of the name.
src = Path(r"C:\data\raw\Trail Data 2024.shp")
out = f"{gdb}/{arcsmith.ws.clean_name(src.name)}"
# '.../glacier.gdb/Trail_Data_2024'
# Import a folder of shapefiles, each under a legal name
for shp in Path(r"C:\data\raw").iterdir():
if shp.suffix.lower() == ".shp":
arcpy.conversion.ExportFeatures(str(shp), f"{gdb}/{arcsmith.ws.clean_name(shp.name)}")
# Field names from a CSV header row. A header is not a file name, so
# strip_ext=False keeps whatever follows a dot.
headers = ["Trail Name", "Shape", "2024 Visits", "Temp.Max"]
[arcsmith.ws.clean_name(h, kind="field", strip_ext=False) for h in headers]
# ['Trail_Name', 'Shape_', 'x2024_Visits', 'Temp_Max']