Curiosity: why are documented methods throwing AttributeError (is my ignorance showing?)

'PyJsonObjectAdapter' object has no attribute 'update'dict(PyJsonObjectAdapter).update()

more fun: 'list' object has no attribute 'get'list().__getitem__()

I'm guessing the get() method is purposefully stripped/overridden to ensure parity with cpython, but I'd love to know how/why.

edit - looks like it's not necessarily overidden in jython?

Bracket notation is how you use the dunder method of __getitem__ typically personally I have never run into an issue with doing a getitem from a list. Can you post the code that is causing the issue?

The update() method of PyJsonObjectAdapter is marked with the org.python.core.PyIgnoreMethodTag which tells Jython to ignore the method.

https://www.javadoc.io/static/org.python/jython/2.7.0/index.html?org/python/core/PyIgnoreMethodTag.html

So it not being available from Jython was an intentional choice.

Like @bkarabinchak.psi I would be interested to see the code that was throwing the attribute error with list. Not sure I understand why you would need to call the dunder method directly.

1 Like

He's not talking about __getitem__ but a simple get, which could do the same thing as dict.get: return a default instead of throwing an error if trying to access something that doesn't exist.

1 Like

I suppose, part of my confusion is that list() is not the same as PyObject() or PyList().

python list's don't have a 'get' method. There are of course ways to get a default value if needed.

The example links go to the documentation for PyList which does have a get() but that method doesn't return a default, it throws an IndexOutOfRangeException.

Then he references PyObject.get(), but that method also doesn't exist.

My conclusion was he was trying to call __getitem__() directly.

To be honest I'm not quite sure what the thread is about :X

The thread is about "Documented Methods" throwing an error in Ignition, but the OP is referencing the Java Docs, and I suspect the methods are being attempted in Ignition's Jython scripting environment.

To be honest, I probably reference the Java docs more than anything when developing scripts for Ignition, but I do it with the understanding that they're written for Java developers, not Jython developers.

1 Like

Yes, specifically I suspect in some Perspective Context, since that is where you would most often see a PyJsonObjectAdapter.

I strongly suspect that this is brought about by trying to use dict() methods on what can be easily confused with one but isn't

1 Like

When I originally made PyJsonObjectAdapter act more like a Python dictionary, I deliberately omitted some methods (via the PyIgnoreMethodTag @lrose noticed) because the underlying object, an instance of Google's Gson JsonObject, doesn't have an exact analogue of the Python method so it was easier to omit it than to try to add the extra functionality.

1 Like

Thanks for this!! How can I see/discover this myself?

is Jython list() not Java PyList()?

I mistakenly mentioned PyObject.get() (removed from original post)

@pgriffith does this (difficulty) suggest my workaround is contrived/naive?

update-able_dict = dict(PyJsonObjectAdapter)
update-able_dict.update()

Well, the Ignore in a Throws statement, made me scratch my head, because Ignore and Exceptions aren't really something you see together. Which made me go find out what it did.

1 Like

Your workaround will accomplish a shallow copy - only the first layer of keys/values will be moved to a true dictionary. There's some snippets floating around on the forum for a true deep copy.

1 Like

short explanation for everyone asking:

  • list.get()
    • I wanted an expression structure binding parameter to support named query (including 'json' Return Format option and input parameters)
      • accomplished via runscript() and script transform on expression structure binding
      • enabled parameters to be passed as json string or java.util.hashMap (no dict in expressions)
      • scripting extensions module dataset to json arranges data differently
      • wanted to return a default like named query does
binding
{
  "type": "expr-struct",
  "enabled": false,
  "config": {
    "struct": {
      "consoles": "runScript(\n\t\u0027_system.db.runNamedQuery\u0027,\n\t0,\n\t\u0027Consoles/getConsoles\u0027\n)",
      "userPrefs": "runScript(\n\t\u0027_system.db.runNamedQuery\u0027,\n\t0,\n\t\u0027Metadata/Get User Prefs\u0027, \n\tconcat(\"{\u0027Username\u0027:\", {session.props.auth.user.userName}, \"}\")\n)"
    },
    "waitOnAll": true
  },
  "transforms": [
    {
      "code": "\treturn {\n\t\t\u0027userPrefs\u0027:_system.dataset.pdsToJson(value.userPrefs).get(0,{}),\n\t\t\u0027consoles\u0027:_system.dataset.pdsToJson(value.consoles)\n\t}",
      "type": "script"
    }
  ]
}
_system
'''
need to sublcass system.* instead of this hack
'''

class dataset:

	@staticmethod
	class pdsToJson(list):
		def __init__(self, pds):
			self.pds = [{colName: value for colName, value in zip(pds.columnNames, row)} for row in pds] if pds else []
			super(dataset.pdsToJson, self).__init__(self.pds)
	
		def get(self, index, default=None):
			if index < len(self.pds): return super(dataset.pdsToJson, self).__getitem__(index)
			return default

class db:

	@staticmethod
	def runNamedQuery(path, parameters = None, tx = None, getKey = None):
		import java.util.HashMap
		assert type(path) == str or unicode
		if type(parameters) ==  java.util.HashMap:
			return system.db.runNamedQuery(path, dict(parameters), tx, getKey)
		elif type(parameters) == str or unicode:
			return system.db.runNamedQuery(path, system.util.jsonDecode(parameters), tx, getKey)
		else: return system.db.runNamedQuery(path, parameters, tx, getKey)
  • PyJsonObjectAdapter.update()
    • was chaining script transforms (abandoned that per PTurmel's post on the matter)

basically I wanted one binding instead of multiple

critiques/alternatives welcome!
go easy on me