Setting the BaseX Document/Database context programmatically - python-3.x

I am attempting to set the database/document context programmatically via the Python API. My steps are as follows:
session = BaseXClient.Session("localhost", 1984, "admin", "admin")
query = session.query("//node")
query.context("doc('dbname')") # **NOT SURE HOW TO SET THE DB TO USE**
query.execute()
I already know that I can simply use the session object as follows and it works fine:
session.execute("xquery doc('dbname')//node/child")
But I am looking for a way to OPEN a database within the scope of the program call separate from the query string. I am not able to find the documentation to explicitly set the database prior to executing the query using the context object. I have looked at the source code for the python BaseXClient and there is context method for the Query() instance that is not well documented. I am attempting to use this to set the Database and not having much luck.

The context you have supplied is just a string. It is not evaluated. In a client server context it is difficult to see how one could pass in a database here.
I think your alternatives are to use the execute command to open a database before running the query. This will set the context. e.g.
var q = session.execute("open mydatabase",log.print)
var q = session.query("count(*)")
or use the query command bind command to pass parameters
var q = session.query("declare variable $db external; count(collection($db))")
q.bind("db", "mydatabase","",log.print);
q.execute(log.print);
Sorry these examples use Javascript and my BaseX Node client as I am not familiar with the Python API but I am sure the same applies in the Python API

Related

How to pass session parameters with python to snowflake?

The below code is my attempt at passing a session parameter to snowflake through python. This part of an existing codebase which runs in AWS Glue, & the only part of the following that doesn't work is the session_parameters.
I'm trying to understand how to add session parameters from within this code. Any help in understanding what is going on here is appreciated.
sf_credentials = json.loads(CACHE["SNOWFLAKE_CREDENTIALS"])
CACHE["sf_options"] = {
"sfURL": "{}.snowflakecomputing.com".format(sf_credentials["account"]),
"sfUser": sf_credentials["user"],
"sfPassword": sf_credentials["password"],
"sfRole": sf_credentials["role"],
"sfDatabase": sf_credentials["database"],
"sfSchema": sf_credentials["schema"],
"sfWarehouse": sf_credentials["warehouse"],
"session_parameters": {
"QUERY_TAG": "Something",
}
}
In AWS Cloudwatch, I can find the parameter was sent with the other options. In snowflake, the parameter was never set.
I can add more detail where necessary, I just wasn't sure what details are needed.
It turns out that there is no need to specify that a given parameter is a session parameter when you are using the Spark Connector. So instead:
sf_credentials = json.loads(CACHE["SNOWFLAKE_CREDENTIALS"])
CACHE["sf_options"] = {
"sfURL": "{}.snowflakecomputing.com".format(sf_credentials["account"]),
"sfUser": sf_credentials["user"],
"sfPassword": sf_credentials["password"],
"sfRole": sf_credentials["role"],
"sfDatabase": sf_credentials["database"],
"sfSchema": sf_credentials["schema"],
"sfWarehouse": sf_credentials["warehouse"],
"QUERY_TAG": "Something",
}
Works perfectly.
I found this in the Snowflake Documentation for Using the Spark Connector: Here's the section on setting Session Parameters

Invoke autoscript (has WO object launch point) when Service Address is updated?

