Attr: Deserialize deeply nested json? - python-attrs

I have a deeply nested JSON structure like this:
json_data = """{
"title: "...",
"links": [
{
"href": "string",
"method": {
"method": "string"
},
"rel": "string"
}
]
}"""
my classes:
import attr
import typing as ty
import enum
class HttpMethod(enum.Enum):
GET = 0
HEAD = 1
POST = 2
PUT = 3
DELETE = 4
CONNECT = 5
OPTIONS = 6
TRACE = 7
PATCH = 8
#attr.s(frozen=True, auto_attribs=True)
class LinkInfo:
href: str = attr.ib()
link: str = attr.ib(converter=ensure_cls)
method: HttpMethod = attr.ib(converter=lambda x: x.name)
#attr.s(frozen=True, auto_attribs=True)
class AnalysisTemplateGetModel:
title: x: str = attr.ib()
links: ty.List[mlinks.LinkInfo]= attr.ib(default=attr.Factory(list))
and I want to deserialize them to attr classes,
js = json.loads(**json_data)
but the links field of js is still a dictionary, not a LinkInfo object?
str(js)
AnalysisTemplateGetModel(..., links=[{'href': 'string', 'method': {'method': 'string'}, 'rel': 'string'}])

Related

Terraform for_each iteration with the file function

My requirement is to create a dynamic resource Confluent Schema. Below is the schema.tf file.
Basically, need to include map type object and will be creating different Schema by passing name and file attributes. What changes to be done on below highlighted "schema" file parameter so that it can be included in the for_each block?
resource "confluent_schema" "sample_avro_schema" {
schema_registry_cluster {
id = confluent_schema_registry_cluster.essentials.id
}
rest_endpoint = confluent_schema_registry_cluster.essentials.rest_endpoint
for_each = toset(var.subject_name_avro)
subject_name = each.key
format = "AVRO"
**schema = file("modules/confluent_kafka_cluster_dedicated/schemas/sample_schema_avro.avsc")**
credentials {
key = confluent_api_key.env-manager-schema-registry-api-key.id
secret = confluent_api_key.env-manager-schema-registry-api-key.secret
}
}
Variable declaration as below: variable.tf file
variable "subject_name_avro" {
description = "AVRO Schema Name"
type = list(string)
default = ["avro-topic-value"]
}
And I am running this execution using .tfvars file:
subject_name_avro = ["avro-topic-1-value"]
My requirement is to include below changes in .tfvars file. Kindly suggest what resource and variable level changes to be done to include schema file parameter dynamically.
subject_name_avro = [
{
subject_name_avro = "avro-topic-1-value"
schema = file("modules/confluent_kafka_cluster_dedicated/schemas/sample_schema_avro1.avsc")
},
{
subject_name_avro = "avro-topic-2-value"
schema = file("modules/confluent_kafka_cluster_dedicated/schemas/sample_schema_avro2.avsc")
},
]
Sample file content "sample_schema_avro.avsc"
{
"type": "record",
"namespace": "io.confluent.developer.avro",
"name": "Purchase",
"fields": [
{
"name": "item",
"type": "string"
},
{
"name": "amount",
"type": "double"
},
{
"name": "customer_id",
"type": "string"
}
]
}
You can't use file in a variabiles. You can use only path in your case:
subject_name_avro = [
{
subject_name_avro = "avro-topic-1-value"
schema = "./modules/confluent_kafka_cluster_dedicated/schemas/sample_schema_avro1.avsc"
},
{
subject_name_avro = "avro-topic-2-value"
schema = "./modules/confluent_kafka_cluster_dedicated/schemas/sample_schema_avro2.avsc"
},
]
To iterate over this you can use count or for_each. With for_each it would be:
resource "confluent_schema" "sample_avro_schema" {
for_each = {for idx, val in var.subject_name_avro: idx => val}
schema_registry_cluster {
id = confluent_schema_registry_cluster.essentials.id
}
rest_endpoint = confluent_schema_registry_cluster.essentials.rest_endpoint
subject_name = each.value.subject_name_avro
format = "AVRO"
**schema = file(each.value.schema)
credentials {
key = confluent_api_key.env-manager-schema-registry-api-key.id
secret = confluent_api_key.env-manager-schema-registry-api-key.secret
}
}

Merge object and add keys from JSON format using Ruby on Rails

