Fork me on GitHub
#beginners
<
2023-12-18
>
Joseph Graham06:12:41

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)))

Robert Wood06:12:31

Would you be looking for something like this?

(defn read-edn-file [file-path]
  (edn/read-string
   (slurp file-path)))

Joseph Graham07:12:22

ah OK. I did actually think of that I just wasn't sure if slurp was considered safe

Robert Wood07:12:41

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?

Joseph Graham07:12:25

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."

delaguardo07:12:50

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

seancorfield07:12:00

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

dvingo13:12:14

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 interaction

Alex Miller (Clojure team)13:12:48

The 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.

👍 1
2
leifericf14:12:52

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?

Jakob Durstberger14:12:04

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.

👍 1
dpsutton14:12:36

(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"))))

💡 1
leifericf08:12:03

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 []

leifericf08:12:10

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:

1
ghadi14:12:27

(fn [ctoken] (fetch-stacks (cond-> base-request ctoken (assoc :whateverKey ctoken)) parse-response))

👍 1
ghadi14:12:45

not sure where the continuation token goes in your API, but cond-> is your friend here

leifericf08:12:39

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.