This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2023-12-18
Channels
- # adventofcode (12)
- # asami (2)
- # babashka (95)
- # beginners (17)
- # biff (5)
- # calva (1)
- # clojure (90)
- # clojure-europe (15)
- # clojure-nl (1)
- # clojure-norway (8)
- # clojure-uk (6)
- # clojuredesign-podcast (2)
- # clojurescript (34)
- # clr (1)
- # community-development (42)
- # cursive (11)
- # data-science (1)
- # datomic (13)
- # graalvm (5)
- # hoplon (2)
- # hyperfiddle (32)
- # off-topic (1)
- # pathom (6)
- # releases (1)
- # shadow-cljs (25)
- # squint (4)
- # xtdb (10)
is it supposed to be this complicated to import some EDN? can't find a simpler way to do it
(defn read-edn-file [file-path]
(with-open [reader (java.io.PushbackReader. (java.io.FileReader. file-path))]
(edn/read reader)))
Would you be looking for something like this?
(defn read-edn-file [file-path]
(edn/read-string
(slurp file-path)))
ah OK. I did actually think of that I just wasn't sure if slurp was considered safe
As far as I am aware there are no concerns with using slurp, but what do you mean by it being ‘safe’, Is there anything in particular you are worried about?
I don't have any concerns no. Just in https://clojuredocs.org/clojure.edn it implies some extra safety checks are done:
"Do not use the read-*
functions in clojure.core
to deserialize untrusted Clojure code, as http://www.learningclojure.com/2013/02/clojures-reader-is-unsafe.html."
this is about clojure.core/read
and clojure.core/read-string
just use their siblings from clojure.edn
namespace you or should be fine.
btw, source for slurp is very close to what you wrote at the beginning
https://github.com/clojure/clojure/blob/clojure-1.11.1/src/clj/clojure/core.clj#L7009
There's also
which avoids Java interop for the original code you posted https://clojure.github.io/clojure/clojure.java.io-api.html#clojure.java.io/reader
I added the following helper that I use in all my projects, makes it easier to track down typos in edn files
(defn load-edn
"Load edn from an io/reader source
Tries to read as resource first then filename.
original
"
[source]
(try
(with-open [r (io/reader (io/resource source))] (edn/read (PushbackReader. r)))
(catch Exception e
(try
;; try just a file read
(with-open [r (io/reader (if (string? source) (io/file source) source))] (edn/read (PushbackReader. r)))
(catch IOException e
(printf "Couldn't open '%s': %s\n" source (.getMessage e)))
(catch RuntimeException e
(printf "Error parsing edn file '%s': %s\n" source (.getMessage e)))))))
looking at it again though, it needs to be cleaned up to better deal with file/resource distinction, but yea - to your original question, it is this verbose unless you're just wanting a quick repl interactionThe major downside of using slurp is you build a big intermediate string. For files you know to be “small” (few k) that’s no biggie. The code you posted up top is about the closest, although you want the Clojure variant LineNumberingPushbackReader. This is all harder than it should be (and I am glossing over buffering which further complicates things). We have a ticket for this and I spent some time working on it recently but we pushed it out of 1.12. Hoping we can make this better in next release.
I struggle understanding how to use iteration
(https://clojuredocs.org/clojure.core/iteration) to retrieve all pages from a batch-based API. When there are more things to get, the response contains a continuation token that must be included as a URL parameter in the following request(s).
This is what I've currently got (working code), which only fetches the first batch:
https://gist.github.com/leifericf/2574d715a938b9fb0e98418ba4d77f36
The returned @stacks
map looks like this:
{"stacks"
[{"orgName" "my-org",
"projectName" "my-project",
"stackName" "my-stack-1",
"lastUpdate" 1692103259,
"resourceCount" 14}
{"orgName" "my-org",
"projectName" "my-project",
"stackName" "my-stack-2",
"lastUpdate" 1695911748,
"resourceCount" 16}
...],
"continuationToken"
"XYZ"}
This gives me the continuation token: (get @stacks "continuationToken")
But I don't understand how to "plug it in" to the iteration
function.
Is it possible to use iteration
to get all the pages with my existing fetch-stacks
function as step
?I can't see you actually using iteration
in the gist you posted.
I haven't used iteration much myself yet, but you need to pass a function to :kf
. In the example docs you can see the person using :kf :next-page
which will use the keyword as a function and pull next page from the response and pass it on.
(let [fetch {"initial-token" {"stacks" [{:project 1} {:project 2}]
"continuationToken" "XYZ"}
"XYZ" {"stacks" [{:project 3} {:project 4}]
"continuationToken" "ZZZ"}
"ZZZ" {"stacks" [{:project 5} {:project 6}]}}]
(into [] cat (iteration fetch ;; map lookup, but could easily be an http call
:somef (comp seq #(get % "stacks"))
:initk "initial-token"
:vf #(get % "stacks")
:kf #(get % "continuationToken"))))
Thanks for your help, guys! Here's a potentially dumb follow-up question for you, @U11BV7MTK: How do I obtain the initial token for initk
? Do I need to make the first API call outside (for example in the let
) of iteration
to obtain the first token and then hand that over to iteration
? Or can iteration
also make the first API call?
This is what I've got, and it runs:
(def all-stacks
(let [fetch (fetch-stacks base-request parse-response)]
(into [] cat (iteration @fetch
:somef (comp seq #(get % "stacks"))
:initk (get fetch "continuationToken")
:vf #(get % "stacks")
:kf #(get % "continuationToken")))))
But it returns an empty array []
Oh, I think I understand what I'm doing wrong. @fetch
is the response value from the initial call, and not a function to get the next page. :man-facepalming:
(fn [ctoken] (fetch-stacks (cond-> base-request ctoken (assoc :whateverKey ctoken)) parse-response))
Thanks for the tips, @U050ECB92! The continuation token should be a query parameter in the request. https://gist.github.com/leifericf/2574d715a938b9fb0e98418ba4d77f36?permalink_comment_id=4800097#gistcomment-4800097 shows my current code, which only fetches the first “page” from the API. I will try to get iteration
working today.