I have a ruby script that I need to convert to Groovy, its a simple case of removing a collection if key: value.
So in my setup I make a request to the github api
def jsonParse(def json) {
new groovy.json.JsonSlurperClassic().parseText(json)
}
def request = sh script: """curl https://api.github.com/repos/org/${repo}/releases?access_token=${env.TOKEN}""", returnStdout: true
def list = jsonParse(request)
return list
This returns output like so
[
[prerelease: 'true', author: [surname: 'surname', book: 'title'], surname: 'surname'],
[prerelease: 'false', author: [surname: 'surname', book: 'title'], surname: 'surname']
]
In Ruby i would do the following
array.delete_if { |key| key['prerelease'] == true }
How would I approach this with Groovy, if an explanation could be provided that would also be great so i can learn from it
Update
Using the approach from #Rao my list is exactly the same
def request = sh script: """curl https://api.github.com/repos/org/${repo}/releases?access_token=${env.TOKEN}""", returnStdout: true
def list = jsonParse(request)
list.removeAll(list.findAll{it.prerelease == 'true'})
return list
Raw response
[
{"prerelease": true, "author": [ {"surname": "surname", "book": "title"}, "surname": "surname"],
{"prerelease": false, "author": [ {"surname": "surname", "book": "title"}, "surname": "surname"]
]
e. g. with
array = array.findAll { it.prerelease != 'true' }
I guess you don't need anymore explanation?
The sample data is list of maps.
Need to remove the items from the list by filtering prerelease is equal to true. Hope this is string only as embedded between the quotes.
Here is the script which results filtered list.
def list = [
[prerelease: 'true', author: [surname: 'surname', book: 'title'], surname: 'surname'],
[prerelease: 'true', author: [surname: 'surname', book: 'title'], surname: 'surname'],
[prerelease: 'false', author: [surname: 'surname', book: 'title'], surname: 'surname']
]
//Remove all those elements(map) from list if matching the condition
list.removeAll(list.findAll{it.prerelease == 'true'})
//Show the updated list
println list
You can quickly try it online Demo
EDIT: based on OP comment.
Isn't this below output what you want?
EDIT2: Based on OP changed response as Json
def json = """[
{"prerelease": true, "author": [ {"surname": "surname", "book": "title"}]},
{"prerelease": false, "author": [ {"surname": "surname", "book": "title"}]}
]"""
list = new groovy.json.JsonSlurper().parseText(json)
println new groovy.json.JsonBuilder(list.findAll{ it.prerelease != true }).toPrettyString()
Related
I have an item where multiple attributes may need to be updated (but not always), and each of them requires a different set of conditions. In the below example, I would like to do the following:
Set a new JobTitle if it has changed, and the LastUpdated value is more recent
Append a Project ID to the ProjectsList list and Project to Projects if a new Project ID listed.
Is it possible to run all of these updates within a single update_item expression, or is it necessary to use transactions?
Example item:
{
'PK': 'PERSON#123456789',
'SK': 'PERSON#123456789',
'ID': '123456789',
'Name': 'Bob Smith',
'Birthday': '01/01/2000',
'Street': '65 LEXINGTON AVE',
'Zip': '12345',
'City': 'NEW YORK',
'State': 'NY',
'JobTitle': 'Project Manager',
'LastUpdated': '20210101',
'Projects': [
{
'Name': 'Project A',
'ID': '987654321'
},
{
'Name': 'Project B',
'ID': '756394733'
},
],
"ProjectList": ['987654321', '756394733']
}
Each separate update expressions (via a Table resource):
# Update job title if changed
r = table.update_item(
Key = {
"PK": data['PK'],
"SK": data['SK']
},
UpdateExpression = "SET #JobTitle = :JobTitle",
ExpressionAttributeNames = {
"#JobTitle": "JobTitle",
"#LastUpdated": "LastUpdated"
},
ExpressionAttributeValues = {
":JobTitle": "Senior Project Manager",
":LastUpdated": '20210102'
},
ConditionExpression = "#LastUpdated < :LastUpdated AND #JobTitle <> :JobTitle"
)
# Update projects if not in list
r = table.update_item(
Key = {
"PK": data['PK'],
"SK": data['SK']
},
UpdateExpression = """
SET #Projects = list_append(if_not_exists(#Projects, :empty_list), :ProjectMap),
#ProjectList = list_append(if_not_exists(#ProjectList, :empty_list), :ProjectID)
""",
ConditionExpression = "not(contains(#ProjectList, :ProjectID))",
ExpressionAttributeNames = {
"#Projects": "Projects",
"#ProjectList": "ProjectList"
},
ExpressionAttributeValues = {
":empty_list": [],
":ProjectMap": [{
"Name": "Project C"
"ID": "7463848373"
}],
":ProjectID": ["7463848373"]
}
)
What I want to do
# (1) Update job title if changed or (2) update projects if not in list
r = table.update_item(
Key = {
"PK": data['PK'],
"SK": data['SK']
},
UpdateExpression = """
SET
#JobTitle = :JobTitle,
#Projects = list_append(if_not_exists(#Projects, :empty_list), :ProjectMap),
#ProjectList = list_append(if_not_exists(#ProjectList, :empty_list), :ProjectID)
""",
ExpressionAttributeNames = {
"#JobTitle": "JobTitle",
"#LastUpdated": "LastUpdated",
"#Projects": "Projects",
"#ProjectList": "ProjectList"
},
ExpressionAttributeValues = {
":empty_list": [],
":JobTitle": "Senior Project Manager",
":LastUpdated": '20210102'
":ProjectMap": [{
"Name": "Project C"
"ID": "7463848373"
}],
":ProjectID": ["7463848373"]
},
ConditionExpression = "(#LastUpdated < :LastUpdated AND #JobTitle <> :JobTitle) OR not(contains(#ProjectList, :ProjectID))"
)
I'm skeptical that this is possible due to this line in the AWS documentation:
For these data manipulation operations, you can specify a condition expression to determine which items should be modified. If the condition expression evaluates to true, the operation succeeds; otherwise, the operation fails.
How do I return the array of newly inserted documents? I want to perform a bulk insert from Java.
INSERT {
text: #text0,
createdAt: #createdAt0
} IN messages
LET result0 = { id: NEW._key, text: NEW.text, createdAt: NEW.createdAt }
INSERT {
text: #text1,
createdAt: #createdAt1
} IN messages
LET result1 = { id: NEW._key, text: NEW.text, createdAt: NEW.createdAt }
RETURN [result0, result1]
Is there a better way to collect the results from each insert, other than defining a new variable to keep the results in it?
This query should do what you want.
FOR d IN [
{text:#text0, createdAt: #createdAt0}, {text:#text1, createdAt: #createdAt1}
]
INSERT d INTO abc
RETURN {
id: NEW._id,
text: d.text,
createdAt: d.createdAt
}
An example response from the AQL is:
[
{
"id": "abc/21971344",
"text": "apple",
"createdAt": 1584107091
},
{
"id": "abc/21971345",
"text": "banana",
"createdAt": 1584108473
}
]
So long as the end user doesn't craft the AQL it should be fine, especially since you're using parameters.
If you ever used the product beyond testing, I'd definitely recommend looking at Foxx, as it gives you a layer of abstraction, hosted (relatively) within the database, which at the least stops consumers from having to execute AQL queries at all, rather they just communicate via REST endpoints.
PS. This query format also works:
FOR d IN #messages
INSERT d INTO abc
RETURN {
id: NEW._id,
text: d.text,
createdAt: d.createdAt
}
Where #messages is
"messages": [
{
"text": "apple",
"createdAt": 1584107091
},
{
"text": "banana",
"createdAt": 1584108473
}
]
Using Python 3.7, I have this confusing-looking, nested dictionary:
dict = \
{
'HBL_Posts':
{'vNames':[ 'id_no', 'display_msg_no', 'thread', 'headline', 'category', 'author',
'auth_addr', 'author_pic_line', 'postbody',
'last_msg_no', 'mf_lnk', 'subject_header' ],
'data_fname':'_Posts_plain.htm', 'tpl_fname':'_Posts_tpl.htm', 'addrs_fname':'_addrs.csv' },
'MOTM':
{'vNames':[ 'work_month', 'zoom', 'zoom_id', 'headline', 'description', 'subject_header' ],
'data_fname':'_Posts_plain.htm', 'tpl_fname':'_Posts_tpl.htm', 'addrs_fname':'_addrs.csv'},
'MOTM recording':
{'vNames':[ 'topic', 'description', 'wDate', 'box', 'chat'],
'data_fname':'_Recording_data.htm', 'tpl_fname':'_Recording_tpl.htm', 'addrs_fname':'_addrs.csv'},
'Enticement':
{'vNames':[ 'enticing_post', 'headline', 'hb_preface', 'postscript'],
'data_fname':'_Entice_data.htm', 'tpl_fname':'_Entice_tpl.htm', 'addrs_fname':'_entice.csv'}
}
If I initially set each variable to its own name, like: HBL_Posts = 'HBL_Posts', I can substitute this, much clearer and less typo-prone, code:
dict = \
{
HBL_Posts:
{vNames:[ id_no, display_msg_no, thread, headline, category, author,
auth_addr, author_pic_line, postbody,
last_msg_no, mf_lnk, subject_header ],
data_fname:_Posts_plain.htm, tpl_fname:_Posts_tpl.htm, addrs_fname:_addrs.csv },
MOTM:
{vNames:[ work_month, zoom, zoom_id, headline, description, subject_header ],
data_fname:_Posts_plain.htm, tpl_fname:_Posts_tpl.htm, addrs_fname:_addrs.csv},
MOTM recording:
{vNames:[ topic, description, wDate, box, chat],
data_fname:_Recording_data.htm, tpl_fname:_Recording_tpl.htm, addrs_fname:_addrs.csv},
Enticement:
{vNames:[ enticing_post, headline, hb_preface, postscript],
data_fname:_Entice_data.htm, tpl_fname:_Entice_tpl.htm, addrs_fname:_entice.csv}
}
In fact I accomplished this by just doing all the required assignments, one at a time. But that is about as complicated as the original dictionary set up, with the apostrophes. What I'd like is a function that would enable me to do this neatly and economically.
def self_name(s):
[?????]
Then I could have a list of all the variables, vars_lst, and loop through it setting each to the literal version of itself:
for item in vars_lst:
item = self_name(item)
To avoid having to use apostrophes in setting up vars_lst, I would accept doing:
HBL_Posts = vNames = id_no = . . . = ''
After many, many hours of struggle, I have been unable to supply the needed code for the self_name function. How can I do that, or how can I find another way of avoiding so many apostrophes?
Indent it like JSON:
{
"HBL_Posts": {
"vNames": [
"id_no",
"display_msg_no",
"thread",
"headline",
"category",
"author",
"auth_addr",
"author_pic_line",
"postbody",
"last_msg_no",
"mf_lnk",
"subject_header"
],
"data_fname": "_Posts_plain.htm",
"tpl_fname": "_Posts_tpl.htm",
"addrs_fname": "_addrs.csv"
},
"MOTM": {
"vNames": [
"work_month",
"zoom",
"zoom_id",
"headline",
"description",
"subject_header"
],
"data_fname": "_Posts_plain.htm",
"tpl_fname": "_Posts_tpl.htm",
"addrs_fname": "_addrs.csv"
},
"MOTM recording": {
"vNames": [
"topic",
"description",
"wDate",
"box",
"chat"
],
"data_fname": "_Recording_data.htm",
"tpl_fname": "_Recording_tpl.htm",
"addrs_fname": "_addrs.csv"
},
"Enticement": {
"vNames": [
"enticing_post",
"headline",
"hb_preface",
"postscript"
],
"data_fname": "_Entice_data.htm",
"tpl_fname": "_Entice_tpl.htm",
"addrs_fname": "_entice.csv"
}
}
or even store that in a .json file and load it via:
import json
with open('my_file.json', 'r') as f:
my_dict = json.load(f)
JSON is easy for most people to read and the indentation is easy to see. Plus it is easy to save and read from a file so you don't have to clutter your code.
FYI:
You can pretty print a dictionary using:
import json
my_dict = ...
print(json.dumps(my_dict, indent=4))
which is how I printed your dictionary.
Is it possible to access an object's key inside the name portion of a .each?
let accounts =
[
{
details:
{
company_name:
"company_name",
email,
password:
"asdf",
},
find:
[
"_id",
"company_name",
"email",
"type",
],
type:
"creator"
},
{
details:
{
email,
first_name:
"first_name",
last_name:
"last_name",
password:
"asdf",
},
find:
[
"_id",
"email",
"first_name",
"last_name",
"type",
],
type:
"user"
},
]
describe.each(accounts)(
"%s", // <-- access the 'type' key, e.g. account.type
function (account)
{
// test code
}
)
Jest describe.each expects an array of arrays in the first parameter. If you pass in a 1D array, internally it will be mapped to an array of arrays (i.e. passing [1, 2, 3] as first parameter would be converted to [[1], [2], [3]]).
Each one of the arrays inside of the array is used as the data for a test suite. So, in the previous example, describe.each would generate three test suites, the first with 1 as data, the second with 2 as data and the third with 3 as data.
Now, in the test suite name, you can only format the parameters you are providing to it. In your case, you are passing to each test suite the data in each object of the accounts array. So, when you set the format specifiers in the test suite name, they will apply to the whole account object (i.e. the %s in your example will stringify your object resulting in [object Object]). Unfortunately, I don't think you can apply the format specifiers to a key of the object.
Some ideas to accomplish what you want:
Solution 1
If you use the %s formatter to compose the test suite name, the toString method of Object will be called (which by default returns [object Object]).
If you define a toString method in each of your accounts objects, that method will be used instead. So, we could add the toString method to each one of the account objects with this code (note that the toString method we are adding is returning the value for the type key):
const accounts = [{
details: {
company_name: "company_name",
email: "aa",
password: "asdf",
},
find: [ "_id", "company_name", "email", "type", ],
type: "creator"
}, {
details: {
email: 'bb',
first_name: "first_name",
last_name: "last_name",
password: "asdf",
},
find: [ "_id", "email", "first_name", "last_name", "type", ],
type: "user"
}].map(account => Object.assign(account, { toString: function() { return this.type; } }));
Now, with the %s format specifier you should see the account type in each test suite:
describe.each(accounts)(
"%s", // <-- This will cause the toString method to be called.
function (account)
{
// test code
}
)
Solution 2
You can always redefine each one of your test suite data so that the first parameter is the account type (note that now accounts is a 2D array):
let accounts = [
[
"creator",
{
details: {
company_name: "company_name",
email: "email",
password: "asdf",
},
find: [ "_id", "company_name", "email", "type", ],
type: "creator"
}
], [
"user",
{
details: {
email: "email",
first_name: "first_name",
last_name: "last_name",
password: "asdf",
},
find: [ "_id", "email", "first_name", "last_name", "type", ],
type: "user"
},
]
]
You can now use that first parameter (which is the account type) to give the test suite its name:
describe.each(accounts)(
'%s', // <-- This %s will format the first item in each test suite array.
function (accountType, account) {
// test code
}
);
Note that now your test function receives two parameters as each test suite array has two elements. The first one is the account type and the second one is the account data.
Solution 3
You can use the tagged template literal form of describe.each. With this solution you don't have to change your current definition of accounts array.
describe.each`
account
${accounts[0]}
${accounts[1]}
`('$account.type', function (account) {
// test code
});
The downside of this solution is that you have to manually append each test suite data in the template literal in a new line (i.e. if you add a new element to the accounts array you have to remember to add it in the template literal in a new line as ${accounts[2]}).
you can map your initial account array to convert each account into an array with 2 items:
the account type
the initial account element
Now, you can use the first element array in describe name
describe.each(accounts.map(account => [account.type, account]))(
'testing %s', // %s replaced by account type
(type, account) => { // note: 2 arguments now
it('details should be defined ', () => {
expect(account.details).toBeDefined();
});
},
);
As modern doc says, you can
generate unique test titles by injecting properties of test case object with $variable
So simply:
describe.each(accounts)(
"$type",
function (account) {
// tests
}
)
You can access nested object values like this: $variable.path.to.value
The same works on test.each level.
I had a similar problem with an object. I wanted to test an error message depending on http error codes, so I wrote a test object like so:
const expectedElements = {
error: {
code: 500,
title: "Problème avec l'API"
},
notFound:{
code: 404,
title: "Élement absent"
},
unauthorized:{
code: 401,
title: "Accès non autorisé"
}
};
I used Object.entries(obj) to get an array with those entries written like so: ['key','value']. I can access thoses as two parameters in the test. Here's how I wrote it:
test.each(Object.entries(expectedElements))("NoAccess show the right element for %s",(key,expectedElement)=>{
const { getByRole } = render(<NoAccess apiStatusCode={expectedElement.code}/>);
//test code
});
Now I can add cases as much as I want and I won't have to rewrite the test or create an array. I just write an new value in my expectedElements object. Bonus, I also have a descriptive test name!
Another alternative is to create a wrapper class and stick to a simple convention:
class TestCase {
constructor(value) {
this._value = value;
}
get value() {
return this._value;
}
toString() {
return JSON.stringify(this._value);
}
}
Then a test will look like this:
const testCases = accounts.map(TestCase)
describe.each(accounts)(
"%s", // <-- you can customize this in TestCase toString
function ({value: account})
{
// test code
}
)
I am unable to convert a file that I downloaded to a dictionary object so that I can access each element. I think the quotations are missing for the keys which prevent me from using json_loads() etc. Could you please help me with some solution. I have given the results of the download below. I need to format it.
{
success: true,
results: 2,
rows: [{
Symbol: "LITL",
CompanyName: "LancoInfratechLimited",
ISIN: "INE785C01048",
Ind: "-",
Purpose: "Results",
BoardMeetingDate: "26-Sep-2017",
DisplayDate: "19-Sep-2017",
seqId: "102121067",
Details: "toconsiderandapprovetheUn-AuditedFinancialResultsoftheCompanyonstandalonebasisfortheQuarterendedJune30,2017."
}, {
Symbol: "PETRONENGG",
CompanyName: "PetronEngineeringConstructionLimited",
ISIN: "INE742A01019",
Ind: "-",
Purpose: "Results",
BoardMeetingDate: "28-Sep-2017",
DisplayDate: "21-Sep-2017",
seqId: "102128225",
Details: "Toconsiderandapprove,interalia,theUnauditedFinancialResultsoftheCompanyforthequarterendedonJune30,2017."
}]
}
Here is one way to do it if you have a string of the dict. It is a little hacky but should work well.
import json
import re
regex_string = '(\w{1,}(?=:))'
regex = re.compile(regex_string, re.MULTILINE)
string = open('test_string', 'r').read() # I had the string in a file, but how
# just put the value here based on how you already had it stored.
string = regex.sub(r'"\1"', string)
python_object = json.loads(string)
# Now you can access the python_object just like any normal python dict.
print python_object["results"]
Here is the dict after it has been put through the regex. Now you can read it in with json
{
"success": true,
"results": 2,
"rows": [{
"Symbol": "LITL",
"CompanyName": "LancoInfratechLimited",
"ISIN": "INE785C01048",
"Ind": "-",
"Purpose": "Results",
"BoardMeetingDate": "26-Sep-2017",
"DisplayDate": "19-Sep-2017",
"seqId": "102121067",
"Details": "toconsiderandapprovetheUn-AuditedFinancialResultsoftheCompanyonstandalonebasisfortheQuarterendedJune30,2017."
}, {
"Symbol": "PETRONENGG",
"CompanyName": "PetronEngineeringConstructionLimited",
"ISIN": "INE742A01019",
"Ind": "-",
"Purpose": "Results",
"BoardMeetingDate": "28-Sep-2017",
"DisplayDate": "21-Sep-2017",
"seqId": "102128225",
"Details": "Toconsiderandapprove,interalia,theUnauditedFinancialResultsoftheCompanyforthequarterendedonJune30,2017."
}]
}