Python: Fetching nested dictionary values GCP Recommendations - python-3.x

I am trying to illicit certain key values out of an API JSON output from GCP Recommendations API using Python, and am newer to using Python. Most of the values that I am trying to illicit I can fetch without an issue, however, when I try to illicit certain values within a more deeply nested code block in the JSON, it fails with the error: TypeError: 'OperationGroup' object is not subscriptable
The full output of the JSON response is here (some of the values are changed to protect company information):
name: "projects/12345678910/locations/us-central1-a/recommenders/google.compute.instance.MachineTypeRecommender/recommendations/abcd-efg-hijk-lmnop-qrstuv-123456"
description: "Save cost by changing machine type from e2-medium to e2-small."
last_refresh_time {
seconds: 1623222401
}
primary_impact {
category: COST
cost_projection {
cost {
currency_code: "USD"
units: -12
nanos: -98539964
}
duration {
seconds: 2592000
}
}
}
content {
operation_groups {
operations {
action: "test"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/xyz/zones/us-central1-a/instances/abcname123"
path: "/machineType"
value_matcher {
matches_pattern: ".*zones/us-central1-a/machineTypes/e2-medium"
}
}
operations {
action: "replace"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/xyz/zones/us-central1-a/instances/abcname123"
path: "/machineType"
value {
string_value: "zones/us-central1-a/machineTypes/e2-small"
}
}
}
}
state_info {
state: ACTIVE
}
etag: "\"abc-123-def-456\""
recommender_subtype: "CHANGE_MACHINE_TYPE"
name: "projects/12345678910/locations/us-central1-a/recommenders/google.compute.instance.MachineTypeRecommender/recommendations/abcdefg-hijklmnop-123-456"
description: "Save cost by changing machine type from e2-medium to e2-small."
last_refresh_time {
seconds: 1623222401
}
primary_impact {
category: COST
cost_projection {
cost {
currency_code: "USD"
units: -12
nanos: -99648292
}
duration {
seconds: 2592000
}
}
}
content {
operation_groups {
operations {
action: "test"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/xyz/zones/us-central1-a/instances/instance-example1"
path: "/machineType"
value_matcher {
matches_pattern: ".*zones/us-central1-a/machineTypes/e2-medium"
}
}
operations {
action: "replace"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/xyz/zones/us-central1-a/instances/instance-example1"
path: "/machineType"
value {
string_value: "zones/us-central1-a/machineTypes/e2-small"
}
}
}
}
state_info {
state: ACTIVE
}
etag: "\"abcdefg12345\""
recommender_subtype: "CHANGE_MACHINE_TYPE"
name: "projects/12345678910/locations/us-central1-a/recommenders/google.compute.instance.MachineTypeRecommender/recommendations/abcd1234"
description: "Save cost by changing machine type from e2-medium to e2-small."
last_refresh_time {
seconds: 1623222401
}
primary_impact {
category: COST
cost_projection {
cost {
currency_code: "USD"
units: -11
nanos: -568971875
}
duration {
seconds: 2592000
}
}
}
content {
operation_groups {
operations {
action: "test"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/abcd/zones/us-central1-a/instances/instance-example2"
path: "/machineType"
value_matcher {
matches_pattern: ".*zones/us-central1-a/machineTypes/e2-medium"
}
}
operations {
action: "replace"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/abcd/zones/us-central1-a/instances/instance-example2"
path: "/machineType"
value {
string_value: "zones/us-central1-a/machineTypes/e2-small"
}
}
}
}
state_info {
state: ACTIVE
}
etag: "\"abcd1234\""
recommender_subtype: "CHANGE_MACHINE_TYPE"
Here is my code in Python:
from google.cloud import recommender
import os
client = recommender.RecommenderClient()
def main():
name = client.list_recommendations(parent='projects/xyzproject/locations/us-central1-a/recommenders/google.compute.instance.MachineTypeRecommender')
for element in name:
# print(element)
print(element.description)
print(element.primary_impact.category)
print(element.primary_impact.cost_projection.cost.currency_code)
print(element.primary_impact.cost_projection.cost.units)
print(element.state_info.state)
print(element.content.operation_groups)
for item in element.content.operation_groups:
print(item['resource_type'])
main()
The following portions of the above work:
print(element.description)
print(element.primary_impact.category)
print(element.primary_impact.cost_projection.cost.currency_code)
print(element.primary_impact.cost_projection.cost.units)
print(element.state_info.state)
print(element.content.operation_groups)
but the one that I'm having the error and trouble with is:
for item in element.content.operation_groups:
print(item['resource_type'])
Whenever I try to use that portion of the python script it fails with the error:
TypeError: 'OperationGroup' object is not subscriptable
So, can someone help me understand how I can properly tap into the JSON response and illicit the information within the below block (e.g. the 'resource_type')?
content {
operation_groups {
operations {
action: "test"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/abcd/zones/us-central1-a/instances/instance-example2"
path: "/machineType"
value_matcher {
matches_pattern: ".*zones/us-central1-a/machineTypes/e2-medium"
}
}
operations {
action: "replace"
resource_type: "compute.googleapis.com/Instance"
resource: "//compute.googleapis.com/projects/abcd/zones/us-central1-a/instances/instance-example2"
path: "/machineType"
value {
string_value: "zones/us-central1-a/machineTypes/e2-small"
}
}
}
}

