I am trying to write a Form class that handles the basics of form submittal and validation. I also want to provide the opportunity for custom methods at certain points like validating/modifying data, and I do so with the importlib library. I am testing and modifying the module that gets imported by my class and noticed that it doesn’t update unless I exit the entire project and reload it. I assume its because my Form class imported the module once and does not go to grab the module on every project update in an attempt to be smart and not hog resources. Is there a way to force it?
For clarity here’s my current class definition
class Form():
form_payload = None
initial_data = None
prefix = None
form_name = None
db_table = None
idx = None
userId = None
module_name = None
result = None #result of insert
def __init__(self, form_name, container, prefix="_out_", userId=None):
self.form_name = validateFormName(form_name)
self.db_table = validateDBTable(form_name)
self.module_name = validateModule(form_name)
self.form_payload = getData(container, prefix=prefix)
self.idx = self.form_payload['idx']
if userId is None:
self.userId = self.form_payload['userId']
else:
self.userId = userId
def db_insert(self):
return True, 1
def insert(self):
i = importlib.import_module(self.module_name)
try:
i.validateNew(self.form_payload)
except AttributeError:
pass
except FormError, e:
# TODO Here - we would put the universal way to alert users for vision and perspective
system.gui.warningBox(str(e))
print "about to enter modify New"
i.modifyNew(self.form_payload)
self.result, self.idx = self.db_insert()
###
# Here - we do universal displaying of success or failure
###
i.postinsert(self.form_payload, self.idx, self.result)
Under my def insert(), working with my validate new module. That’s where I am getting caught up - I have a module forms.testForm that is my module_name, which does have a validateNew which does run. However, when I modify validateNew and then retest in my open project window, no changes come through, unless I close the project and reopen. Is there a way around this? In production I don’t see this as an issue because the script module won’t be changing but in development it’s slowing me down a bit.
Ignition has no way to track references across script modules. Don’t import them, ever. Use fully-qualified names. For your case, I recommend simply passing function objects representing your customizations when instantiating Form. You could subclass Form, but that will have the same problem with delayed updates.
Generally speaking, once I get my modules solidified and they aren’t updated - this should work fine otherwise in a production environment right? Are there any Ignition environment specific gotcha’s I need to be on the lookout for when using a class or general pitfalls to watch out for trying to use a class in Ignition?
Don’t import from script modules! (And overwriting a module name with a class name makes me cringe.)
Adjust class Form to look something like this:
class Form(object):
def __init__(self, args...., iVal, uVal):
.... other stuff ....
self.insertValidate = iVal
self.updateValidate = uVal
def insert(self):
try:
self.insertValidate(self, self.form_payload)
.... other stuff ....
Elsewhere, your customized form would be defined something like this:
def insertValidator(self, someData):
# can behave like method of Form if explicitly passed self
pass
def updateValidator(self, someData):
# can behave like method of Form if explicitly passed self
pass
def TestForm(argsForForm....):
return Form.Form(argsForForm..., insertValidator, updateValidator)
I’m not so sure I’d be filling form data in the general init method, unless you’re going to instantiate and throw away in one pass. But that’s orthogonal to the class customization problem you have.
Subclassing is cleaner, of course, but harder to work with in development of the base class. (Just don’t import the base class!)
What, you don't like from datetime import datetime? Makes sense I don't really either, this for me is a proof of concept that I started coding quickly during a slow work day and want to see if I can get working nicely. My entire application is just all GUI forms and they all go through the same or very similar process, so I am trying hard to abstract the common processes away.
I will give your way a shot. If you think this is ultimately a fools errand though please do let me know.
It’s not a bad plan. I’d probably do the subclassing, but without handling data in __init__, just setting up runtime global data. I’d probably keep the base class and all subclasses in a one script module, so there wouldn’t be any reload issues. I’d also probably instantiate one of each in the script module. Then in use, you simply supply the container to the instance and method you wish to execute.
My entire application is just all GUI forms and they all go through the same or very similar process, so I am trying hard to abstract the common processes away.
That's the crux of the DRY principle, innit?
Anyway this is the real cringe
if userId is None:
self.userId = self.form_payload['userId']
else:
self.userId = userId
when you could
self.userId = userId or self.form_payload['userId']
I was definitely self conscious posting so much of my own code, I know I hacked it together and was expecting it to be torn apart, especially since over other places on this forum I consistantly talk about writing good python lol. Tbh I didn't think that syntax was available in 2.7 but good to know!
Ha trust me I'm aware of java.lang.Exception with SQL errors. That's ANOTHER thing I am trying to refactor for all my forms. Before my time all the validation was done with a sql query - does this name exist? If not, ok then we can insert! When really we should just be using UNIQUE columns, try/excepting inserts with the java.lang.Exception and then examining the error.
Yea I am taking it one step at a time. When I first got my hands on this project the entirety was basically Magic Pushbutton anti patterns everywhere. Now I have sorted each form into its own module, but they all look so similar, following the same set of steps, so I am trying to take it one step further with a class.
HA I’ve seen that too here. Well a ID column that’s not auto-incrementing (bleh enough as is) but then for the next insert has a MAX(id)+1 to calculate the next ID. just whyyyyy.
And lol its fine I can take it. Thanks yea it definitely seems like there has to be a better way. I’ve seen
def create():
validateNew()
modifyNew()
insert()
over enough modules to think there’s a better way.
One last idea I have but is probably problematic in its own def insertForm(data, config, post_insert=None) general function where i feed a configuration dictionary along with the data dictionary. Config dictionary that says what sort of validation/modification to do run by string name and then hardcode those functions underneath insertFunction - those too are very formulaic and repeated a lot and I already have functions for. Also have a parameter post_insert=None for forms that have me do stuff after insertion.
@pturmel Any security threats or otherwise problematic issues with this way over the classes?
After a lot of consideration I have decided to leave things exactly as they are. Each form has it’s own module which is completely self contained. I do appreciate the input though and only by talking it through have I come to this conclusion.