Liking cljdoc? Tell your friends :D

pharmacist.cache

Caching tools for use with Pharmacist prescriptions

Caching tools for use with Pharmacist prescriptions
raw docstring

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

pharmacist.prescription

A prescription is a map of [path data-source] that Pharmacist can fill. From this fulfilled prescription you can select all, or parts of the described data. You can consume/stream data as it becomes available, or combine everything into a single map of [path result-data].

(require '[pharmacist.prescription :as p]
         '[pharmacist.data-source :as data-source]
         '[clojure.core.async :refer [<!!]])

(def prescription
  {::auth {::data-source/fn #'spotify-auth
           ::data-source/params ^::data-source/dep [:config]}

   ::playlists {::data-source/fn #'spotify-playlists
                ::data-source/params {:token ^::data-source/dep [::auth :access_token]}}})

(-> prescription
    (p/fill {:params {:config {:spotify-user "..."
                               :spotify-pass "..."}}})
    (p/select [::playlists])
    p/collect
    <!!
    :pharmacist.result/data)
A prescription is a map of `[path data-source]` that Pharmacist can fill.
  From this fulfilled prescription you can select all, or parts of the described
  data. You can consume/stream data as it becomes available, or combine
  everything into a single map of `[path result-data]`.

```clj
(require '[pharmacist.prescription :as p]
         '[pharmacist.data-source :as data-source]
         '[clojure.core.async :refer [<!!]])

(def prescription
  {::auth {::data-source/fn #'spotify-auth
           ::data-source/params ^::data-source/dep [:config]}

   ::playlists {::data-source/fn #'spotify-playlists
                ::data-source/params {:token ^::data-source/dep [::auth :access_token]}}})

(-> prescription
    (p/fill {:params {:config {:spotify-user "..."
                               :spotify-pass "..."}}})
    (p/select [::playlists])
    p/collect
    <!!
    :pharmacist.result/data)
```
raw docstring

pharmacist.result

Data structures and functions to work with the result of pharmacist.data-source fetch functions. A result is a map with the key :pharmacist.result/success? (a boolean) and any number of the below keys.

keydescription
:pharmacist.result/success?Boolean. Indicates successful retrieval
:pharmacist.result/dataThe resulting data from the fetch functions
:pharmacist.result/retryable?A boolean indicating whether this failure is worth retrying
:pharmacist.result/refreshOptional set of parameters that must be refreshed before retrying

The following keys are set by pharmacist.prescription/fill:

keydescription
:pharmacist.result/partial?true when the selection returned collection items, but the collection items are not yet available
:pharmacist.result/attemptsThe number of attempts made to fetch this source
:pharmacist.result/retrying?true if the result was a failure and Pharmacist intends to try it again
:pharmacist.result/raw-dataWhen the source has a schema that transforms ::pharmacist.result/data, this key has the unprocessed result
:pharmacist.result/timeout-afterThe number of milliseconds at which this source was considered timed out
:pharmacist.cache/cached-atTimestamp indicating when this result was originally cached
Data structures and functions to work with the result
  of [[pharmacist.data-source]] fetch functions. A result is a map with the key
  `:pharmacist.result/success?` (a boolean) and any number of the below keys.

| key                             | description |
| --------------------------------|-------------|
| `:pharmacist.result/success?`   | Boolean. Indicates successful retrieval
| `:pharmacist.result/data`       | The resulting data from the fetch functions
| `:pharmacist.result/retryable?` | A boolean indicating whether this failure is worth retrying
| `:pharmacist.result/refresh`    | Optional set of parameters that must be refreshed before retrying

  The following keys are set by [[pharmacist.prescription/fill]]:

| key                                | description |
| -----------------------------------|-------------|
| `:pharmacist.result/partial?`      | `true` when the selection returned collection items, but the collection items are not yet available
| `:pharmacist.result/attempts`      | The number of attempts made to fetch this source
| `:pharmacist.result/retrying?`     | `true` if the result was a failure and Pharmacist intends to try it again
| `:pharmacist.result/raw-data`      | When the source has a schema that transforms `::pharmacist.result/data`, this key has the unprocessed result
| `:pharmacist.result/timeout-after` | The number of milliseconds at which this source was considered timed out
| `:pharmacist.cache/cached-at`      | Timestamp indicating when this result was originally cached
raw docstring

pharmacist.schema

Tools for mapping, coercing, and verifying data.

Tools for mapping, coercing, and verifying data.
raw docstring

pharmacist.validate

Tools to validate a prescription before attempting to fill it. Pharmacist will make the best the situation, and in the case of cyclic dependencies and other forms of invalid states, it will just stop processing. The functions in this namespace can help you trigger those situations as errors instead.

Tools to validate a prescription before attempting to fill it. Pharmacist
will make the best the situation, and in the case of cyclic dependencies and
other forms of invalid states, it will just stop processing. The functions in
this namespace can help you trigger those situations as errors instead.
raw docstring

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

× close