I took the code in your question and rewrote it to match the Python Recommender API. The primary issue is that you are accessing internal member and field names. I have changed the code to use public interfaces.
Review the type definitions for the API:
Types for Recommender API Client
GitHub Gist: Python example demonstrating the Google Cloud Compute Recommender list recommendations api
# pip install google-cloud-recommender
from google.cloud import recommender
import os
# Enter values for your Project ID and Zone
PROJECT=
LOCATION=
RECOMMENDER = 'google.compute.instance.MachineTypeRecommender'
def print_recommendations():
client = recommender.RecommenderClient()
parent = client.recommender_path(PROJECT, LOCATION, RECOMMENDER)
recommendations = client.list_recommendations(parent=parent)
for recommendation in recommendations:
print('Recommendation:')
print(' Description: ', recommendation.description)
print(' Impact Category: ', recommendation.primary_impact.category)
print(' Currency Code: ', recommendation.primary_impact.cost_projection.cost.currency_code)
print(' Units: ', recommendation.primary_impact.cost_projection.cost.units)
print(' State: ', recommendation.state_info.state)
print('')
for op_group in recommendation.content._pb.operation_groups:
for operation in op_group.operations:
print(' Operation:')
print(' Action: ', operation.action)
print(' Resource Type: ', operation.resource_type)
print(' Path: ', operation.path)
print(' Value: ', operation.value.string_value)
print('')
print_recommendations()
Example output:
Recommendation:
Description: Save cost by changing machine type from e2-standard-4 to e2-highmem-2.
Impact Category: Category.COST
Currency Code: USD
Units: -31
State: State.ACTIVE
Operation:
Action: test
Resource Type: compute.googleapis.com/Instance
Path: /machineType
Value:
Operation:
Action: replace
Resource Type: compute.googleapis.com/Instance
Path: /machineType
Value: zones/us-east1-b/machineTypes/e2-highmem-2

Related

ChartJS v3 custom displayFormats

