My favourite API is a zipfile on the European Central Bank's website
csvbase.com
csvbase.com
IIRC it was by far the most downloaded file on the ECB website. Tons of people, including many financial institutions, downloaded it daily, and used it to update their own systems.
IIRC #2 in the minutes immediately after the daily scheduled time for publishing this file, there was a massive traffic spike.
It was a conscious decision to make it a simple CSV file (once unzipped): it made it possible to serve the file reliably, fast, and with little resources needed.
The small team responsible at the time for the ECB’S public website was inordinately proud of the technical decisions made to serve this data in a single static file. And rightly so.
(Scale8 is a web analytics and tag management software like google analytics)
Just in case anybody else is in the same boat you can change the url in the code to the new one yourself.
simgle zip file is really the easiest solution for cases when the file must absolutely be compressed
Eg, http://nginx.org/en/docs/http/ngx_http_gunzip_module.html
even a big old russet only has, what, like 32 bites?
For some, if you slice them, you might even end up with only 16 bits.
If I'm not missing the point here, this was, and still is, about offering the simplest, most reliable solution over a long period. This is a near perfect example of how to do exactly that. No changing formats, no moving requirements, no big swings in frameworks, apis, or even standards. And most importantly, no breaking your customers business workflows.
No emergency change requests from the outage team that has to be impacted by other areas and fit into the infrastructure's teams maintenance windows and their capacity to address that.
No rebalancing of workloads because Jane had to implement (or schedule the task and monitor it) that change, Joe had to check and verify that the external availability tests passed, and Annick had to sign off on the change complete, and now everyone isn't available for another OT window for the week.
Or something.
At least one of us is confused about history here; are you really saying that circa 2008, HTTP compression would have been considered immature or unstable?
1 - save on cpu usage, compress once, serve many
2 - with zip you can have some rudimentary data integrity checks (unzip -t)
Nginx does this: https://docs.nginx.com/nginx/admin-guide/web-server/compress...
Last entry: the directive is `gzip_static on;`
Just guessing. I have no idea :-)
I used to work for a large, old company whose products you have all bought and buy (and which shall remain nameless) about 15 years ago. I worked on data interchange between the systems used for record keeping of said products and various other downstream or parallel systems (same purpose, separate system because left over from merger/acquisition).
So basically bulk data import and export. The product was 15 years old at that point. Data interchange was via various fixed width or delimited (CSV but the C might be some other character or character sequence) files transferred to and from various SFTP servers.
It's been a while but there were probably like 20 or 30 different such data sources or exports going in and out. Worked like a charm. I bet they're still used without much change. The frontend was being rewritten at the time (old one was in Smalltalk).
But we didn't stop there: this SaaS for our open source browser product is entirely built like this: behind the scenes it's a collection of bash scripts that implement and execute the business operation of the SaaS.
So basically, it's a command-line interface to the SaaS. Think of it this way, say I didn't have a website, with login, and "click a button to open a browser", but instead people would write me letters, send me cheques, or call me on the phone. Then I can serve their requests manually, at the command line.
The reason I made it like this was:
- clear separation between thin web front-end and actual business logic
- nice command-line interface (options, usage, help, clear error messages) to business logic for maintenance and support to jump on and fix things
- inheritance of operating system permissions and user process isolation
- highly testable implementation
Maybe this is dumb, but I really like it. To me it's an architecture and approach that makes sense.
I'm sure this is not new, and I think a lot of good quality operations must be built via this way. I highly align with the author's stance of the composition of a few simple command line tools to get the job done.
Perhaps we can call this "unix driven development", or "unix-philosophy backend engineering"
browser product: https://github.com/dosyago/BrowserBoxPro (saas coming soonish)
You're being a jerk, someone shared something they were proud of that's relevant to the discussion. What's the problem?
sure, a lot of the criticisms of CSV are true, but given the above constraints it's really hard to beat them.
Architects would complain ZIP isn’t a format conform to their specs for this purpose. Compliance would complain there needs to be a check that no private informatie leaked. Risk that I should prevent bad actors from downloading the file. Web people that I need an approved change to add stuff to the site.
Perfect usecase for bittorrent protocol imho. Funny thing is you can serve files with bittorrent protocol from Amazon S3 without any additional complexity or charges I think.
There's a bunch of wrapper tools to make this particular pipeline easier. Also something like Datasette is great if you want a web view and some fancier features.
The book Recoding America has a lot of anecdotes to this effect; most of these situations reduce to a Congressional mandate that got misinterpreted along the way. My favorite was for an update to Social Security. The department in charge of the implementation swore that Congress was forcing them to build a facebook for doctors (literally where doctors could friend other doctors and message them). Congress had no such intention; it was actually 3rd party lobbying that wanted the requirement so they could build their own solution outside of government. Really crazy stuff.
Right, but 3rd party lobbying can't force anyone to do anything, whereas Congress can (and did) give this mandate the force of law. The fact that lobbyists got Congress to do something that they had "no such intention" to do is its own problem, but let's not lose sight of who is responsible for laws.
I agree with your broader point however. Congress needs to do a better job of owning outcomes.
I will decline to share my personal anecdote's about these companies because I am like 10+ years out of date, but I can tell you that most of these companies seemed to have certain very specific non-technical things in common.
(As a side note, I can understand why in years past it would cost multiple cents per page to physically photocopy a federal document - but it is absolutely absurd that already-digitized documents, documents which are fundamentally part of the precedent that decides whether our behavior does or doesn't cause civil or criminal liability, are behind a paywall for a digital download!)
All sorts of strange things happen with accessing US government data. But most agencies have a lot of excellent data available for free and motivated data scientists who want to make it available to you.
Sometimes they send questionnaires to data consumers.
Some of them look like they’re straight from the early 2000s.
Another thing is that privacy is dead and almost everything is deemed public information. Your address, mugshot, voter rolls, you name it, it’s all deemed public information.
But once you actually want to access information that is useful to society as whole, more often than not it’s behind a paywall. Despite this, they still call it “public” information because theoretically anyone can pay for it and get access.
It’s one of the first things I noticed when I moved to the US.
Another thing that I’ve noticed is that, if possible, there always needs to be a middle man inserted, a private corporation that can make a profit out of it.
You want to identify yourself to the government? Well you’re gonna need an ID.me account or complete a quiz provided to you by LexisNexis or one of the credit reporting agencies.
Why? How is it that the government of all entities, isn’t capable of verifying me themselves?
Zooming out even further you’ll start to recognize even more ancient processes you interact with in daily life.
The whole banking situation and the backbone that’s running it is a great example. The concept of pending transactions, checks and expensive and slow transfers is baffling to me.
It’s so weird, like ooh, aah this country is the pinnacle of technological innovation, yet in daily life there’s so much that hinges on ancient and suboptimal processes that you don’t see in, say, Western Europe.
My best guess is that’s this is because of a mix of lack of funding and politicians that wanting to placate profit seeking corporations.
Ironically, and I have no hard evidence for this because I’m too lazy to look into it, I suspect that on the long term it costs more to outsource it than it does to do it themselves.
/rant
In the USA you find these whole industries that exist due to the inadequacies of the old systems. E.g. venmo doesnt need to exist in Western Europe (and probably rest of world?) because person-to-person bank transactions are free and easy.
I mean... do they make you select one or more files, then navigate to another page to download your selected files?
Federal, State, local and hyper local solutions cannot be the same unless the financier is also the same.
You can search for companies, select the documents you’d like to see (like shareholder lists), then you go through a checkout process and pay 0 EUR (used to be like a few euros years ago), and then you can finally download your file. Still a super tedious process, but at least for free nowadays.
There are other ways to access the data on here, but they’re fragmented. It’s nicely organized here so it’s a bummer they make it hard to programmatically retrieve files once you find what you’re looking for.
we have lots of reporting in CSV, can't wait to start using it to run queries quickly
$> csvData = @"
Name,Department,Salary
John Doe,IT,60000
Jane Smith,Finance,75000
Alice Johnson,HR,65000
Bob Anderson,IT,71000
"@;
$> csvData
| ConvertFrom-Csv
| Select Name, Salary
| Sort Salary -Descending
Name Salary
---- ------
Jane Smith 75000
Bob Anderson 71000
Alice Johnson 65000
John Doe 60000
You can also then convert the results back into CSV by piping into ConvertTo-Csv $> csvData
| ConvertFrom-Csv
| Select Name, Salary
| Sort Salary -Descending
| ConvertTo-Csv
"Name","Salary"
"Jane Smith","75000"
"Bob Anderson","71000"
"Alice Johnson","65000"
"John Doe","60000" /tmp/> "Name,Department,Salary
::: John Doe,IT,60000
::: Jane Smith,Finance,75000
::: Alice Johnson,HR,65000
::: Bob Anderson,IT,71000" |
::: from csv |
::: select Name Salary |
::: sort-by -r Salary
╭───┬───────────────┬────────╮
│ # │ Name │ Salary │
├───┼───────────────┼────────┤
│ 0 │ Jane Smith │ 75000 │
│ 1 │ Bob Anderson │ 71000 │
│ 2 │ Alice Johnson │ 65000 │
│ 3 │ John Doe │ 60000 │
╰───┴───────────────┴────────╯They're a powerful pairing
Here I'll:
- Send the csv data from stdin (using echo and referred to in the command by -)
- Refer to the data in the query by stdin. You may also use the _t_N syntax (first table is _t_1, then _t_2, etc.), or the file name itself before the .csv extension if we were using files.
- Pipe the output to the table command for formatting.
- Also, the shape of the result is printed to stderr (the (4, 2) below).
$ echo 'Name,Department,Salary
John Doe,IT,60000
Jane Smith,Finance,75000
Alice Johnson,HR,65000
Bob Anderson,IT,71000' |
qsv sqlp - 'SELECT Name, Salary FROM stdin ORDER BY Salary DESC' |
qsv table
(4, 2)
Name Salary
Jane Smith 75000
Bob Anderson 71000
Alice Johnson 65000
John Doe 60000The document format doesn't seem to have much to do with the problem. I mean, if the CSV is replaced by a zipped JSON doc then the benefits are the same.
> Every time I have to fill a "shopping cart" for a US government data download I die a little.
Now that seems to be the real problem: too many hurdles in the way of a simple download of a statically-served file.
Being able to use things like jq and gron might make simple use cases extremely straightforward. I'm not aware of anything similarly nimble for CSV.
Those tools are either line- or table-oriented, and thus don't handle CSVs as a structured format. For instance, is there any easy way to use AWK to handle a doc where one line has N columns but some lines have N+1, with a random column entered in an undetermined point?
Both pawk and the recent version of awk can parse the CSV format correctly (not just splitting on commas, but also handling the quoting rules for example).
For example:
"Look, this contains \"quotes\"!",012345
Or: "Look, this contains ""quotes""!",012345
Or, for some degenerate examples: "Look, this contains "quotes"!",012345
Or: Look, this contains "quotes"!,012345
Or the spoor of a spreadsheet: "Look, this contains ""quotes""!",12345
Theoretically, JSON isn't immune to being hand-hacked into a semi-coherent mess. In practice, people don't seem to do that to JSON files, at least not that I've seen. Ditto number problems, in that in JSON, serial numbers and such tend to be strings instead of integers a "helpful" application can lop a few zeroes off of.Practically no formats actually pass those rules. Even plain text is bound to be "improved" by text editors frequently (uniformation of line endings, removal of data not in a known encoding, UTF BOM, UTF normalization, etc.)
Just don't do that.
Arbitrary CSV is not, in general, round-trippable.
You clearly haven't had to deal with tools that cannot read and write a JSON file without messing up and truncating large numbers.
Regular JSON would work fine for static file, and make Schema and links (JSON-LD) possible. But then the file could be any structure. JSONL works better for systems that assume line-based records, and are more likely to have consistent, simple records.
In practice, you open the file a disappointed customer sends you because your software doesn't parse it. You resist yelling at whomever decided to ever do CSV support in your software because you know what it's like. But the customer is important, so then either write a one-off converter for them to a more proper CSV format, or you add - not change - several lines of code in mainline to be able to also parse customer's format. God forbid maybe also write. Repeat that a couple of times. See the mess increasing. Decide to use a so-called battle-tested CSV parser after all, only to find out it doesn't pass all tests. Weep. Especially because at the sametime you also support a binary format which is 100 times faster to read and write and has no issues whatsoever.
I'm not blind for the benefits of CSV, but oh boy it would be nice if it would be, for starters, actually comma-separated.
CSV files can be a nightmare to work with depending where they come from and various liberties that were taken when generating the file or reading the file.
Use a goddam battle tested library people and don't reinvent the wheel. /oldman rant over
However, I would recommend using a tested library to do the parsing, sqlite for example, rather than rolling your own. Unless you have to of course.
I've found template injection in a CSV upload before because they didn't anticipate a doublequote being syntactically relevant or something.
It was my job to find these things and I still felt betrayed by a file format I didn't realize wasn't just comma separated values only.
https://github.com/mafintosh/csv-parser/blob/master/index.js
Looking at the parser I see a few problems with it just by skimming the code. I'm not saying it wouldn't work or that it's not good enough for certain purposes.
You can pick on some of the corner case issues here: https://github.com/mafintosh/csv-parser/issues Also look at ones that were solved. https://github.com/mafintosh/csv-parser/pulls
Some interesting ones: https://github.com/mafintosh/csv-parser/pull/121 https://github.com/mafintosh/csv-parser/pull/151 https://github.com/mafintosh/csv-parser/issues/218
The author of the library probably has learned, the hard way many many lessons (and probably also decided to prioritize some of the requested issues / feature requests along the way).
The above is not meant as a ding on the project itself and I am sure it is used successfully by many people. The point here is that your claim that you can easily write a csv parser in 200 lines of code does not hold water. It's anything but easy and you should use a battle tested library and not reinvent the wheel.
Character set handling isn't really an issue for JavaScript as strings are always utf-16. When a file is read into a string the runtime handles the needed conversion.
As for handling large files, I've used this with 50mb CSVs, which would need a 32bit integer to index. Is that large enough? It's not like windows notepad which can only read 64kb files.
I think the original point I was making still stands.
CSV is remarkably robust in practice.
You can load the zip file as a stream, read the CSV line by line, transform it, and then load it to the db using COPY FROM stdin (assuming Postgres).
which wouldn't exist if the api is simply just a single CSV file?
at least with a zip, the CRC exists (an incomplete zip file is detectable, an incomplete, but syntactically correct CSV file is not)
Upstream can easily f-up and (accidentally) delete production data if you do this on a live db. Which is why PostgreSQL and nearly all other DBS have a miriad of tools to solve this by not doing it directly on a production database
What if someone screws up the zip and instead of 10000 today, it’s only 10?
In either the solution is probably to check rough counts and error if not reasonable.
One of the first things I played around with was using Python to get that file, unzip it, then iterate through the xml files grabbing the info I wanted and putting it into ElasticSearch to make it searchable then putting an angular front end on it.
I used to have it published somewhere but I think I let it all die. :(
They also work nicely as an interop between different stacks (Cobol <-> Ruby).
One concrete example is the French standard to describe Electrical Vehicles Charge Points, which is made of 2 parts (static = position & overall description, dynamic = current state, occupancy etc). Both "files" are just CSV files:
https://github.com/etalab/schema-irve
Both sub-formats are specified via TableSchema (https://frictionlessdata.io/schemas/table-schema.json).
Files are produced directly by electrical charge points operators, which can have widely different sizes & technicality, so CSV works nicely in that case.
I've worked in gov/civic tech for 5+ years and, as you're probably aware, there is now a highly lucrative business in centralizing and consequently selling easy access to this data to lobbyists/fortune 500s/nonprofits.
USDS/18f are sort of addressing this, but haven't really made a dent in the market afaik since they're focusing on modernizing specific agencies rather than centralizing everything.
The whole data set could have been zipped into a <1MB file but instead a “solution architect” go their hands on the requirements. We ended up with a slow API because they wouldn’t let us cache results in case the data had changed just as it was requested. And an overly complex webhook system for notifying subscribers of changes to the data.
A zip file probably was too simple, but not far off what was actually required.
If the API was used rarely, that would be even more of an argument for a simple implementation and not a complex system involving webhooks.
cat /api/version.txt
2023.01.01
ls /api
version.txt data.zip 2023.01.01-data.zipThe version file can be quired at least the two ways:
the ETag/If-Modified-Since way (metadata only)
content itself
The best part with the last one - you don't need semver shenanigans. Just compare it with the latest dloaded copy, if version != dloaded => do_the_thing
Apache/nginx do it just fine...
If you want to get really fancy, offer an additional webhook which triggers when the file changes - so clients know when to redownload it and don't have to poll once a day.
...or make a script that sends a predefined e-mail to a mailing list when there is a change.
See above. Also you can just publish the version in DNS with a long enough TTL
I completely agree and csvbase already implements this (so does curl btw), try:
curl --etag-compare stock-exchanges-etag.txt --etag-save stock-exchanges-etag.txt https://csvbase.com/meripaterson/stock-exchanges.parquet -OMy concern was "what if file is updated while it's mid-download" but Linux would probably keep the old version of the file until the download finishes (== until file is still open by webserver process). Probably. It's better to test
If it's updated in place, did the web server read the whole thing into a buffer or is it doing read/send in a loop?
I don't want to seem overly negative -- zipped CSV files are fantastic when you want to import lots of data that you then re-serve to users. I would vastly prefer it over e.g. protobufs that are currently used for mass transit system's live train times, but have terrible support in many languages.
But it's incredibly wasteful to treat it like an API to retrieve a single value. I hope nobody would ever write something like that into an app...
(So the article is cool, it's just the headline that's too much of a "hot take" for me.)
I'm talking about zipped CSV files as easier to use than protobuf files. Neither is a request in this comparison.
For sorted data you only need the relevant row groups which can be tunable to sensible sizes for your data and access pattern.
"Why parquet files are my preferred API for bulk open data" https://www.robinlinacre.com/parquet_api/
Just add ".parquet" - eg https://csvbase.com/meripaterson/stock-exchanges.parquet
I have got some bad news for you...
Not directly API related, but I remember supporting some land management application, and a new version of their software came out. Before that point it was working fine on our slow satellite offices that may have been on something like ISDN at the time. New version didn't work at all. The company said to run it on an RDP server.
I thought their answer was bullshit and investigated what they were doing. One particular call, for no particular reason was doing a 'SELECT * FROM sometable' for no particular reason. There were many other calls that were using proper SQL select clauses in the same execution.
I brought this up with the vendor and at first they were confused as hell how we could even figure this out. Eventually they pushed out a new version with a fixed call that was usable over slow speed lines, but for hells sake, how could they not figure that out in their own testing and instead pushed customers to expensive solutions?
This one is easy. Testing with little data on fast network (likely localhost).
I’ve seen code that was using an ORM, where they needed to find some data that matched certain criteria. With plain SQL it would have resulted in just a few rows of data. Put instead with their use of the ORM they ended up selecting all rows in the table from the database, and then looping over the resulting rows in code.
The result of that was that the code was really slow to run, for something that would’ve been super fast if it wasn’t for the way they used the ORM in that code.
Product.where(category_id: xx).filter {|p| p.name.include?("computer") }
vs Product.where(category_id: xx).where("name like '%?%'", "computer")
PS: I know there's an Arel way of doing the above without putting any SQL into the app -- use your imagination and pretend that's what I did, because I still don't have that memorized :)565 KB, that's about 3 minutes download on a 28.8kbps modem I started getting online with...
I often wonder what I'll think of technology in say another 20 years but I can never tell if it's all just some general shift in perspective or, as you look farther back, if certain people were always just about a certain perspective (e.g. doing the most given the constraints) and technology changes enough that different perspectives (e.g. getting the most done easily for the same unit of human time) become the most common users for the newer stuff and that these people will also have a different perspective than the ones another 20 years down the line from them and so on.
For example, maybe you think it's crazy to ask for the exact piece of data you need, I think it's crazy to do all the work to not just grab the whole half MB and just extract what you need quickly on the client side as often as you want, and someone equidistant from me will think it's nuts to not just feed all the data into their AI tool and ask it "what is the data for ${thing}" instead of caring about how the data gets delivered at all. Or maybe that's just something I hope for because I don't want to end up a guy who says everything is the same just done slower on faster computers since that seems... depressing in comparison :).
If this were being used to get the current rates, then yes, it would be a terrible design choice. But there are other services for that. This one fits its typical use case.
565 KB + the logic to get the big one is miniscule today by any reasonable factor.
Something like:
curl https://www.ecb.europa.eu/stats/eurofxref/api?fields=Date&order=USD&limit=1I've been building loads of financial software, both back and front ends. In frontend world it, sadly is quite common to send that amount of "data" over wires before even getting to actual data
And in backends it's really a design decision. There's nothing faster than a Cron job parsing echange rates nightly and writing them to a purpose designed todays-rates.json served as static file to your mobile, web or microservices apps.
Nothing implies your mobile app has to consume this zip-csv_over_http
It's one of those headlines that we'd never allow if it were an editorialization by the submitter (even if the submitter were the author, in this case), but since it's the article's own subtitle, it's ok. A bit baity but more in a whimsical than a hardened-shameless way.
(I'm sure you probably noticed this but I thought the gloss might be interesting)
(Yes, this assumes you keep the state, and you have to pay the price of the first download + state-keeping. Yes, it's also inefficient if you just need to get the EUR/JPY rate from 2007-08-22 a single time.)
Very WIP but check out my current "research quality" code here: https://pypi.org/project/csvbase-client/
That's if downloading a few hundred kB more per day matters to you. It probably doesn't.
> curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | gunzip | sqlite3 ':memory:' '.import /dev/stdin stdin' "select Date from stdin order by USD asc limit 1;"
Error: in prepare, no such column: Date (1)
There is a typo in the example (that is not in the screenshot): you need to add a -csv argument to sqlite.Erk - readding it and busting the cache. After I put my kids to bed I will figure out what is wrong
EDIT: the reason it works for me is because I've got this config in ~/.sqliterc:
.separator ','
Apparently at some point in the past I realised that I mostly insert csv files into it and set that default.If you were to ask my own tool preferences which I use for $DAYJOB: pandas for data cleaning and small data because I think that dataframes are genuinely a good API. I use SQL for larger datasets and I am not keen on matplotlib but still use it for graphs I have to show to other people.
> Why was the Euro so weak back in 2000? It was launched, without coins or notes, in January 1999. The Euro was, initially, a sort of in-game currency for the European Union. It existed only inside banks - so there were no notes or coins for it. That all came later. So did belief - early on it didn't look like the little Euro was going to make it: so the rate against the Dollar was 0.8252. That means that in October 2000, a Dollar would buy you 1.21 Euros (to reverse exchange rates, do 1/rate). Nowadays the Euro is much stronger: a Dollar would buy you less than 1 Euro.
Even if the Euro initially only existed electronically, it still had fixed exchange rates with the old currencies of the EU zone members. Most importantly with the established and well-trusted Deutsche Mark of Germany.
So any explanation of 'why was the Euro initially weak' also has to explain why the DEM was weak at the time. The explanation given in that paragraph doesn't sound like it's passing that test.
> Some things we didn't have to do in this case: negotiate access (for example by paying money or talking to a salesman); deposit our email address/company name/job title into someone's database of qualified leads, observe any quota; authenticate (often a substantial side-quest of its own), read any API docs at all or deal with any issues more serious than basic formatting and shape.
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
| gunzip \
| sqlite3 ':memory:' '.import /dev/stdin stdin' \
"select Date from stdin order by USD asc limit 1;"
SQLite can read and write zip files.https://sqlite.org/zipfile.html
Is it possible to use sqlite3 instead of gunzip for decompression.
If you don't mind saving the file to disk, you can do:
sqlite3 -newline '' ':memory:' "SELECT data FROM zipfile('eurofxref-hist.zip')" \
| sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \
"select ...;"
Doing this without a temporary file is tricky; for example `readfile('/dev/stdin')` doesn't work because SQLite tries to use seek(). Here is a super ugly method (using xxd to convert the zipfile to hex, to include it as a string literal in the SQL query): curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
| { printf "SELECT data FROM zipfile(x'"; xxd -p | tr -d '\n'; printf "')"; } \
| sqlite3 -newline '' \
| sqlite3 -csv ':memory:' '.import /dev/stdin stdin' \
"select ...;"> That data comes as a zipfile, which gunzip will decompress.
Doesn't gunzip expect gzip files, as opposed to ZIP (i.e. .zip) files?
On Linux I get further (apparently Linux gunzip is more tolerant of the format error than the macOS default one), but there, I then run into:
> Error: no such column: Date
As for the second error, I think you might be trying to import an empty file or maybe an error message? Probably related to the first problem.
Only on some gzip implementations, e.g. the one shipping with many Linux distributions. It doesn't work on macOS, for example.
# gunzip -d master.zip gunzip: master.zip: unknown suffix -- ignored
Ubuntu's version of g(un)zip actually does tolerate ZIP inputs though; for that usage, just omit the '-d'.
> Files created by zip can be uncompressed by gzip only if they have a single member compressed with the “deflation” method. This feature is only intended to help conversion of tar.zip files to the tar.gz format.
-- https://www.gnu.org/software/gzip/manual/html_node/Overview....
curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | funzip | sqlite3 -csv ':memory:' '.import /dev/stdin stdin' "select Date,USD from stdin order by USD asc limit 1;" # !/bin/bash
ARG1=${1:-'select Date from stdin order by USD asc limit 1;'}
curl -o - -Ls https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip \
| funzip | sqlite3 -csv ':memory:' '.import /dev/stdin stdin' "${ARG1}"
# Usage:
$ ./run.sh 'select * from stdin limit 1;'1. inconsistent binary formats, float precision edge conditions, and unknown Endianness. Thus, assuming the Marshalling of some document is reliable is risky/fragile, so pick some standard your partners also support... try XML/XSLT, BSON, JSON, SOAP, AMQP+why, or even EDIFACT.
2. "dump and load" is usually inefficient, with an exception when the entire dataset is going to change every time (NOAA weather maps etc.)
3. Anyone wise to the 42TiB bzip 18kiB file joke is acutely aware of what compressed files can do to server scripts.
4. Tuning firewall traffic-shaping for a web-server is different from a server designed to handle large files. Too tolerant rules causes persistent DDoS exposure issues, and too strict causes connections to become unreliable when busy/slow.
5. Anyone that has to deal with CSV files knows how many proprietary interpretations of a simple document format emerge.
Best of luck, and remember to have fun =)
One learns over the years that every program stage/interface must sanitize input, sanity check formats, and isolate data on a per account log.
Did the user intend "P=0/O" as a joke... we'll never know. =)
As someone with a lot of data experience, and in particular including financial data (trading team in a hedge fund) I definitely prefer the column format where each currency is its own column.
That way it's very easy to filter for only the currencies I care about, and common data processing software (e.g. Pandas for Python) natively support columns, so you can get e.g. USDGBP rate simply by dividing two columns.
The biggest drawback of `eurofxref-hist` IMO is that it uses EUR as the numeraire (i.e. EUR is always 1), whereas most of the finance world uses USD. (Yeah I know it's in the name, it's just that I'd rather use a file with USD-denominated FX rates, if one was available.)
That's not completely true. In most SQL databases you can query information about the table, and this is true for Sqlite too. You cannot use this information to have a query with a "dynamic" number of columns, but you can always generate the query from the metadata and then executed the generated queries. It is not exactly fun to do as a one-liner, but it works:
> curl 'https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.csv' | sqlite3 -csv ':memory:' '.import /dev/stdin stdin' '.mode list' '.output query.sql' "select 'select Date, ''' || name || ''', ' || name || ' from stdin ;' from pragma_table_info('stdin') where cid > 0 and cid < 42;" '.output stdout' '.mode csv' '.read query.sql'
(yes, you're also left with some temporary file on the computer).
> Date,USD,JPY,BGN,CYP,CZK,DKK,EEK,GBP,HUF,LTL,LVL,MTL,[and on, and on]
> When doing filters and aggregations, life is easier if the data is in "long" format, like this:
> Date,Currency,Rate
> Switching from wide to long is a simple operation, commonly called a "melt". Unfortunately, it's not available in SQL.
> No matter, you can melt with pandas:
> curl -s https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | \ gunzip | \ > python3 -c 'import sys, pandas as pd > pd.read_csv(sys.stdin).melt("Date").to_csv(sys.stdout, index=False)'
You sure this can't be done in SQL? I myself don't know enough SQL, but I believe that you can decompose a single row into multiple rows in SQL by joining multiple queries using UNION ALL:
https://stackoverflow.com/questions/46217564/converting-sing...
https://stackoverflow.com/questions/52279384/how-do-i-melt-a...
Now the question is, how to make this less verbose (so that the query doesn't need to mention each of the 42 columns)
Amazon S3 let's you query csv files already loaded in buckets which is interesting but I haven't used yet.
One company I worked at a long time ago used free dropbox accounts as a ftp like drop that they would consume. Was hilarious and it worked well and was easy to stay under the free limits.
$> (invoke-webrequest "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.csv").Content
| ConvertFrom-Csv
| sort "usd"
| select "date" -first 1
Date
----
2000-10-26
Doing it with a zip file would be a little more verbose since there is no built in "gunzip" type command which operates on streams, but you can write one which does basically that out of built in .Net functions: function ConvertFrom-Zip {
param(
[Parameter(Position=0, Mandatory=$true, ValueFromPipeline=$true)]
[byte[]]$Data
)
process {
$memoryStream = [System.IO.MemoryStream]::new($Data)
$zipArchive = [System.IO.Compression.ZipArchive]::new($memoryStream)
$outputStreams = @()
foreach ($entry in $zipArchive.Entries) {
$reader = [System.IO.StreamReader]::new($entry.Open())
$outputStreams += $reader.ReadToEnd()
$reader.Close()
}
$zipArchive.Dispose()
$memoryStream.Dispose()
return $outputStreams
}
}
and call it like: # unary "," operator required to have powershell
# pipe the byte[] as a single argument rather
# than piping each byte individually to
# ConvertFrom-Zip
$> ,(invoke-webrequest "https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip").Content
| ConvertFrom-Zip
| ConvertFrom-Csv
| sort "usd"
| select "date" -first 1
Date
----
2000-10-26
I love powershell /tmp> # be kind to the server and only download the file if it's updated
/tmp> curl -s -o /tmp/euro.zip -z /tmp/euro.zip https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip
/tmp> unzip -p /tmp/euro.zip | from csv | select Date USD | sort-by USD | first
╭──────┬────────────╮
│ Date │ 2000-10-26 │
│ USD │ 0.83 │
╰──────┴────────────╯
(I removed the pipe to gunzip because 1. gunzip doesn't work like that on mac and 2. it's not something you should expect to work anyway, zip files often won't work like that, they're a container and their unzip can't normally be streamed)I wonder if their webserver supports the If-modified-since http header
tl;dr: the server doesn't support that header, but since the response does include a Last-Modified header, curl helpfully aborts the transfer if the Last-Modified date is the same as the mtime of the previously downloaded file.
One downside is that dev experience is pretty bad in my opinion. It took me years of fiddling in my free time to realize that if you happen to try to use the CSVs near a schedule change, you don't know what data you're missing, or that you're missing data, until you go to use it. My local agency doesn't publish historical CSVs so if you just missed that old CSV and need it, you're relying on the community to have a copy somewhere. Similarly, if a schedule change just happened but you failed to download the CSV, you don't know until you go to match up IDs in the APIs.
[1]: https://github.com/andrewmcwattersandco/programming-language...
I get the satisfaction as a curiosity, but other than that, to me that wouldn't be enough to make the my "favorite" or even "good".
http get https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.zip | gunzip | from csv | sort-by USD | first | get Date
Unless there is caching going on here? Perhaps a CDN cache on the server side?
See:
https://news.ycombinator.com/item?id=37528558
by llimllib
Specifically:
curl -o /tmp/euro.zip -z /tmp/euro.zip
Option o is output, option z says"Request a file that has been modified later than the given time and date, or one that has been modified before that time."
And:
https://news.ycombinator.com/item?id=37529690
by sltkr:
"the server doesn't support that header, but since the response does include a Last-Modified header, curl helpfully aborts the transfer if the Last-Modified date is the same as the mtime of the previously downloaded file."
And you could use awk to do the melt if you don’t have python and pandas.
#!/bin/sh
set -eu
u=https://www.ecb.europa.eu/stats/eurofxref/eurofxref.zip
ftp -Vo - $u | zcat | sed 's/, $//' | awk -F ', ' '
NR == 1 {
for(i = 1; i <= NF; ++i) {
col[$i] = i
c = ($i != "GBP") ? $i : "EUR"
printf "%s%s", c, (i < NF) ? "\t" : "\n"
}
}
NR >= 2 {
printf "%s\t", $1
for(i = 2; i <= NF; ++i) {
c = (i != col["GBP"]) ? $i/$(col["GBP"]) : $i
printf "%s%s", c, (i < NF) ? "\t" : "\n"
}
}
'
Fetches the latest rates and converts the base currency to GBP and format to TSV. Then I run a cron(8) job 15~30 minutes after the release each work day and dump the output so that I can retrieve it from httpd(8) over HTTPS to be injected into my window manager status bar. A pretty elegant solution that spares the ECB from my desktops hammering their server. All with tools from the OpenBSD base install.Except not versioned I suppose, but if the only version is LATEST, then maybe it is.
gunzip: unknown compression formatAll. Ways. (TM)