I have a list of array that is queried that needs to be merged with the same location_id based on the objects.
**this are the code for generating array **
filled = Product.on_hand_location(pid).to_a
empty = Product.on_hand_location_empty_cylinder(pid).to_a
data = filled + empty
result = data.map{ |k|
{
details: {
location_id: k['location_id'],
"location_name"=>k['location_name'],
"onhandcylynder"=>k['onhand'] == nil ? 0 : k['onhand'],
"emptycylynder"=> k['emptyonhand'] == nil ? 0 : k['emptyonhand']
} ,
}
}
respond_with [ onhand: result ]
This JSON format below is the output of code above. which has location_id that needs to be merge
[{
"onhand": [{
"details": {
"location_id": 1,
"location_name": "Capitol Drive",
"onhandcylynder": "4.0",
"emptycylynder": 0
}
},
{
"details": {
"location_id": 2,
"location_name": "SM City Butuan",
"onhandcylynder": "5.0",
"emptycylynder": 0
}
},
{
"details": {
"location_id": 1,
"location_name": null,
"onhandcylynder": 0,
"emptycylynder": "2.0"
}
}
]
}]
My desired output
[{
"onhand": [{
"details": {
"location_id": 1,
"location_name": "Capitol Drive",
"onhandcylynder": "4.0",
"emptycylynder": 0
}
},
{
"details": {
"location_id": 2,
"location_name": "SM City Butuan",
"onhandcylynder": "5.0",
"emptycylynder": "2.0"
}
}
]
}]
I think instead of data = filled + empty you should try
data = Product.on_hand_location(pid).to_a
empty = Product.on_hand_location_empty_cylinder(pid).to_a
empty.each do |location|
data.push(location) if data.none? { |item| item['location_id'] == product['location_id'] }
end
result = ...
or
hash = {}
Product.on_hand_location_empty_cylinder(pid).map { |l| hash[l['location_id']] = l }
Product.on_hand_location(pid).map { |l| hash[l['location_id']] = l }
data = hash.values
and if you need some data from both of the queries you should try
hash = {}
Product.on_hand_location_empty_cylinder(pid).map { |l| hash[l['location_id']] = l }
Product.on_hand_location(pid).map { |l| hash[l['location_id']].merge!(l) }
data = hash.values
to merge in place
I refactor my code and able to get my desired result
filled = Product.on_hand_location(pid).to_a
emptyTank = Product.on_hand_location_empty_cylinder(pid).to_a
data = filled + emptyTank
cylinder = data.map(&:dup)
.group_by { |e| e.delete('location_id') }
finalResult = cylinder.map{|_, location_id | location_id.reduce({}) { |result, location_id|
result.merge(location_id)
} }
respond_with [ onhand: finalResult]
The result already merge with the same location id and emptyonhand keys are merge already in corresponding to its location ID
[
{
"onhand": [
{
"onhand": "1.0",
"location_name": "Capitol Drive",
"emptyonhand": "5.0"
},
{
"onhand": "5.0",
"location_name": "SM City Butuan"
}
]
}
]

Ruby make put / post http call with array of object