I'm trying to add a string (Week) next to the week number in xAxes in Chartjs 3. But it shows a random number instead of the string.
Here is the part of my code in the options:
scales: {
x: {
type: 'time',
time: {
unit: 'week',
displayFormats: {
week: "'Week' W",
},
tooltipFormat: 'YYYY-W',
},
ticks: {
source: 'data',
},
offset: true,
},
.........
Current output:
'51124' 10
Expected output:
Week 10
any help would be appreciated.
Thanks
"'Week' W" is a formating string, depending on which adapter you are using 'Week' will be translated to a specific value.
You could check if your adapter support custom strings in the format-string,...
Or you try to modify the label with a callback, like shown here in the documentation: https://www.chartjs.org/docs/latest/axes/labelling.html
Here the relevat code, adapted to fit your code snippet:
...
scales: {
x: {
type: 'time',
time: {
unit: 'week',
displayFormats: {
week: 'W',
}
},
ticks: {
// This prepends the "Week" before the label
callback: function(value, index, ticks) {
return '"Week" ' + value;
}
}
}
}
...

lodash fails on the nested groupBy

I have a dataset like this:
var data = [ { keyword: 'organizational inertia',
project: 'org_inertia',
target: 'A' },
{ keyword: 'agent based model organization',
project: 'abm_org',
target: 'A' },
{ keyword: 'organizational inertia',
project: 'org_inertia',
target: 'B' },
{ keyword: 'organizational inertia',
project: 'org_inertia',
target: 'C' },
{ keyword: 'organization inertia',
project: 'org_inertia',
target: 'A' } ]
And a simple code that does this:
const result = _.chain(data)
.groupBy('project')
.mapValues(values => _.chain(values)
.groupBy('target')
.mapValues(values => _.join(_.map(values, 'keyword'),', '))
.value()
)
.value()
Giving back the expected result of:
{
abm_org: {
A: "agent based model organization"
},
org_inertia: {
C: "organizational inertia",
A: "organizational inertia, organization inertia",
B: "organizational inertia"
}
}
So far so good, on JFiddle works perfect. However, when I port the code on my Express application running on Node (v14.15.5), the result is different:
{
org_inertia: {
undefined: 'organizational inertia, organizational inertia, organizational inertia, organization inertia' },
abm_org: {
undefined: 'agent based model organization'
}
}
It is like if the second groupBy doesn't recognise the keys (but the name is correct)... any hint of why that might happen?
the error I had was related to my mongoose model for the object I was trying to operate with.
fixing the model by adding the target field fixed the problem. #Nick Parsons you where right!

Flask-restplus: how to define a nested model with 'allOf' operation?

Creating a python flask rest plus server application,
I'm trying to create a model for input body (in POST operation) with 'allOf' operator,
which is equivalent to the following example, taken from swagger.yaml I've created with the swagger editor:
definitions:
XXXOperation:
description: something...
properties:
oper_type:
type: string
enum:
- oper_a
- oper_b
- oper_c
operation:
allOf:
- $ref: '#/definitions/OperA'
- $ref: '#/definitions/OperB'
- $ref: '#/definitions/OperC'
It should be something like (just in my crazy imagination):
xxx_oper_model = api.model('XXXOperation', {
'oper_type': fields.String(required=True, enum['oper_a', 'oper_b', 'oper_c']),
'operation': fields.Nested([OperA, OperB, OperC], type='anyof')
})
when OperA, OperB, OperC are also defined as models.
How can I do that?
Actually, I prefer to use 'oneOf', but as I understand it's not supported even in the swagger editor, so I try to use the 'allOf' with not required fields.
Versions: flask restplus: 0.10.1, flask: 0.12.2, python: 3.6.2
Thanks a lot
You need to use api.inherit. As mentioned in page 30 of documentation example;
parent = api.model('Parent', {
'name': fields.String,
'class': fields.String(discriminator=True)
})
child = api.inherit('Child', parent, {
'extra': fields.String
})
this way, Child will have all properties of Parent + its own additional property extra
{
"Child": {
"allOf": [
{
"$ref": "#/definitions/Parent"
},
{
"properties": {
"extra": {
"type": "string"
}
}
}
]
}
}

Marathon says 'Insufficient resources' for a task that needs less than what is available

I have a Mesos Master available through a public IP.
I have a Slave that offers some resources.
I try to deploy a small app, but Marathon respond
25646:[2016-12-13 15:26:57,440] INFO Offer [bd40f00f-ce24-4014-b1b1-82db64e68c10-O92]. Considering resources with roles {*} without resident reservation labels. Insufficient ports in offer for run spec [/nginx-test-n2] (mesosphere.marathon.tasks.PortsMatcher:marathon-akka.actor.default-dispatcher-1073)
25647:[2016-12-13 15:26:57,440] INFO Offer [bd40f00f-ce24-4014-b1b1-82db64e68c10-O92]. Insufficient resources for [/nginx-test-n2] (need cpus=0.2, mem=32.0, disk=0.0, gpus=0, ports=(), available in offer: [id { value: "bd40f00f-ce24-4014-b1b1-82db64e68c10-O92" } framework_id { value: "40aadcc7-8e0f-4634-af46-29d9c33bc03e-0000" } slave_id { value: "bd40f00f-ce24-4014-b1b1-82db64e68c10-S1" } hostname: "myproj-slave-vm-1" resources { name: "disk" type: SCALAR scalar { value: 3985.0 } role: "*" } resources { name: "cpus" type: SCALAR scalar { value: 0.6 } role: "*" } resources { name: "mem" type: SCALAR scalar { value: 6478.0 } role: "*" } url { scheme: "http" address { hostname: "myproj-slave-vm-1" ip: "10.1.10.20" port: 5051 } path: "/slave(1)" }] (mesosphere.mesos.TaskBuilder$:marathon-akka.actor.default-dispatcher-1073)
Which once unfolded is:
25628:[2016-12-13 15:26:52,425] INFO Offer [bd40f00f-ce24-4014-b1b1-82db64e68c10-O91].
Insufficient resources for [/nginx-test-n2] (need
cpus=0.2,
mem=32.0,
disk=0.0,
gpus=0,
ports=(),
available in offer:
[id {
value: "bd40f00f-ce24-4014-b1b1-82db64e68c10-O91"
}
framework_id {
value: "40aadcc7-8e0f-4634-af46-29d9c33bc03e-0000"
}
slave_id {
value: "bd40f00f-ce24-4014-b1b1-82db64e68c10-S1"
}
hostname: "myproj-slave-vm-1"
resources {
name: "disk"
type: SCALAR scalar {
value: 3985.0
}
role: "*"
}
resources {
name: "cpus"
type: SCALAR scalar {
value: 0.6
}
role: "*"
}
resources {
name: "mem"
type: SCALAR scalar {
value: 6478.0
}
role: "*"
}
url {
scheme: "http"
address {
hostname: "myproj-slave-vm-1"
ip: "10.1.10.20"
port: 5051
}
path: "/slave(1)"
}
]
I can't understand why Marathon says 'Insufficient resources', since it seems from the log that there is enough...
I solved it by adding resources=ports:[1-65000] to the agent execution command:
/usr/sbin/mesos-slave --master=10.2.0.56:5050 --work_dir=/var/lib/mesos/agent --containerizers=docker --executor_registration_timeout=3mins --log_dir=/var/log/mesos --resources=ports:[1-65000] --advertise_ip=10.1.10.20 --advertise_port=5051

Chart.js Customize tool tip to show more data

I'm using the Chart.js 2.0
The formula : Failure Rate: Failure Count / Total Count.
I've got the an array for failure rate, failure count and total count.
Plotting failure rate of a component against time in a line chart is straight forward but now i also want the failure count and total count to be displayed when the user hovers over the tool tip. I'm not able to figure out how to customize it.
we have e.time, e.count, e.failure_count, e.rate
new Chart(document.getElementById(event_id).getContext("2d"), {type: "line", data:
{
labels: e.time,
datasets: [
{
label: "Failure Rate",
fill: false,
borderColor: "#4caf50",
backgroundColor: "#4caf50",
pointBorderWidth: 1,
pointHoverRadius: 3,
data: e.rate
}
]
}, options: {
scales: {
yAxes: [{
ticks: {
suggestedMin: -1,
suggestedMax: 6
}
}]
},
tooltips: {
custom: function(tooltip) {
tooltip.text= "Not working????"
}
}
}
});
In 2.0, the callback needs a return.
So basically what you want to do is :
custom: function(tooltip) {
tooltip.text= "Not working????"
}
By
callbacks: {
label: function(tooltipItem, data) { return "What you want as a tooltip" }
}

Resources