Liking cljdoc? Tell your friends :D

pharmacist.data-source

A data source is the declarative description of a single remote source of data. It must include a reference to a function (or an id if using multi-method dispatch), and can include additional information about parameters, dependencies on other sources/initial parameters, the maximum number of retries, and how to fetch child sources.

(require '[pharmacist.data-source :as data-source]
         '[pharmacist.result :as result])

(defn fetch-playlist [source]
  (result/success {:id (-> source ::data-source/params :playlist-id)}))

(def playlist-data-source
  {::data-source/fn #'fetch-playlist
   ::data-source/params {:playlist-id 42}})

Fetch functions

Pharmacist will try the following three ways to fetch your source, in order of preference:

  1. Call the function in :pharmacist.data-source/fn
  2. Call the function in :pharmacist.data-source/async-fn
  3. Dispatch fetch on the source's :pharmacist.data-source/id

All of these functions accept the fully resolved source as their only argument. The return value from the function passed to :pharmacist.data-source/fn should be a pharmacist.result. The return value from :pharmacist.data-source/async-fn should be a clojure.core.async channel that emits a single message which should be a pharmacist.result.

If the fetch function throws an exception, or otherwise fails to meet its contract, the error will be wrapped in a descriptive [[parmacist.result]].

Parameters and dependencies

In its simplest form, the parameter map is just a map of values to pass to the fetch function. To further parameterize the data source, the parameter map can depend on values that will be provided as the full prescription is filled, or even resulting values from other sources. Dependencies are declared with a vector that has the :pharmacist.data-source/dep keyword set as meta-data:

(require '[pharmacist.data-source :as data-source])

(def playlist
  {::data-source/fn #'fetch-playlist
   ::data-source/params {:id ^::data-source/dep [:playlist-id]}})

Now Pharmacist will look for :playlist-id in the initial parameters passed to pharmacist.prescription/fill or other sources in the prescription. It will then retrieve the source and pass the full result as the :id parameter to the fetch-playlist source. If you just need to pick a single value from the dependency, use a path:

(def playlist
  {::data-source/fn #'fetch-playlist
   ::data-source/params {:id ^::data-source/dep [:user :id]}})

In this updated example, the :id parameter will receive the value of :id in the result of the :user data source - or the :id key of the map passed as :user in the initial parameters:

(pharmacist.prescription/fill
  {:playlist playlist}
  {:params {:user {:id 42}}})

When the keys of the dependency are the same as the parameters your source expects, like above (:id => [:user :id]), you can just make the whole parameters map a dependency:

(def playlist
  {::data-source/fn #'fetch-playlist
   ::data-source/params ^::data-source/dep [:user]})

Collection sources

Pharmacist can fetch collections where you don't know upfront how many sources you'll find. To do this you must provide two source definitions: one that defines a single source in the collection, and another that specifies how many times to fetch that source, and parameters for each individual fetch.

Imagine that you wanted to fetch all of a user's playlists. First define a function that receives a user and returns a vector of all their playlists:

(require '[pharmacist.result :as result])

(defn all-playlists [{::data-source/keys [params]}]
  ;; params is now the user - find their playlists from the map, over HTTP, from
  ;; disk, or whatever
  (result/success (:playlists params)))

Then define the prescription with the collection and the item source:

(require '[pharmacist.data-source :as data-source])

(def prescription
  {:playlist {::data-source/fn #'fetch-playlist
              ::data-source/params {:id ^::data-source/dep [:playlist-id]}}

   :user-playlists {::data-source/fn #'all-playlists
                    ::data-source/params {:user ^::data-source/dep [:user]}
                    ::data-source/coll-of :playlist}})

You can now fill this with an initial user (you could of course also get the user from yet another data source):

(pharmacist.prescription/fill
  prescription
  {:params {:user {:playlists [{:id 1}
                               {:id 2}]}}})

The :playlist source will be fetched twice - once with {:id 1} as its parameters, and once with {:id 2}. The parameters returned from the selection function are merged into the map in the prescription, so in the above example, {:id ^::data-source/dep [:playlist-id]} isn't strictly necessary, but it is recommended, because it documents the expected parameters and it makes the source usable outside of the collection as well.

In order to load a different selection of the collection, define another selection function, and make it a ::data-source/coll-of the same item source.

Data source keys

keydescription
::data-source/idKeyword identifying this source. Can be inferred from ::fn or ::async-fn, see id
::data-source/fnFunction that fetches this source. Should return a pharmacist.result
::data-source/async-fnFunction that fetches this source asynchronously. Should return a core.async channel that emits a single pharmacist.result
::data-source/paramsParameter map to pass to the fetch function
::data-source/retriesThe number of times ::result/retryable? results from this source can be retried
::data-source/timeoutThe maximum number of milliseconds to wait for this source to be fetched.
::data-source/coll-ofThe type of source this source is a collection of
::data-source/cache-paramsA collection of paths to extract as the cache key, see [[pharmacist.cache/cache-params]]
::data-source/cache-depsThe dependencies required to compute the cache key. Usually inferred from ::data-source/cache-params, see [[pharmacist.cache/cache-deps]]
A data source is the declarative description of a single remote source of data.
  It must include a reference to a function (or an id if using multi-method
  dispatch), and can include additional information about parameters,
  dependencies on other sources/initial parameters, the maximum number of
  retries, and how to fetch child sources.

```clj
(require '[pharmacist.data-source :as data-source]
         '[pharmacist.result :as result])

(defn fetch-playlist [source]
  (result/success {:id (-> source ::data-source/params :playlist-id)}))

(def playlist-data-source
  {::data-source/fn #'fetch-playlist
   ::data-source/params {:playlist-id 42}})
```

  ## Fetch functions

  Pharmacist will try the following three ways to fetch your source, in order of
  preference:

  1. Call the function in `:pharmacist.data-source/fn`
  2. Call the function in `:pharmacist.data-source/async-fn`
  3. Dispatch [[fetch]] on the source's `:pharmacist.data-source/id`

  All of these functions accept the fully resolved source as their only
  argument. The return value from the function passed to
  `:pharmacist.data-source/fn` should be a [[pharmacist.result]]. The return
  value from `:pharmacist.data-source/async-fn` should be a clojure.core.async
  channel that emits a single message which should be a [[pharmacist.result]].

  If the fetch function throws an exception, or otherwise fails to meet its
  contract, the error will be wrapped in a descriptive [[parmacist.result]].

  ## Parameters and dependencies

  In its simplest form, the parameter map is just a map of values to pass to the
  fetch function. To further parameterize the data source, the parameter map can
  depend on values that will be provided as the full prescription is filled, or
  even resulting values from other sources. Dependencies are declared with a
  vector that has the `:pharmacist.data-source/dep` keyword set as meta-data:

```clojure
(require '[pharmacist.data-source :as data-source])

(def playlist
  {::data-source/fn #'fetch-playlist
   ::data-source/params {:id ^::data-source/dep [:playlist-id]}})
```

  Now Pharmacist will look for `:playlist-id` in the initial parameters passed
  to [[pharmacist.prescription/fill]] or other sources in the prescription. It
  will then retrieve the source and pass the full result as the `:id` parameter
  to the `fetch-playlist` source. If you just need to pick a single value from
  the dependency, use a path:

```clj
(def playlist
  {::data-source/fn #'fetch-playlist
   ::data-source/params {:id ^::data-source/dep [:user :id]}})
```

  In this updated example, the `:id` parameter will receive the value of `:id`
  in the result of the `:user` data source - or the `:id` key of the map passed
  as `:user` in the initial parameters:

```clj
(pharmacist.prescription/fill
  {:playlist playlist}
  {:params {:user {:id 42}}})
```

  When the keys of the dependency are the same as the parameters your source
  expects, like above (`:id => [:user :id]`), you can just make the whole
  parameters map a dependency:

```clj
(def playlist
  {::data-source/fn #'fetch-playlist
   ::data-source/params ^::data-source/dep [:user]})
```

  ## Collection sources

  Pharmacist can fetch collections where you don't know upfront how many sources
  you'll find. To do this you must provide two source definitions: one that
  defines a single source in the collection, and another that specifies how many
  times to fetch that source, and parameters for each individual fetch.

  Imagine that you wanted to fetch all of a user's playlists. First define a
  function that receives a user and returns a vector of all their playlists:

```clj
(require '[pharmacist.result :as result])

(defn all-playlists [{::data-source/keys [params]}]
  ;; params is now the user - find their playlists from the map, over HTTP, from
  ;; disk, or whatever
  (result/success (:playlists params)))
```

  Then define the prescription with the collection and the item source:

```clj
(require '[pharmacist.data-source :as data-source])

(def prescription
  {:playlist {::data-source/fn #'fetch-playlist
              ::data-source/params {:id ^::data-source/dep [:playlist-id]}}

   :user-playlists {::data-source/fn #'all-playlists
                    ::data-source/params {:user ^::data-source/dep [:user]}
                    ::data-source/coll-of :playlist}})
```

  You can now fill this with an initial user (you could of course also get the
  user from yet another data source):

```clj
(pharmacist.prescription/fill
  prescription
  {:params {:user {:playlists [{:id 1}
                               {:id 2}]}}})
```

  The `:playlist` source will be fetched twice - once with `{:id 1}` as its
  parameters, and once with `{:id 2}`. The parameters returned from the
  selection function are merged into the map in the prescription, so in the
  above example, `{:id ^::data-source/dep [:playlist-id]}` isn't strictly
  necessary, but it is recommended, because it documents the expected parameters
  **and** it makes the source usable outside of the collection as well.

  In order to load a different selection of the collection, define another
  selection function, and make it a `::data-source/coll-of` the same item
  source.

  ## Data source keys

| key                          | description |
| -----------------------------|-------------|
| `::data-source/id`           | Keyword identifying this source. Can be inferred from `::fn` or `::async-fn`, see [[id]]
| `::data-source/fn`           | Function that fetches this source. Should return a [[pharmacist.result]]
| `::data-source/async-fn`     | Function that fetches this source asynchronously. Should return a core.async channel that emits a single [[pharmacist.result]]
| `::data-source/params`       | Parameter map to pass to the fetch function
| `::data-source/retries`      | The number of times `::result/retryable?` results from this source can be retried
| `::data-source/timeout`      | The maximum number of milliseconds to wait for this source to be fetched.
| `::data-source/coll-of`      | The type of source this source is a collection of
| `::data-source/cache-params` | A collection of paths to extract as the cache key, see [[pharmacist.cache/cache-params]]
| `::data-source/cache-deps`   | The dependencies required to compute the cache key. Usually inferred from `::data-source/cache-params`, see [[pharmacist.cache/cache-deps]]
raw docstring

cache-depsclj/smultimethod

Specify which dependencies are relevant to compute a cache key. Should return a set of dependencies. The default implementation extracts the dependencies from the result from cache-params. Most use-cases are best solved using cache-params, and in the rare cases where you need to control cache dependencies manually, it is recommended to specify it declaratively in your sources:

(require '[pharmacist.data-source :as data-source])

(def my-source
  {::data-source/fn #'fetch-my-source
   ::data-source/params {:id ^::data-source/dep [:dep1 :id]
                         :type ^::data-source/dep [:dep2 :type]}
   ::data-source/cache-deps #{:dep1}})
Specify which dependencies are relevant to compute a cache key. Should return a
  set of dependencies. The default implementation extracts the dependencies from
  the result from [[cache-params]]. Most use-cases are best solved using
  [[cache-params]], and in the rare cases where you need to control cache
  dependencies manually, it is recommended to specify it declaratively in your
  sources:

```clojure
(require '[pharmacist.data-source :as data-source])

(def my-source
  {::data-source/fn #'fetch-my-source
   ::data-source/params {:id ^::data-source/dep [:dep1 :id]
                         :type ^::data-source/dep [:dep2 :type]}
   ::data-source/cache-deps #{:dep1}})
```
sourceraw docstring

cache-keyclj/smultimethod

Given a source, return the cache key that uniquely addresses content loaded by the source. The default implementation combines the :pharmacist.data-source/id with the parameter values selected by cache-params. It is strongly recommended to make sure the source's variations are expressed as parameters so you can rely on the default implementation of this method. If this is somehow not possible, you can implement this method, which dispatches on a source's :pharmacist.data-source/id, to provide a custom cache key:

(require '[pharmacist.data-source :as data-source])

;; This is just an example, NOT recommended. Environment variables could just as
;; easily be fed in as parameters
(defmethod data-source/cache-key ::my-source [source]
  [(get (System/getenv) "app_env") (-> source ::data-source/params :id)])
Given a source, return the cache key that uniquely addresses content loaded by
  the source. The default implementation combines the :pharmacist.data-source/id
  with the parameter values selected by [[cache-params]]. It is strongly
  recommended to make sure the source's variations are expressed as parameters
  so you can rely on the default implementation of this method. If this is
  somehow not possible, you can implement this method, which dispatches on a
  source's `:pharmacist.data-source/id`, to provide a custom cache key:

```clojure
(require '[pharmacist.data-source :as data-source])

;; This is just an example, NOT recommended. Environment variables could just as
;; easily be fed in as parameters
(defmethod data-source/cache-key ::my-source [source]
  [(get (System/getenv) "app_env") (-> source ::data-source/params :id)])
```
sourceraw docstring

cache-paramsclj/smultimethod

Selects which parameters to use to calculate the cache key for a source. Should return a vector of paths to extract from the source after all dependencies are resolved. Specify cache-params declaratively in your sources instead of implementing this method.

(require '[pharmacist.data-source :as data-source])

(def my-source
  {::data-source/fn #'fetch-my-source
   ::data-source/params {:id ^::data-source/dep [:dep1 :id]
                         :type ^::data-source/dep [:dep2 :type]}
   ::data-source/cache-params [[:dep1 :id]]})
Selects which parameters to use to calculate the cache key for a source.
  Should return a vector of paths to extract from the source after all
  dependencies are resolved. Specify cache-params declaratively in your sources
  instead of implementing this method.

```clojure
(require '[pharmacist.data-source :as data-source])

(def my-source
  {::data-source/fn #'fetch-my-source
   ::data-source/params {:id ^::data-source/dep [:dep1 :id]
                         :type ^::data-source/dep [:dep2 :type]}
   ::data-source/cache-params [[:dep1 :id]]})
```
sourceraw docstring

fetchclj/smultimethod

Fetch a source asynchronously. This method is dispatched on the :pharmacist/data-source/id key of the source. If no :pharmacist.data-source/fn or :pharmacist.data-source/async-fn is provided, this method is called with a single source. It should return a clojure.core.async/chan that emits a single message, which should be one of [[parmacist.result/success]] or [[parmacist.result/failure]]. The default implementation delegates to [fetch-async].

Fetch a source asynchronously. This method is dispatched on the
`:pharmacist/data-source/id` key of the source. If no
`:pharmacist.data-source/fn` or `:pharmacist.data-source/async-fn` is
provided, this method is called with a single source. It should return a
`clojure.core.async/chan` that emits a single message, which should be one
of [[parmacist.result/success]] or [[parmacist.result/failure]]. The default
implementation delegates to [fetch-async].
sourceraw docstring

fetch-syncclj/smultimethod

Fetch a source synchronously. This method is dispatched on the :pharmacist/data-source/id key of the source. If no :pharmacist.data-source/fn or :pharmacist.data-source/async-fn is provided, and fetch is not implemented for the id, then this method is called with a single source. It should return a [[parmacist.result/success]] or [parmacist.result/failure].

Fetch a source synchronously. This method is dispatched on the
`:pharmacist/data-source/id` key of the source. If no
`:pharmacist.data-source/fn` or `:pharmacist.data-source/async-fn` is
provided, and [[fetch]] is not implemented for the id, then this method is
called with a single source. It should return a [[parmacist.result/success]]
or [parmacist.result/failure].
sourceraw docstring

idclj/s

(id {:pharmacist.data-source/keys [id fn async-fn]})

Return the id of the provided source. Returns the :pharmacist.data-source/id key if set, otherwise makes an effort to infer the id from either :pharmacist.data-source/fn or :pharmacist.data-source/async-fn, whichever is set. If you don't want to manually assign ids, it is strongly recommended that :fn/:async-fn is set to vars, not function values, as Pharmacist will be better able to infer function names.

The id is used to address results from the source in cache, and to dispatch fetch (when not using :pharmacist.data-source/fn or :pharmacist.data-source/async-fn).

Return the id of the provided source. Returns the `:pharmacist.data-source/id`
key if set, otherwise makes an effort to infer the id from either
`:pharmacist.data-source/fn` or `:pharmacist.data-source/async-fn`, whichever
is set. If you don't want to manually assign ids, it is strongly recommended
that `:fn`/`:async-fn` is set to vars, not function values, as Pharmacist will
be better able to infer function names.

The id is used to address results from the source in cache, and to dispatch
[[fetch]] (when not using `:pharmacist.data-source/fn` or
`:pharmacist.data-source/async-fn`).
sourceraw docstring

cljdoc is a website building & hosting documentation for Clojure/Script libraries

× close