i have this http call code, the type is form
param = {
form: {
"creatives[]" => [
{
is_visible: params[:creative_banner_is_visible],
type: "banner",
value_translations: {
id: params[:creative_banner_value_id],
en: params[:creative_banner_value_en]
}
},
{
is_visible: params[:creative_video_is_visible],
type: "video",
value_translations: {
id: params[:creative_video_value_id],
en: params[:creative_video_value_en]
}
}
]
}
}
http = HTTP.headers(headers)
http.put(base_url, param)
but somehow this is translated to this on the target server
"creatives"=>[
"{:is_visible=>\"true\", :type=>\"banner\", :value_translations=>{:id=>\"Banner URL ID\", :en=>\"Banner URL EN\"}}",
"{:is_visible=>\"true\", :type=>\"video\", :value_translations=>{:id=>\"12345ID\", :en=>\"12345EN\"}}"
]
do you know how to make this http call not stringified? i used same schema on postman and work just fine
"creatives": [
{
"is_visible": true,
"type": "banner",
"value_translations": {
"id": "http://schroeder.info/elinore",
"en": "http://wehner.info/dusti"
}
},
{
"is_visible": true,
"type": "video",
"value_translations": {
"id": "85177e87-6b53-4268-9a3c-b7f1c206e002",
"en": "5134f3ca-ead7-4ab1-986f-a695e69ace96"
}
}
]
i'm using this gem https://github.com/httprb/http
EDIT
First, replace your "creatives[]" => [ ... with creatives: [ ... so the end result should be the following.
creatives = [
{
is_visible: params[:creative_banner_is_visible],
type: "banner",
value_translations: {
id: params[:creative_banner_value_id],
en: params[:creative_banner_value_en]
}
},
{
is_visible: params[:creative_video_is_visible],
type: "video",
value_translations: {
id: params[:creative_video_value_id],
en: params[:creative_video_value_en]
}
}
]
http = HTTP.headers(headers)
http.put(base_url, creatives.to_json)
Second, I don't see any problem with what you get in your target server, you just have to parse it to JSON, so if you also have a Rails app there use JSON.parse on the body.
somehow this approach fixed the issue
create_params = {}.compare_by_identity
create_params["creatives[][is_visible]"] = params[:creative_banner_is_visible]
create_params["creatives[][type]"] = 'banner'
create_params["creatives[][value_translations][id]"] = params[:creative_banner_value_id]
create_params["creatives[][value_translations][en]"] = params[:creative_banner_value_en]
create_params["creatives[][is_visible]"] = params[:creative_video_is_visible]
create_params["creatives[][type]"] = 'video'
create_params["creatives[][value_translations][id]"] = params[:creative_video_value_id]
create_params["creatives[][value_translations][en]"] = params[:creative_video_value_en]

Add bpmn:Resource in propertiesPanel

Is it possible to add "bpmn:Resource" element in propertiesPanel and edit its attribute? how to do it?
I've already added some properties to property panel based on the property-panel[link] example.
But I have a challenge in adding 'bpmn:Resource' to the properties panel. I don't want it to be added as an extensionElement.
I'd like it to be inside bpmn:Definitions (beside bpmn:Process). Also I'd like to extend the original bpmn:Resource to add some parameters.
So in MyModdle.json I added:
{
"name": "Resource",
"extends": [
"bpmn:Resource"
],
"properties": [
{
"name": "parameters",
"isMany": true,
"type": "MyParameter"
}
]
}, {
"name": "MyParameter",
"properties": [
{
"name": "myParameterType",
"isAttr": true,
"type": "String"
}
{
"name": "myParameterName",
"isAttr": true,
"type": "String"
},
{
"name": "myParameterValue",
"isAttr": true,
"type": "String"
}
]
}
now for example in newElement function of MyResource.js
var newElement = function (type, prop, factory) {
return function (element, extensionElements, value) {
var commands = [];
var resource = getResource(element);
if (!resource) {
var parent = extensionElements;
resource = createResource(parent, bpmnFactory);
console.log('resource', resource);
commands.push(cmdHelper.addAndRemoveElementsFromList(
element,
extensionElements,
'values',
'extensionElements',
[resource],
[]
));
}
var newElem = createResourceParameter(type, resource, bpmnFactory, {
resourceId: 'id-' + value
});
commands.push(cmdHelper.addElementsTolist(element, parameters, prop, [newElem]));
return commands;
};
}
I know this cmdHelper adds 'bpmn:Resource' to extensionElements but I don't know what to use instead!

How to access URI query parameters from a Resource class using Apigility?

I'm developing an Apigility driven application based on the Zend Framework 2.
I want my application to provide nested responses for both -- single items and lists:
/projects/1
{
"id": "1",
"title": "...",
...
"_embedded": {
"images": [
{
"id": "1",
"project_id": "1",
"title": "...",
...
},
{
"id": "2",
"project_id": "1",
"title": "...",
...
}
]
},
...
}
/projects
{
...
"_embedded": {
"projects": [
{
"id": "1",
"title": "...",
...
"_embedded": {
"images": [
{
"id": "1",
"project_id": "1",
"title": "...",
...
},
...
]
},
...
},
...
]
},
"total_items": 2
}
Since I've not found an apigility conform solution for implementing lists with nested lists (in this case projects with a list of images for every project list item, see here). I have to deal with the Paginator and DbAdapter and provide the page parameter manually:
class ProjectResource extends AbstractResourceListener {
...
public function fetchAll($params = array()) {
$pageNumber = $this->getEvent()->getRouteMatch()->getParam('page', 1); <-- that doesn't work
$projectService = $this->getProjectService();
$offset = $pageNumber > 0 ? $pageNumber - 1 : 0;
$config = $this->getServiceManager()->get('Config');
$itemCountPerPage = $config['zf-rest']['Portfolio\\V2\\Rest\\Project\\Controller']['page_size'];
$projects = $projectService->getProjects($offset, $itemCountPerPage);
return $projects;
}
...
}
The problem is, that $this->getEvent()->getRouteMatch()->getParam('page', 1) doesn't work. Instead of the page parameter, $this->getEvent()->getRouteMatch()->getParams() returns
Array
(
[controller] => Portfolio\V2\Rest\Project\Controller
[version] => 2
)
How to access request parameters?
Request parameters have first to be added onto the whitelist. It can be done over the Apigility GUI or directly in the config:
module.config.php
return array(
...
'zf-rest' => array(
...
'Portfolio\\V2\\Rest\\Project\\Controller' => array(
...
'collection_query_whitelist' => array('page'),
...
),
...
),
);
Then the parameter can be accessed over the arguments of the end point methods of the Resource class:
public function fetchAll($params = array()) {
$projectService = $this->getProjectService();
$config = $this->getServiceManager()->get('Config');
$itemCountPerPage = $config['zf-rest']['Portfolio\\V2\\Rest\\Project\\Controller']['page_size'];
$pageNumber = isset($params['page']) && intval($params['page']) > 0
? $params['page']
: 1
;
$offset = ($pageNumber - 1) * $itemCountPerPage;
$projects = $projectService->getProjects($offset, $itemCountPerPage);
return $projects;
}
See also the Apiglity documentation: ZF REST -> Configuration -> User Configuration -> collection_query_whitelist.

Resources