|
POST
|
Agreed, this is my preferred method as well. It's also the methodology @RichardFairhurst uses for Turbo Charging Data Manipulation with Python Curso... - Esri Community
... View more
09-05-2023
12:41 PM
|
1
|
1
|
4275
|
|
POST
|
@clt_cabq wrote: what happens in the version without the 'else' is those records (where no changes are found) end up as nulls instead of the original text, which is the problem. The 'field = field' line seems to correct this issue. Oh, interesting. There's no chance you have a selection on the feature class when you're calling update_address()? I don't see what would cause that behavior.
... View more
08-17-2023
09:49 AM
|
0
|
1
|
1622
|
|
POST
|
Unrelated comment: It seems weird that you have variables for in_fc in update_address() but it's not a parameter. I recommend changing that to be a parameter you pass in when you call update_address(). Also, string.replace() will return the original string even if there's no replacements to be made, so you don't need to check if find_txt is in field; just make the replacement. On to your question, I'm also not sure why it would behave differently. However, you say that in one case it updates everything and the other it only updates things that have changes. It seems like you would only want to update things that have changes, no? Maybe I need more clarification.
... View more
08-17-2023
09:11 AM
|
0
|
3
|
1736
|
|
POST
|
The API is changing quickly so I appreciate the follow-up reply @shaylavi!
... View more
08-17-2023
08:23 AM
|
0
|
0
|
617
|
|
POST
|
@Luke_Pinner wrote: create a test/debug script that imports your script and calls the function with hardcoded values and run the test/debug script in the IDE debugger. Oh, that's an interesting idea. Could you share a sample of your calling script?
... View more
08-11-2023
04:53 PM
|
1
|
2
|
6321
|
|
POST
|
For debugging your main code block, use the arcpy message functions to write to the geoprocessing history output. For debugging code in updateMessages() when validating parameter inputs, I use the message methods on the parameter object to output debug info to the little tooltip messages that pop up on the tool interface in ArcGIS Pro. It looks like your toolbox doesn't have any parameters, so you might not need the parameter messaging. Also keep in mind that when you make changes to the code of the toolbox, you need to restart ArcGIS Pro. There is a way around having to restart ArcGIS Pro, which you can find here: Editing a Python toolbox—ArcGIS Pro | Documentation
... View more
08-11-2023
02:09 PM
|
2
|
0
|
6391
|
|
POST
|
Where you are referring to df in query2, it's just a string. There's nothing to distinguish df in query 2 as a Pandas dataframe object in memory vs a table named "df" in your Oracle connection. You would need to refer to df in your query as a variable. query2 = f"""
SELECT a.*, b.PODE,b.CODE
FROM wh.wc201di_06 b
JOIN {df} a
ON a.DI_ID = b.DI_ID""" However, I don't use Pandas so I'm not sure how exactly you would go about this. I suspect you would be better off not mixing use of cx_Oracle and Pandas. Either query all the data from your Oracle tables into their own Pandas dataframes and do the joins and queries on the dataframes, or build out PL-SQL to do the query solely using cx_Oracle.
... View more
08-10-2023
08:46 AM
|
2
|
0
|
2572
|
|
POST
|
Certainly more processing is going to impact the run time, but in my experience, this logging has not had a noticeable impact on any scripts. The benefit of logging far outweighs any negatives. As far as understanding it all, it is a lot to take in, but it's because I have it modularized and in an example with other stuff (a whole task script) for the example. If you look at the docs for the logging module, it can be implemented very simply! logging — Logging facility for Python — Python 3.11.4 documentation
... View more
08-04-2023
07:34 AM
|
0
|
0
|
6151
|
|
POST
|
This is a great topic. Maybe I should create a more formal article for this, but here's what we have started doing. It's a modular approach that utilizes the Python built-in Logging module. The logging module is cool because when you log an exception, it automatically includes the traceback. However, I noticed that the ArcGIS API for Python also uses the logging module, which is why I had to make some custom log levels so my log messages could be filtered out from the Esri ones. First, we have a helper framework script that all of our scheduled scripts import. To make it easy, this is saved in the same directory as the scripts that use it. In this example, it's named _helperLibrary.py I've included only two functions: initLogging() and sendEmail() def initLogging(
log_level="CUSTOM_INFO", # default to CUSTOM_INFO log level
filemode="w",
msg_format="%(asctime)s %(levelname)s at line %(lineno)s in %(funcName)s(): %(message)s\n",
date_format="%Y-%m-%d %H:%M:%S"
):
"""Creates a standard logging object for use by a requesting script to log information.
Requires modules are
import inspect
from pathlib import Path
import logging
Uses some black magic with inspect to get the file name path
of the origin script that imported and called this.
for log_level:
NOTSET (0)
CUSTOM_DEBUG (5): Custom logging level mimicking DEBUG but slightly lower level so as
to differentiate from logging produced by the ArcGIS API for Python.
DEBUG (10): Detailed information, typically of interest only when diagnosing problems.
INFO (20): Confirmation that things are working as expected.
CUSTOM_INFO (25): Custom logging level mimicking INFO but slightly higher level so as
to exclude redundant logging produced by the ArcGIS API for Python.
WARNING (30): An indication that something unexpected happened, or indicative of some
problem in the near future (e.g. 'disk space low'). The software is still working as expected.
ERROR (40): Due to a more serious problem, the software has not been able to perform some function.
CRITICAL (50): A serious error, indicating that the program itself may be unable to continue running.
All parameters are optional.
:param str log_level: Set the root logger level to the specified level.
:param str filemode: determines the mode in which the log file has to be opened, i.e., read, write, append, etc.
:param str msg_format: Use the specified format string for the handler.
:param str date_format: Use the specified date/time format, as accepted by time.strftime().
:return: logger object
:rtype: class
"""
import inspect
from pathlib import Path
import logging
# Define custom CUSTOM_INFO log level.
# Set log level
CUSTOM_INFO_LEVEL = 25
# "Register" new logging level.
logging.addLevelName(CUSTOM_INFO_LEVEL, "CUSTOM_INFO")
def custom_info(self, message, *args, **kwargs):
if self.isEnabledFor(CUSTOM_INFO_LEVEL):
self._log(CUSTOM_INFO_LEVEL, message, args, **kwargs)
# Make custom_info() available in the logger.
logging.Logger.custom_info = custom_info
# Define custom CUSTOM_DEBUG log level.
# Set log level
CUSTOM_DEBUG_LEVEL = 5
# "Register" new logging level.
logging.addLevelName(CUSTOM_DEBUG_LEVEL, "CUSTOM_DEBUG")
def custom_debug(self, message, *args, **kwargs):
if self.isEnabledFor(CUSTOM_DEBUG_LEVEL):
self._log(CUSTOM_DEBUG_LEVEL, message, args, **kwargs)
# Make custom_debug() available in the logger.
logging.Logger.custom_debug = custom_debug
# Get file name path of the origin script that imported and called this.
# Use the last (origin) frame's file name.
previous_frame = inspect.currentframe().f_back
origin_file_path = inspect.getframeinfo(previous_frame).filename
# Set log file name and output folder for the log file.
log_file_path = Path(origin_file_path).parent / "logs" / f"{Path(origin_file_path).stem}.log"
Path(log_file_path).parent.mkdir(parents=True, exist_ok=True)
# Create logging object and return it to the origin script.
logging.basicConfig(
filename=log_file_path,
filemode=filemode,
level=log_level.upper(),
format=msg_format,
datefmt=date_format
)
return logging.getLogger(log_file_path.name)
def sendEmail(subject="", body_content="", to=[], cc=[], bcc=[], from_addr="[email protected]"):
"""Send a plain text email. Returns a dictionary of recipients.
Required imports:
from email.message import EmailMessage # for a text/plain email message
import smtplib # for sending email
:param str subject: Subject line of email.
:param str body_content: Main message in the body of the email.
:param list to: A list of email addresses as strings for sending the email to.
:param list cc: A list of email addresses as strings for CC'ing the email.
:param list bcc: A list of email addresses as strings for BCC'ing the email.
:param str from_addr: The email address from which to send the email.
:return: All email addresses that received the email (to, cc, bcc).
:rtype: Dictionary
"""
from email.message import EmailMessage
import smtplib
# Validate email recipient args
for recipient in [to, cc, bcc]:
if not isinstance(recipient, list):
raise TypeError(f"Recipients (to, cc, bcc) must each be a list of strings; not {type(recipient)}")
# Create email message object and content.
msg = EmailMessage()
msg.set_content(body_content)
# Set recipients
msg["Subject"] = subject
msg["From"] = from_addr
msg["To"] = ", ".join(to)
msg["Cc"] = ", ".join(cc)
msg["Bcc"] = ", ".join(bcc)
# Send the message via our own SMTP server.
with smtplib.SMTP("your.mail.server.here") as smtp:
smtp.send_message(msg)
# Confirmation messaging.
recipients = {"to": to, "cc": cc, "bcc": bcc}
print(f"sendEmail() successful for recipients {recipients}")
return recipients Then this is what a scheduled script might look like. Notice it's importing the _helperLibrary script. this makes initializing the logging very simple. import _helperLibrary as helper # assumes the py file is in this same directory
import arcpy
import os
from socket import gethostname
from tempfile import TemporaryDirectory
def main():
#
# Some example code to demonstrate logging.
#
logging.custom_info("Starting description of code block.")
# Get something from the database. In this case, a list of fields
logging.custom_debug("Something very specific for CUSTOM_DEBUG")
logging.debug("Here's a general DEBUG level log message.")
logging.info("An INFO level log message.")
logging.custom_info("A CUSTOM_INFO level log message.")
logging.warning("A sample WARNING message")
bad_func() # demonstrate getting an error from another function.
logging.custom_info("Finished description of code block.")
def bad_func():
try:
return this_does_not_exist
except Exception as e:
raise Exception("Here you can add more context to the error message.") from e
# Script start
if __name__ == "__main__":
# Initialize file logging.
logging = helper.initLogging()
logging.custom_info(f"{os.path.basename(__file__)} started on {gethostname()}")
# Create basic email subject line with name of script
email_subject = os.path.basename(__file__)
try:
# Run core functions
with TemporaryDirectory() as temp_dir:
main()
except Exception as e:
print(e)
logging.exception("An Exception Occurred.")
finally:
# Cleanup
try:
# Delete the memory workspace (if applicable)
# https://pro.arcgis.com/en/pro-app/2.9/help/analysis/geoprocessing/basics/the-in-memory-workspace.htm
arcpy.management.Delete("memory")
# Ensure the geoprocessor object no longer has any hold on
# the enterprise geodatabase workspace that has been cleared.
arcpy.management.ClearWorkspaceCache()
except Exception as e:
print(e)
logging.exception("Error with cleaning up.")
pass
# Send log file as email
try:
logging.custom_info("Send log file as email.")
log_file_path = logging.root.handlers[0].baseFilename
with open(log_file_path) as log_file:
log_file_content = log_file.read()
# Build email subject line with noteworthy log levels.
email_subject = os.path.basename(__file__)
for log_level in ["WARNING", "ERROR", "CRITICAL"]:
if log_level in log_file_content:
email_subject = f"[{log_level}] {email_subject}"
# Send the email.
recipients = helper.sendEmail(
subject=email_subject,
body_content=log_file_content,
to=["[email protected]"]
)
logging.custom_info(f"Email sent to {recipients}")
except Exception as e:
print(e)
logging.exception("Error sending email.")
pass When everything is finished, the log file is saved in a "logs" subdirectory where the script runs and the contents of the log file is emailed. The log file is overwritten each time the script runs.
... View more
08-03-2023
04:53 PM
|
1
|
2
|
6173
|
|
POST
|
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.
... View more
08-02-2023
03:23 PM
|
1
|
1
|
4179
|
|
POST
|
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().
... View more
08-02-2023
01:25 PM
|
1
|
3
|
4219
|
|
POST
|
locator | API Reference | ArcGIS Maps SDK for JavaScript 4.27 | ArcGIS Developers Try specifying the outFields=* in the requestOptions of the params object.
... View more
08-01-2023
12:51 PM
|
0
|
1
|
1360
|
|
POST
|
You mentioned having a listener on the layer for edits. Can you make the query before the edit is committed? reactiveUtils | API Reference | ArcGIS Maps SDK for JavaScript 4.27 | ArcGIS Developers
... View more
07-03-2023
08:10 AM
|
0
|
1
|
1466
|
|
POST
|
That is exactly the purpose of GlobalID. I would recommend against using ObjectID as a primary key field because those can get recalculated. Using GlobalID is a "set it and forget it" kind of thing. Just enable GlobalIDs in your feature class and the geodatabase takes care of the rest. You don't need to create the value yourself.
... View more
07-03-2023
08:08 AM
|
0
|
0
|
1054
|
| Title | Kudos | Posted |
|---|---|---|
| 1 | 10-23-2025 03:53 PM | |
| 1 | 04-28-2026 07:25 AM | |
| 1 | 03-19-2026 08:59 AM | |
| 1 | 02-12-2026 01:37 PM | |
| 1 | 12-01-2025 06:19 AM |