I have a WO in Maximo 7.6.1.1.
When a user updates the Service Address, I want to invoke an autoscript that has an Object Launch Point on the WORKORDER object.
Is there a way to invoke an autoscript (that has an object launch point on the WORKORDER object) when the Service Address is updated?
You should see if mbo.getOwner() returns something and if that something.getName() is WORKORDER and, further, the work order you are expecting it to be. Subject to all that, you can invoke that other autoscript with code like this:
from java.util import HashMap
lpVars = HashMap()
lpVars.put("mbo",mbo.getOwner())
#repeat the last line for any other implicit/explicit variables your target
#script is going to use / expect to be defined
service.invokeScript("YOURSCRIPTNAME", lpVars)
someVar = lpVars.get("someVarDefinedInYOURSCRIPTNAMEWhenItEnded")
Note the work with the lpVars variable. I use it to store the "implicit"/"explicit" variables (e.g. "mbo") that the script I'm calling will expect to be defined. Basically, I'm doing the setup a launch point normally does, since my code is the launch point. Then, since I'm the launch point, I have access to whatever variables were defined when the script ended by Maximo adding them to / updating them in lpVars.
You can create reusable "library" scripts that you can call directly as Preacher explained. See IBM example here: https://www.ibm.com/support/knowledgecenter/SSFGJ4_7.6.0/com.ibm.mbs.doc/autoscript/c_example_reuse.html
So you could have your WO object launchpoint call the library script and your SA object launchpoint calling the same. You then just need to make change to one script if needed and that's great.
I don't believe you can. An object launch point is all about telling Maximo which object to monitor for the following event(s), not exactly about which object to launch the script on (though, for various reasons, those two are necessarily tied together).
What you can do, though, is put your launch point on the service address as you really do want, but then in your script fetch the on-screen/in-memory work order that you want to do something with and do that. This is done through the getOwner() method call or the special ":owner" (maybe with the ampersands, I can't remember) relationship reference.
This is the solution I came up with:
mboName=mbo.getName()
if mboName == 'WOSERVICEADDRESS':
mboWO = mbo.getOwner()
elif mboName == 'WORKORDER':
mboWO=mbo
sax = mboWO.getDouble("SERVICEADDRESS.LONGITUDEX")
say = mboWO.getDouble("SERVICEADDRESS.LATITUDEY")
if sax and say:
mboWO.setValue("longitudex", sax)
mboWO.setValue("latitudey", say)
elif mboWO.getString("ASSETNUM") and mboWO.getBoolean("ASSET.PLUSSISGIS") == 1:
mboWO.setValue("longitudex", mboWO.getDouble("ASSET.longitudex"))
mboWO.setValue("latitudey", mboWO.getDouble("ASSET.latitudey"))
elif mboWO.getString("LOCATION") and mboWO.getBoolean("LOCATION.PLUSSISGIS") == 1:
mboWO.setValue("longitudex", mboWO.getDouble("LOCATION.longitudex"))
mboWO.setValue("latitudey", mboWO.getDouble("LOCATION.latitudey"))
else:
mboWO.setValue("longitudex", None)
mboWO.setValue("latitudey", None)
The script has launch points on multiple objects:

Passing sets of properties and nodes as a POST statement wit KOA-NEO4J or BOLT

I am building a REST API which connects to a NEO4J instance. I am using the koa-neo4j library as the basis (https://github.com/assister-ai/koa-neo4j-starter-kit). I am a beginner at all these technologies but thanks to some help from this forum I have the basic functionality working. For example the below code allows me to create a new node with the label "metric" and set the name and dateAdded propertis.
URL:
/metric?metricName=Test&dateAdded=2/21/2017
index.js
app.defineAPI({
method: 'POST',
route: '/api/v1/imm/metric',
cypherQueryFile: './src/api/v1/imm/metric/createMetric.cyp'
});
createMetric.cyp"
CREATE (n:metric {
name: $metricName,
dateAdded: $dateAdded
})
return ID(n) as id
However, I am struggling to know how I can approach more complicated examples. How can I handle situations when I don't know how many properties will be added when creating a new node beforehand or when I want to create multiple nodes in a single post statement. Ideally I would like to be able to pass something like JSON as part of the POST which would contain all of the nodes, labels and properties that I want to create. Is something like this possible? I tried using the below Cypher query and passing a JSON string in the POST body but it didn't work.
UNWIND $props AS properties
CREATE (n:metric)
SET n = properties
RETURN n
Would I be better off switching tothe Neo4j Rest API instead of the BOLT protocol and the KOA-NEO4J framework. From my research I thought it was better to use BOLT but I want to have a Rest API as the middle layer between my front and back end so I am willing to change over if this will be easier in the longer term.
Thanks for the help!
Your Cypher syntax is bad in a couple of ways.
UNWIND only accepts a collection as its argument, not a string.
SET n = properties is only legal if properties is a map, not a string.
This query should work for creating a single node (assuming that $props is a map containing all the properties you want to store with the newly created node):
CREATE (n:metric $props)
RETURN n
If you want to create multiple nodes, then this query (essentially the same as yours) should work (but only if $prop_collection is a collection of maps):
UNWIND $prop_collection AS props
CREATE (n:metric)
SET n = props
RETURN n
I too have faced difficulties when trying to pass complex types as arguments to neo4j, this has to do with type conversions between js and cypher over bolt and there is not much one could do except for filing an issue in the official neo4j JavaScript driver repo. koa-neo4j uses the official driver under the hood.
One way to go about such scenarios in koa-neo4j is using JavaScript to manipulate the arguments before sending to Cypher:
https://github.com/assister-ai/koa-neo4j#preprocess-lifecycle
Also possible to further manipulate the results of a Cypher query using postProcess lifecycle hook:
https://github.com/assister-ai/koa-neo4j#postprocess-lifecycle

ScriptError using Google Apps Script Execution API

Following these guides https://developers.google.com/apps-script/guides/rest/quickstart/target-script and https://developers.google.com/apps-script/guides/rest/quickstart/nodejs, I am trying to use the Execution API in node to return some data that are in a Google Spreadsheet.
I have set the script ID to be the Project Key of the Apps Script file. I have also verified that running the function in the Script Editor works successfully.
However, when running the script locally with node, I get this error:
The API returned an error: Error: ScriptError
I have also made sure the script is associated with the project that I use to auth with Google APIs as well.
Does anyone have any suggestion on what I can do to debug/ fix this issue? The error is so generic that I am not sure where to look.
UPDATE: I've included a copy of the code in this JSBin (the year function is the entry point)
https://jsbin.com/zanefitasi/edit?js
UPDATE 2: The error seems to be caused by the inclusion of this line
var spreadsheet = SpreadsheetApp.open(DriveApp.getFileById(docID));
It seems that I didn't request the right scopes. The nodejs example include 'https://www.googleapis.com/auth/drive', but I also needed to include 'https://www.googleapis.com/auth/spreadsheets' in the SCOPES array. It seems like the error message ScriptError is not very informative here.
In order to find what scopes you'd need, to go the Script Editor > File > Project Properties > Scopes. Remember to delete the old credentials ~/.credentials/old-credential.json so that the script will request a new one.
EDIT: With the update in information I took a closer look and saw you are returning a non-basic type. Specifically you are returning a Sheet Object.
The basic types in Apps Script are similar to the basic types in
JavaScript: strings, arrays, objects, numbers and booleans. The
Execution API can only take and return values corresponding to these
basic types -- more complex Apps Script objects (like a Document or
Sheet) cannot be passed by the API.
https://developers.google.com/apps-script/guides/rest/api
In your Account "Class"
this.report = spreadsheet.getSheetByName(data.reportSheet);
old answer:
'data.business_exp' will be null in this context. You need to load the data from somewhere. Every time a script is called a new instance of the script is created. At the end of execution chain it will be destroyed. Any data stored as global objects will be lost. You need to save that data to a permanent location such as the script/user properties, and reloaded on each script execution.
https://developers.google.com/apps-script/reference/properties/

node-postgres: how to prepare a statement without executing the query?

I want to create a "prepared statement" in postgres using the node-postgres module. I want to create it without binding it to parameters because the binding will take place in a loop.
In the documentation i read :
query(object config, optional function callback) : Query
If _text_ and _name_ are provided within the config, the query will result in the creation of a prepared statement.
I tried
client.query({"name":"mystatement", "text":"select id from mytable where id=$1"});
but when I try passing only the text & name keys in the config object, I get an exception :
(translated) message is binding 0 parameters but the prepared statement expects 1
Is there something I am missing ? How do you create/prepare a statement without binding it to specific value in order to avoid re-preparing the statement in every step of a loop ?
I just found an answer on this issue by the author of node-postgres.
With node-postgres the first time you issue a named query it is
parsed, bound, and executed all at once. Every subsequent query issued
on the same connection with the same name will automatically skip the
"parse" step and only rebind and execute the already planned query.
Currently node-postgres does not support a way to create a named,
prepared query and not execute the query. This feature is supported
within libpq and the client/server protocol (used by the pure
javascript bindings), but I've not directly exposed it in the API. I
thought it would add complexity to the API without any real benefit.
Since named statements are bound to the client in which they are
created, if the client is disconnected and reconnected or a different
client is returned from the client pool, the named statement will no
longer work (it requires a re-parsing).
You can use pg-prepared for that:
var prep = require('pg-prepared')
// First prepare statement without binding parameters
var item = prep('select id from mytable where id=${id}')
// Then execute the query and bind parameters in loop
for (i in [1,2,3]) {
client.query(item({id: i}), function(err, result) {...})
}
Update: Reading your question again, here's what I believe you need to do. You need to pass a "value" array as well.
Just to clarify; where you would normally "prepare" your query, just prepare the object you pass to it, without the value array. Then where you would normally "execute" your query, set the value array in the object and pass it to the query. If it's the first time, the driver will do the actual prepare for you the first time around, and simple do binding and execution for the rest of the iteration.

Resources