{"id":1112,"date":"2019-12-20T18:24:02","date_gmt":"2019-12-20T17:24:02","guid":{"rendered":"http:\/\/blog.zhaw.ch\/splab\/?p=1112"},"modified":"2019-12-20T19:42:37","modified_gmt":"2019-12-20T18:42:37","slug":"building-a-singer-io-tap-for-an-open-data-source","status":"publish","type":"post","link":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/","title":{"rendered":"Building a Singer.io tap for an open data source"},"content":{"rendered":"\n<p><a href=\"https:\/\/www.singer.io\/\">Singer.io<\/a> is an open-source JSON-based data shifting (ETL: extract, transform, load) framework, designed to bring simplicity when moving data between a source and a destination service on the Internet. In this post, we present the framework as entry point into the world of SaaS-level data exchange and some associated research questions.<\/p>\n\n\n\n<!--more-->\n\n\n\n<p>A script that extracts data from a source (like <a href=\"https:\/\/github.com\/singer-io\/tap-stripe\">Stripe payments<\/a>) and outputs it following the Singer format convention is called a <strong>tap<\/strong>. A script that consumes that output and works with it (like putting it into a <a href=\"https:\/\/github.com\/singer-io\/target-csv\">.csv file<\/a> or a <a href=\"https:\/\/github.com\/datamill-co\/target-postgres\">PostgreSQL<\/a> database) is called a <strong>target<\/strong>.<\/p>\n\n\n\n<p>The idea is that, using the defined convention based on JSON data exchange, any tap can be connected to any target. With this in mind, our goal is to see how easy it is to create a tap and connect it to an existing target, as a first step in the evaluation of this framework for prototyping SaaS-level applications working with diverse data sources. In this context, Singer is placed on a higher level compared to general data orchestration frameworks for cloud environments such as <a href=\"https:\/\/www.alluxio.io\/\">Alluxio<\/a>, and links up with many multi-cloud and cross-cloud ideas but is also subject to the same issues such as the <a href=\"https:\/\/thenewstack.io\/avoiding-least-common-denominator-approach-hybrid-clouds\/\">least common denominator problem<\/a>.<\/p>\n\n\n\n<p>Although there are a lot of already made <a href=\"https:\/\/www.singer.io\/#taps\">taps<\/a> to download and use, those are useful only if you already have meaningful data in any of those sources. For our testing purposes, that was simply not the case.<\/p>\n\n\n\n<p>So today we&#8217;re going to build a simple Python tap that takes data from an open data source and outputs it using the Singer spec.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creating a tap from the template<\/h2>\n\n\n\n<p>In order to develop a tap, we first need to install the Singer library:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install singer-python<\/code><\/pre>\n\n\n\n<p>Next, we&#8217;re installing <em>cookiecutter<\/em> and downloading the provided tap template, to have something to start with:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>pip install cookiecutter\ncookiecutter https:\/\/github.com\/singer-io\/singer-tap-template.git\nproject_name [e.g. 'tap-facebook']: tap-weather\npackage_name [tap_weather]:<\/code><\/pre>\n\n\n\n<p>After downloading the template, <em>cookiecutter<\/em> will ask for a project name. We chose <strong>tap-weather<\/strong>, because our data source will be weather related. Then a package name is required- pressing enter will use the suggested name.<\/p>\n\n\n\n<p>We now have a blank canvas with a few useful functions to get started with. To follow the singer spec, a tap must output two different types of messages:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Schema messages: They describe the datatypes of the data in the stream.<\/li><li>Record messages: They contain the data to be streamed.<\/li><\/ul>\n\n\n\n<p>Additionally, state messages can be streamed, as a way to preserve states between tap executions. A tap can also take some <em>.json<\/em> input files, like a <em>config<\/em> file, used typically for API credentials.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Defining the schemas<\/h2>\n\n\n\n<p>Next step is choosing the source from where the data will be obtained, and understanding the structure and nature of the data. We selected the <a href=\"https:\/\/opendata.swiss\/en\/dataset\/messwerte-wind-boenspitze-1-s-10-min-maxima\">wind measurement<\/a> provided by the Federal Office of Meteorology and Climatology <a href=\"https:\/\/www.meteoswiss.admin.ch\/home.html?tab=overview\">MeteoSwiss<\/a> and published regularly on <a href=\"https:\/\/opendata.swiss\/en\/\">opendata.swiss<\/a>. This is a geospatial dataset with strong regional differences, thus using <a href=\"https:\/\/geojson.org\/\">GeoJSON<\/a> (RFC 7946) as appropriate format.<\/p>\n\n\n\n<p>Taking a look at the <em>.json<\/em> that we&#8217;ll get, it looks something like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n   \"crs\": {\n       \"type\": \"name\",\n       \"properties\": {\n           \"name\": \"EPSG:21781\"\n       }\n   },\n   \"license\": \"https:\/\/...\",\n   \"mapname\": \"ch.meteoschweiz.messwerte-wind-boeenspitze-kmh-10min\",\n   \"map_long_name\": \"Measurement values wind gust 1 s, 10 min maximum\",\n   \"map_short_name\": \"Wind gust 1 s, 10 min\",\n   \"map_abstract\": \"Current measurement values of Wind gust 1 s, 10 min\",\n   \"creation_time\": \"13.12.2019 14:15\",\n   \"type\": \"FeatureCollection\",\n   \"features\": [\n       {\n           \"type\": \"Feature\",\n           \"geometry\": {\n               \"type\": \"Point\",\n               \"coordinates\": [\n                   771035.92,\n                   184826.09\n               ]\n           },\n           \"id\": \"ARO\",\n           \"properties\": {\n               \"station_name\": \"Arosa\",\n               \"station_symbol\": 1,\n               \"value\": 11.9,\n               \"wind_direction\": 54,\n               \"wind_direction_radian\": 0.942478,\n               \"unit\": \"km\/h\",\n               \"reference_ts\": \"2019-12-13T13:10:00Z\",\n               \"altitude\": \"1888.00\",\n               \"measurement_height\": \"10.00 m\",\n               \"description\": \"...\"\n      },\n     ...\n  ]\n}<\/code><\/pre>\n\n\n\n<p>So now we\u2019re going to define the <a href=\"https:\/\/json-schema.org\/\">JSON schemas<\/a> that will be part of our output, to let the targets know how our data is structured. From that <em>.json<\/em> we only extracted the \u201c<strong>features<\/strong>\u201d, which define each station, its location and wind measurement, along with some other properties. The schema is defined as follows:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>{\n    \"type\": \"object\",\n    \"properties\": {\n        \"type\": {\n            \"type\": \"string\"\n        },\n        \"geometry\": {\n            \"type\": \"object\",\n            \"properties\": {\n                \"type\": {\n                    \"type\": \"string\"\n                },\n                \"coordinates\": {\n                    \"type\": \"array\",\n                    \"items\": {\n                        \"type\": \"number\"\n                    },\n                    \"minItems\": 2,\n                    \"maxItems\": 2\n                }\n            }\n        },\n        \"id\": {\n            \"type\": \"string\",\n            \"minLength\": 3,\n            \"maxLength\": 3\n        },\n        ...\n    }\n}<\/code><\/pre>\n\n\n\n<p>We named this file <em>features.json<\/em> and put it inside the <em>schemas<\/em> folder.<\/p>\n\n\n\n<p>In this case we copied each property as-is, but here we have the freedom to choose what properties the record messages will have (with the condition we\u2019ll have to generate the records with that same structure afterwards)<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Building the tap<\/h2>\n\n\n\n<p>Next we have to create the code that will generate the schema and records from our input.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>def main():\n   schemas = load_schemas()\n   singer.write_schema('features', schemas.get('features'), 'station_properties')\n  \n\n   with urllib.request.urlopen('https:\/\/data.geo.admin.ch\/ch.meteoschweiz.messwerte-wind-boeenspitze-kmh-10min\/ch.meteoschweiz.messwerte-wind-boeenspitze-kmh-10min_en.json') as response:\n       data = json.loads(response.read().decode())\n      \n       singer.write_records('features', data.get('features'))<\/code><\/pre>\n\n\n\n<p>First we get the schemas with the <em>load_schemas()<\/em> function, which is already provided by the template, and returns a dictionary with all the schemas found in the respective folder.<\/p>\n\n\n\n<p>Then we output the schema with the <em>write_schema<\/em> function. The first argument is the name of the schema, the second one is the JSON schema itself and the third one is a list of the primary keys for the schema.<\/p>\n\n\n\n<p>Finally we get the data and use the <em>write_records<\/em> function to output them all. The first argument is the schema name, and the second one the list of objects according to that schema. This part would be different if a different schema structure was chosen, as we would have to traverse the whole features array and build each record separately (which can be done with a similar singer function, <em>write_record<\/em>).<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Running the tap<\/h2>\n\n\n\n<p>Let\u2019s create a <a href=\"https:\/\/docs.python.org\/3.9\/library\/venv.html\">virtual environment<\/a> to run our tap on<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>cd tap-weather\npython3 -m venv ~\/.virtualenvs\/tap-weather\nsource ~\/.virtualenvs\/tap-weather\/bin\/activate\npip install -e\ndeactivate<\/code><\/pre>\n\n\n\n<p>We can now try our newly created tap with<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code> ~\/.virtualenvs\/tap-weather\/bin\/tap-weather<\/code><\/pre>\n\n\n\n<p>As expected, we get our schema message, and a record message for every feature retrieved from the initial <em>.json<\/em> \ud83d\ude42<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Connecting the tap to existing targets<\/h2>\n\n\n\n<p>Based on the Singer premise, any target should work, as long as you are careful with the config files needed to make them work. For this we used the <a href=\"https:\/\/github.com\/singer-io\/target-csv\">.csv target<\/a>.<\/p>\n\n\n\n<p>When running a target, it is recommended to create a different virtual environment, so as not to create dependency conflicts.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>python3 -m venv ~\/.virtualenvs\/target-csv\nsource ~\/.virtualenvs\/target-csv\/bin\/activate\npip install target-csv\ndeactivate<\/code><\/pre>\n\n\n\n<p>Finally, we can try tap and target together<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>~\/.virtualenvs\/tap-weather\/bin\/tap-weather | ~\/.virtualenvs\/target-csv\/bin\/target-csv<\/code><\/pre>\n\n\n\n<p>A <em>.csv<\/em> file will be created in the current directory containing all the fetched records<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Final thoughts<\/h2>\n\n\n\n<p>Singer turned out to be a really easy to understand framework, and in theory, has a lot of potential to achieve its purpose! It&#8217;s open-source and, at the time of writing this post, has 72 taps and 10 targets ready to install and use. But, some problems may arise when trying to use those, as not all of them are kept up to date. Case in point, we tried to use the Google Sheets target with our tap, and it wouldn&#8217;t work when data included floating point values.<\/p>\n\n\n\n<p>Otherwise, the Singer libraries and templates are fairly well documented and maintained, so the process to create a new tap was pretty straight-forward. Open questions thus include: What is the dynamics and the quality of the ecosystem around taps and targets development? How well does the framework scale for larger quantities of data? How extensible is the framework in case data processing and analytics are to be performed? What are alternative frameworks such as <a href=\"https:\/\/www.talend.com\/products\/talend-open-studio\/\">Talend Open Studio<\/a> or <a href=\"https:\/\/nodered.org\/\">Node Red<\/a>, and how do they compare? In the <a href=\"https:\/\/blog.zhaw.ch\/splab\/\">Service Prototyping Lab <\/a>at Zurich University of Applied Sciences, we will investigate these questions in the coming months.<\/p>\n<div class=\"pt-sm\">Schlagw\u00f6rter: <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/datascience\/\">datascience<\/a>, <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/geospatial\/\">geospatial<\/a>, <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/integration\/\">integration<\/a>, <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/opendata\/\">opendata<\/a>, <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/publicdata\/\">publicdata<\/a>, <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/python\/\">python<\/a>, <a href=\"http:\/\/blog.zhaw.ch\/splab\/tag\/saas\/\">saas<\/a><br><\/div>","protected":false},"excerpt":{"rendered":"<p>Singer.io is an open-source JSON-based data shifting (ETL: extract, transform, load) framework, designed to bring simplicity when moving data between a source and a destination service on the Internet. In this post, we present the framework as entry point into the world of SaaS-level data exchange and some associated research questions. Schlagw\u00f6rter: datascience, geospatial, integration, [&hellip;]<\/p>\n","protected":false},"author":497,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"ngg_post_thumbnail":0,"footnotes":""},"categories":[4],"tags":[67,123,125,124,122,121,126],"features":[],"class_list":["post-1112","post","type-post","status-publish","format-standard","hentry","category-research","tag-datascience","tag-geospatial","tag-integration","tag-opendata","tag-publicdata","tag-python","tag-saas"],"yoast_head":"<!-- This site is optimized with the Yoast SEO Premium plugin v27.2 (Yoast SEO v27.2) - https:\/\/yoast.com\/product\/yoast-seo-premium-wordpress\/ -->\n<title>Building a Singer.io tap for an open data source - Service Prototyping Lab<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\" \/>\n<meta property=\"og:locale\" content=\"en_GB\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Building a Singer.io tap for an open data source\" \/>\n<meta property=\"og:description\" content=\"Singer.io is an open-source JSON-based data shifting (ETL: extract, transform, load) framework, designed to bring simplicity when moving data between a source and a destination service on the Internet. In this post, we present the framework as entry point into the world of SaaS-level data exchange and some associated research questions. Schlagw\u00f6rter: datascience, geospatial, integration, [&hellip;]\" \/>\n<meta property=\"og:url\" content=\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\" \/>\n<meta property=\"og:site_name\" content=\"Service Prototyping Lab\" \/>\n<meta property=\"article:published_time\" content=\"2019-12-20T17:24:02+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2019-12-20T18:42:37+00:00\" \/>\n<meta name=\"author\" content=\"boru\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"boru\" \/>\n\t<meta name=\"twitter:label2\" content=\"Estimated reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\"},\"author\":{\"name\":\"boru\",\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/#\/schema\/person\/d8bb83e9bb2a61d61fbe34ccffd3cad5\"},\"headline\":\"Building a Singer.io tap for an open data source\",\"datePublished\":\"2019-12-20T17:24:02+00:00\",\"dateModified\":\"2019-12-20T18:42:37+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\"},\"wordCount\":1102,\"commentCount\":7,\"keywords\":[\"datascience\",\"geospatial\",\"integration\",\"opendata\",\"publicdata\",\"python\",\"saas\"],\"articleSection\":[\"Research\"],\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\",\"url\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\",\"name\":\"Building a Singer.io tap for an open data source - Service Prototyping Lab\",\"isPartOf\":{\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/#website\"},\"datePublished\":\"2019-12-20T17:24:02+00:00\",\"dateModified\":\"2019-12-20T18:42:37+00:00\",\"author\":{\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/#\/schema\/person\/d8bb83e9bb2a61d61fbe34ccffd3cad5\"},\"breadcrumb\":{\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#breadcrumb\"},\"inLanguage\":\"en-GB\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/\"]}]},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Startseite\",\"item\":\"https:\/\/blog.zhaw.ch\/splab\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Building a Singer.io tap for an open data source\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/#website\",\"url\":\"https:\/\/blog.zhaw.ch\/splab\/\",\"name\":\"Service Prototyping Lab\",\"description\":\"A Blog of the ZHAW Zurich University of Applied Sciences\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.zhaw.ch\/splab\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-GB\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.zhaw.ch\/splab\/#\/schema\/person\/d8bb83e9bb2a61d61fbe34ccffd3cad5\",\"name\":\"boru\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-GB\",\"@id\":\"https:\/\/secure.gravatar.com\/avatar\/e828fcec41f1cf1b96559a66bf5c987fe4a6fca8368dea825be4f46aa6995b50?s=96&d=mm&r=g\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/e828fcec41f1cf1b96559a66bf5c987fe4a6fca8368dea825be4f46aa6995b50?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/e828fcec41f1cf1b96559a66bf5c987fe4a6fca8368dea825be4f46aa6995b50?s=96&d=mm&r=g\",\"caption\":\"boru\"},\"url\":\"https:\/\/blog.zhaw.ch\/splab\/author\/boru\/\"}]}<\/script>\n<!-- \/ Yoast SEO Premium plugin. -->","yoast_head_json":{"title":"Building a Singer.io tap for an open data source - Service Prototyping Lab","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/","og_locale":"en_GB","og_type":"article","og_title":"Building a Singer.io tap for an open data source","og_description":"Singer.io is an open-source JSON-based data shifting (ETL: extract, transform, load) framework, designed to bring simplicity when moving data between a source and a destination service on the Internet. In this post, we present the framework as entry point into the world of SaaS-level data exchange and some associated research questions. Schlagw\u00f6rter: datascience, geospatial, integration, [&hellip;]","og_url":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/","og_site_name":"Service Prototyping Lab","article_published_time":"2019-12-20T17:24:02+00:00","article_modified_time":"2019-12-20T18:42:37+00:00","author":"boru","twitter_card":"summary_large_image","twitter_misc":{"Written by":"boru","Estimated reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#article","isPartOf":{"@id":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/"},"author":{"name":"boru","@id":"https:\/\/blog.zhaw.ch\/splab\/#\/schema\/person\/d8bb83e9bb2a61d61fbe34ccffd3cad5"},"headline":"Building a Singer.io tap for an open data source","datePublished":"2019-12-20T17:24:02+00:00","dateModified":"2019-12-20T18:42:37+00:00","mainEntityOfPage":{"@id":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/"},"wordCount":1102,"commentCount":7,"keywords":["datascience","geospatial","integration","opendata","publicdata","python","saas"],"articleSection":["Research"],"inLanguage":"en-GB","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/","url":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/","name":"Building a Singer.io tap for an open data source - Service Prototyping Lab","isPartOf":{"@id":"https:\/\/blog.zhaw.ch\/splab\/#website"},"datePublished":"2019-12-20T17:24:02+00:00","dateModified":"2019-12-20T18:42:37+00:00","author":{"@id":"https:\/\/blog.zhaw.ch\/splab\/#\/schema\/person\/d8bb83e9bb2a61d61fbe34ccffd3cad5"},"breadcrumb":{"@id":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#breadcrumb"},"inLanguage":"en-GB","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/blog.zhaw.ch\/splab\/2019\/12\/20\/building-a-singer-io-tap-for-an-open-data-source\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Startseite","item":"https:\/\/blog.zhaw.ch\/splab\/"},{"@type":"ListItem","position":2,"name":"Building a Singer.io tap for an open data source"}]},{"@type":"WebSite","@id":"https:\/\/blog.zhaw.ch\/splab\/#website","url":"https:\/\/blog.zhaw.ch\/splab\/","name":"Service Prototyping Lab","description":"A Blog of the ZHAW Zurich University of Applied Sciences","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.zhaw.ch\/splab\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-GB"},{"@type":"Person","@id":"https:\/\/blog.zhaw.ch\/splab\/#\/schema\/person\/d8bb83e9bb2a61d61fbe34ccffd3cad5","name":"boru","image":{"@type":"ImageObject","inLanguage":"en-GB","@id":"https:\/\/secure.gravatar.com\/avatar\/e828fcec41f1cf1b96559a66bf5c987fe4a6fca8368dea825be4f46aa6995b50?s=96&d=mm&r=g","url":"https:\/\/secure.gravatar.com\/avatar\/e828fcec41f1cf1b96559a66bf5c987fe4a6fca8368dea825be4f46aa6995b50?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/e828fcec41f1cf1b96559a66bf5c987fe4a6fca8368dea825be4f46aa6995b50?s=96&d=mm&r=g","caption":"boru"},"url":"https:\/\/blog.zhaw.ch\/splab\/author\/boru\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/posts\/1112","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/users\/497"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/comments?post=1112"}],"version-history":[{"count":10,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/posts\/1112\/revisions"}],"predecessor-version":[{"id":1125,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/posts\/1112\/revisions\/1125"}],"wp:attachment":[{"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/media?parent=1112"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/categories?post=1112"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/tags?post=1112"},{"taxonomy":"features","embeddable":true,"href":"https:\/\/blog.zhaw.ch\/splab\/wp-json\/wp\/v2\/features?post=1112"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}