Giter Club home page Giter Club logo

spec-provider's Introduction

spec-provider

This is a library that will produce a best-guess Clojure spec based on multiple examples of in-memory data. The inferred spec is not meant to be used as is and without human intervention, it is rather a starting point that can (and should) be refined.

The idea is analogous to F# type providers -- specifically the JSON type provider, but the input in the case of spec-provider is any in-memory Clojure data structure.

Since Clojure spec is still in alpha, this library should also be considered to be in alpha -- so, highly experimental, very likely to change, possibly flawed.

This library works in both Clojure and ClojureScript.

Maturity level: mature and useful. Has not reached full potential as some ideas are still unexplored.

Usage

To use this library, add this dependency to your Leiningen project.clj file:

[spec-provider "0.4.14"]

Version history

Use cases

The are two main use cases for spec-provider:

  1. You have a lot of examples of raw data (maybe in a JSONB column of a PostreSQL table) and you'd like to:

    • See a summary of what shape the data is. You can use spec-provider as a way to explore new datasets.

    • You already know what shape your data is, and you just want some help getting started writing a spec for it because your data is deeply nested, has a lot of corner cases, you're lazy etc.

    • You think you know what shape your data is, but because it's neither typed checked nor contract checked, some exceptions have sneaked into it. Instead of eyeballing 100,000 maps, you run spec-provider on them and to your surprise you find that one of the fields is (s/or :integer integer? :string string?) instead of just string as you expected. You can use spec-provider as a data debugging tool.

  2. You have an un-spec'ed function and you also have a good way to exercise it (via unit tests, actual usage etc). You can instrument the function with spec-provider, run it a few times with actual data, and then ask spec-provider for the function spec based on the data that flowed through the function.

Inferring the spec of raw data

To infer a spec of a bunch of data just pass the data to the infer-specs function:

> (require '[spec-provider.provider :as sp])

> (def inferred-specs
    (sp/infer-specs
     [{:a 8  :b "foo" :c :k}
      {:a 10 :b "bar" :c "k"}
      {:a 1  :b "baz" :c "k"}]
     :toy/small-map))

> inferred-specs

((clojure.spec.alpha/def :toy/c (clojure.spec/or :keyword keyword? :string string?))
 (clojure.spec.alpha/def :toy/b string?)
 (clojure.spec.alpha/def :toy/a integer?)
 (clojure.spec.alpha/def :toy/small-map (clojure.spec/keys :req-un [:toy/a :toy/b :toy/c])))

The sequence of specs that you get out of infer-spec is technically correct, but not very useful for pasting into your code. Luckily, you can do:

> (sp/pprint-specs inferred-specs 'toy 's)

(s/def ::c (s/or :keyword keyword? :string string?))
(s/def ::b string?)
(s/def ::a integer?)
(s/def ::small-map (s/keys :req-un [::a ::b ::c]))

Passing 'toy to pprint-specs signals that we intend to paste this code into the toy namespace, so spec names are printed using the :: syntax.

Passing 's signals that we are going to require clojure.spec as s, so the calls to clojure.spec/def become s/def etc.

Nested data structures

spec-provider will walk nested data structures in your sample data and attempt to infer specs for everything.

Let's use clojure.spec to generate a larger sample of data with nested structures.

(s/def ::id (s/or :numeric pos-int? :string string?))
(s/def ::codes (s/coll-of keyword? :max-gen 5))
(s/def ::first-name string?)
(s/def ::surname string?)
(s/def ::k (nilable keyword?))
(s/def ::age (s/with-gen
               (s/and integer? pos? #(<= % 130))
               #(gen/int 130)))
(s/def :person/role #{:programmer :designer})
(s/def ::phone-number string?)

(s/def ::street string?)
(s/def ::city string?)
(s/def ::country string?)
(s/def ::street-number pos-int?)

(s/def ::address
  (s/keys :req-un [::street ::city ::country]
          :opt-un [::street-number]))

(s/def ::person
  (s/keys :req-un [::id ::first-name ::surname ::k ::age ::address]
          :opt-un [::phone-number ::codes]
          :req    [:person/role]))

This spec can be used to generate a reasonably large random sample of persons:

(def persons (gen/sample (s/gen ::person) 100))

Which generates structures like:

{:id "d7FMcH52",
 :first-name "6",
 :surname "haFsA",
 :k :a-*?DZ/a,
 :age 5,
 :person/role :designer,
 :address {:street "Yrx963uDy", :city "b", :country "51w5NQ6", :street-number 53},
 :codes
 [:*.?m_o-9_j?b.N?_!a+IgUE._coE.S4l4_8_.MhN!5_!x.axztfh.x-/?*
  :*-DA?+zU-.T0u5R.evD8._r_D!*K0Q.WY-F4--.O*/**O+_Qg+
  :Bh8-A?t-f]}

Now watch what happens when we infer the spec of persons:

> (sp/pprint-specs
   (sp/infer-specs persons :person/person)
   'person 's)

(s/def ::codes (s/coll-of keyword?))
(s/def ::phone-number string?)
(s/def ::street-number integer?)
(s/def ::country string?)
(s/def ::city string?)
(s/def ::street string?)
(s/def
 ::address
 (s/keys :req-un [::street ::city ::country] :opt-un [::street-number]))
(s/def ::age integer?)
(s/def ::k (s/nilable keyword?))
(s/def ::surname string?)
(s/def ::first-name string?)
(s/def ::id (s/or :string string? :integer integer?))
(s/def ::role #{:programmer :designer})
(s/def
 ::person
 (s/keys
  :req [::role]
  :req-un [::id ::first-name ::surname ::k ::age ::address]
  :opt-un [::phone-number ::codes]))

Which is very close to the original spec. We are going to break down this result to bring attention to specific features in the following sections.

Nilable

If the sample data contain any nil values, this is detected and reflected in the inferred spec:

(s/def ::k (s/nilable keyword?))

Optional detection

Things like ::street-number, ::codes and ::phone-number did not appear consistently in the sampled data, so they are correctly identified as optional in the inferred spec.

(s/def
 ::address
 (s/keys :req-un [::street ::city ::country] :opt-un [::street-number]))

Qualified vs unqualified keys

Most of the keys in the sample data are not qualified, and they are detected as such in the inferred spec. The :person/role key is identified as fully qualified.

(s/def
 ::person
 (s/keys
  :req [::role]
  :req-un [::id ::first-name ::surname ::k ::age ::address]
  :opt-un [::phone-number ::codes]))

Note that the s/def for role is pretty printed as ::role because when calling pprint-specs we indicated that we are going to paste this into the person namespace.

> (sp/pprint-specs
   (sp/infer-specs persons :person/person)
   'person 's)

...

(s/def ::role #{:programmer :designer})

Enumerations

You may have also noticed that role has been identified as an enumeration of :programmer and :designer. To see how it's decided whether a field is an enumeration or not, we have to look under the hood. Let's generate a small sample of roles:

> (gen/sample (s/gen ::role) 5)

(:designer :designer :designer :designer :programmer)

spec-provider collects statistics about all the sample data before deciding on the spec:

> (require '[spec-provider.stats :as stats])
> (stats/collect-stats (gen/sample (s/gen ::role) 5) {})

#:spec-provider.stats{:distinct-values #{:programmer :designer},
                      :sample-count 5,
                      :pred-map {#function[clojure.core/keyword?] #:spec-provider.stats{:sample-count 5}}}

The stats include a set of distinct values observed (up to a certain limit), the sample count for each field, and counts on each of the predicates that the field matches -- in this case just keyword?. Based on these statistics, the spec is inferred and a decision is made on whether the value is an enumeration or not.

If the following statement is true, then the value is considered an enumeration:

(>= 0.1
    (/ (count distinct-values)
       sample-count))

In other words, if the number of distinct values found is less that 10% of the total recorded values, then the value is an enumeration. This threshold is configurable.

Looking at the actual numbers can make this logic easier to understand. For the small sample above:

> (sp/infer-specs (gen/sample (s/gen ::role) 5) ::role)

((clojure.spec/def :spec-provider.person-spec/role keyword?))

We have 2 distinct values in a sample of 5, which is 40% of the values being distinct. Imagine this percentage in a larger sample, say distinct 400 values in a sample of size 2000. That doesn't sound likely to be an enumeration, so it's interpreted as a normal value.

If you increase the sample:

> (sp/infer-specs (gen/sample (s/gen ::role) 100) ::role)

((clojure.spec/def :spec-provider.person-spec/role #{:programmer :designer}))

We have 2 distinct values in a sample of 100, which is 2%, which means that the same values appear again and again in the sample, so it must be an enumeration.

Merging

clojure-spec makes the same assumption as clojure.spec that keys that have same name also have the same data shape as their value, even when they appear in different maps. This means that the specs from different maps are merged by key.

To demonstrate this we need to "spike" the generated persons with an id field that's inconsistent with the existing (s/or :numeric pos-int? :string string?):

(defn add-inconsistent-id [person]
  (if (:address person)
    (assoc-in person [:address :id] (gen/generate (gen/keyword)))
    person))

(def persons-spiked (map add-inconsistent-id (gen/sample (s/gen ::person) 100)))

Inferring the spec of persons-spiked yields a different result for ids:

> (sp/pprint-specs
   (sp/infer-specs persons-spiked :person/person)
   'person 's)

...
(s/def ::id (s/or :string string? :integer integer? :keyword keyword?))
...

Do I know you from somewhere?

This feature is not illustrated by the person example, but before returning them, spec-provider will walk the inferred specs and look for forms that already occur elsewhere and replace them with the name of the known spec. For example:

> (sp/pprint-specs
    (sp/infer-specs [{:a [{:zz 1}] :b {:zz 2}}
                     {:a [{:zz 1} {:zz 4} nil] :b nil}] ::foo) *ns* 's)

(s/def ::zz integer?)
(s/def ::b (s/nilable (s/keys :req-un [::zz])))
(s/def ::a (s/coll-of ::b))
(s/def ::foo (s/keys :req-un [::a ::b]))

In this case, because maps like {:zz 2} appear under the key :b, spec-provider knows what to call them, so it uses that name for (s/def ::a (s/coll-of ::b)). This replacement is not performed if the spec definition is a predicate from the clojure.core namespace.

Inferring specs with numerical ranges

spec-provider collects stats about the min/max values of numerical fields, but will not output them in the inferred spec by default. To get range predicates in your specs you have to pass the :spec-provider.provider/range option:

> (require '[spec-provider.provider :refer :all :as sp])

> (pprint-specs
    (infer-specs [{:foo 3, :bar -400}
                  {:foo 3, :bar 4}
                  {:foo 10, :bar 400}] ::stuff {::sp/range true})
    *ns* 's)

(s/def ::bar (s/and integer? (fn [x] (<= -400 x 400))))
(s/def ::foo (s/and integer? (fn [x] (<= 3 x 10))))
(s/def ::stuff (s/keys :req-un [::bar ::foo]))

You can also restrict range predicates to specific keys by passing a set of qualified keys that are the names of the specs that should get a range predicate:

> (sp/pprint-specs
    (sp/infer-specs [{:foo 3, :bar -400}
                     {:foo 3, :bar 4}
                     {:foo 10, :bar 400}] ::stuff {::sp/range #{::foo}})
    *ns* 's)

(s/def ::bar integer?)
(s/def ::foo (s/and integer? (fn [x] (<= 3 x 10))))
(s/def ::stuff (s/keys :req-un [::bar ::foo]))

How it's done

Inferring a spec from raw data is a two step process: Stats collection and then summarization of the stats into specs.

First each data structure is visited recursively and statistics are collected at each level about the types of values that appear, the distinct values for each field (up to a limit), min and max values for numbers, lengths for sequences etc.

Two important points about stats collection:

  • Spec-provider will not run out of memory even if you throw a lot of data at it because it updates the same statistics data structure with every new example datum it receives.

  • Collecting stats will (at least partly) realize lazy sequences.

After stats collection, code from the spec-provider.provider namespace goes through the stats and it summarizes it as a collection of specs.

Alternative uses

As mentioned in the previous section, spec-provider first collects statistics about the data that you pass to it and then it uses them to infer specs for this data. The entry point for collecting stats is the spec-provider.stats/collect function. This can be used to explore your data and give you insight about its structure as it was very nicely explained in this blog post by Dan Lebrero.

Options

Assume this:

(require [spec-provider.provider :as sp]
         [spec-provider.stats :as stats])

There is only one option that affects how the specs are inferred and it can be passed as a map in an extra parameter to sp/infer-specs:

  • ::sp/range If true, all numerical specs include a range predicate. If it's a set of spec names (qualified keywords), only these specs will include range predicates. See section Inferring specs with numerical ranges for an example (default false).

There is a number of options that can affect how the sample stats are collected (and consequently also affect what spec is inferred). These options are passed to stats/collect, or as part of the options map passed to sp/infer-specs.

  • ::stats/distinct-limit How many distinct values are collected for collections (default 10).

  • ::stats/coll-limit How many elements of the collection are used to infer/collect data about the type of the contained element (default 101). This means that lazy sequences are at least partly realized.

  • ::stats/positional Results in positional stats being collected for sequences, so that s/cat can be inferred instead of s/coll-of (default false).

  • ::stats/positional-limit Bounds the positional stats length (default 100).

Inferring the spec of functions

Undocumented/under development: there is experimental support for instrumenting functions for the purpose of inferring the spec of args and return values.

Limitations

  • There is no attempt to infer the regular expression of collections.
  • There is no attempt to infer tuples.
  • There is no attempt to infer multi-spec.
  • For functions, only the :args and :ret parts of the spec is generated, the :fn part is up to you.
  • Spec-provider assumes that you want to follow the Clojure spec convention that the same map keys identify the same "entity", so it will merge stats that appear under the identical keys but in different parts of your tree structure. This may not be what you want. For more details see the "Merging" section.

FAQ

  • Will I run out of memory if I pass a lot of examples of my data to infer-specs?

    No, stats collection works by updating the same data structure with every example of data received. The data structure will initially grow a bit and then maintain a constant size. That means that you can use a lazy sequence to stream your huge table through it if you feel that's necessary (not tested!).

  • Can I do this for Prismatic schema?

    The hard part of inferring a spec is collecting the statistics. Summarizing the stats as specs was relatively easy, so plugging in a different "summarizer" that will output schemas from the same stats should be possible. Look at the provider namespace, write the schema equivalent and send me a pull request!

Developers

Run Clojure unit tests with:

lein test

Run ClojureScript unit tests with (default setup uses node):

lein doo

Run self-hosted ClojureScript unit tests with:

lein tach lumo

and

lein tach planck

Contributors

License

Copyright ยฉ 2016-2018 Stathis Sideris

Distributed under the Eclipse Public License either version 1.0 or (at your option) any later version.

spec-provider's People

Contributors

dlebrero avatar gibranrosa avatar jumarko avatar mfikes avatar pfeodrippe avatar stathissideris avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spec-provider's Issues

` #'spec-provider.stats/empty-stats did not conform to spec.`

#'spec-provider.stats/empty-stats did not conform to spec.

similar to #28 but it looks to me like changes were merged to fix this

project.clj

(defproject broken "0.0.1"
  :dependencies [[orchestra "2021.01.01-1"]
                 [org.clojure/clojure "1.10.0"]
                 [spec-provider "0.4.14"]]
  :source-paths ["src"]
  :main example)

src/example.cljc

(ns example
  (:require [spec-provider.stats :as stats]
            [orchestra.spec.test :as stest]))

(stest/instrument)

(defn -main
  []
  (println (stats/collect ["hi"])))
Exception in thread "main" Syntax error compiling at (C:\Users\richie\AppData\Local\Temp\form-init1921782138713344766.clj:1:109).
Call to #'spec-provider.stats/empty-stats did not conform to spec.
        at clojure.lang.Compiler.load(Compiler.java:7647)
        at clojure.lang.Compiler.loadFile(Compiler.java:7573)
        at clojure.main$load_script.invokeStatic(main.clj:452)
        at clojure.main$init_opt.invokeStatic(main.clj:454)
        at clojure.main$init_opt.invoke(main.clj:454)
        at clojure.main$initialize.invokeStatic(main.clj:485)
        at clojure.main$null_opt.invokeStatic(main.clj:519)
        at clojure.main$null_opt.invoke(main.clj:516)
        at clojure.main$main.invokeStatic(main.clj:598)
        at clojure.main$main.doInvoke(main.clj:561)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.lang.Var.applyTo(Var.java:705)
        at clojure.main.main(main.java:37)
Caused by: clojure.lang.ExceptionInfo: Call to #'spec-provider.stats/empty-stats did not conform to spec. {:clojure.spec.alpha/problems ({:path [:spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{}, :via [:spec-provider.stats/stats :spec-provider.stats/distinct-values], :in [:spec-provider.stats/distinct-values]}), :clojure.spec.alpha/spec #object[clojure.spec.alpha$map_spec_impl$reify__1997 0x7ce4de34 "clojure.spec.alpha$map_spec_impl$reify__1997@7ce4de34"], :clojure.spec.alpha/value {:spec-provider.stats/distinct-values #{}, :spec-provider.stats/sample-count 0, :spec-provider.stats/pred-map {}}, :clojure.spec.alpha/fn spec-provider.stats/empty-stats, :clojure.spec.alpha/ret {:spec-provider.stats/distinct-values #{}, :spec-provider.stats/sample-count 0, :spec-provider.stats/pred-map {}}, :clojure.spec.alpha/failure :instrument, :orchestra.spec.test/caller {:file "stats.cljc", :line 199, :var-scope spec-provider.stats/update-stats}}
        at orchestra.spec.test$spec_checking_fn$conform_BANG___297.invoke(test.cljc:30)
        at orchestra.spec.test$spec_checking_fn$fn__300.doInvoke(test.cljc:39)
        at clojure.lang.RestFn.invoke(RestFn.java:397)
        at spec_provider.stats$update_stats.invokeStatic(stats.cljc:199)
        at spec_provider.stats$update_stats.invoke(stats.cljc:193)
        at clojure.lang.AFn.applyToHelper(AFn.java:160)
        at clojure.lang.AFn.applyTo(AFn.java:144)
        at orchestra.spec.test$spec_checking_fn$fn__300.doInvoke(test.cljc:37)
        at clojure.lang.RestFn.invoke(RestFn.java:436)
        at spec_provider.stats$collect$fn__280.invoke(stats.cljc:223)
        at clojure.lang.PersistentVector.reduce(PersistentVector.java:343)
        at clojure.core$reduce.invokeStatic(core.clj:6827)
        at clojure.core$reduce.invoke(core.clj:6810)
        at spec_provider.stats$collect.invokeStatic(stats.cljc:223)
        at spec_provider.stats$collect.invoke(stats.cljc:219)
        at spec_provider.stats$collect.invokeStatic(stats.cljc:221)
        at spec_provider.stats$collect.invoke(stats.cljc:219)
        at example$_main.invokeStatic(example.cljc:9)
        at example$_main.invoke(example.cljc:7)
        at clojure.lang.Var.invoke(Var.java:380)
        at user$eval140.invokeStatic(form-init1921782138713344766.clj:1)
        at user$eval140.invoke(form-init1921782138713344766.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:7176)
        at clojure.lang.Compiler.eval(Compiler.java:7166)
        at clojure.lang.Compiler.load(Compiler.java:7635)
        ... 12 more

there's no issue if I use [org.clojure/clojure "1.9.0"]

$ lein run
#:spec-provider.stats{:distinct-values #{hi}, :sample-count 1, :pred-map {#object[clojure.core$string_QMARK___5132 0x6b85300e clojure.core$string_QMARK___5132@6b85300e] #:spec-provider.stats{:sample-count 1, :min-length 2, :max-length 2}}}

NullPointerException in merge-stats

Great library!

When I started using it with my data, I ran into the following problem: when I try to run infer-specs on structures like this:

[{:a {:b [1 2]}} {:b [1]}]

I am getting an exception of the following kind:

1. Unhandled java.lang.NullPointerException
   (No message)
                 merge.clj:   23  spec-provider.merge/merge-with-fns/fn
             protocols.clj:   49  clojure.core.protocols/iter-reduce
             protocols.clj:   75  clojure.core.protocols/fn
             protocols.clj:   75  clojure.core.protocols/fn
             protocols.clj:   13  clojure.core.protocols/fn/G
                  core.clj: 6704  clojure.core/reduce
                  core.clj: 6686  clojure.core/reduce
                 merge.clj:   19  spec-provider.merge/merge-with-fns
                 merge.clj:   15  spec-provider.merge/merge-with-fns
                 merge.clj:   69  spec-provider.merge/merge-stats
                 merge.clj:   65  spec-provider.merge/merge-stats
              provider.clj:  145  spec-provider.provider/summarize-stats/fn/fn
              (...)

instead of the expected

(s/def ::b integer?)
(s/def ::a (s/keys :req-un [::b]))
(s/def ::sample (s/keys :req-un [::a ::b]))

Maps as keys are not fully supported

> (pprint-specs
    (infer-specs [{{:a 4} 3}
                  {{:a 8} 4}] ::foo) *ns* 's)

(s/def ::foo (s/map-of (s/keys :req-un [::a]) integer?))

::a should have been promoted to top-level as a named spec

Java inference

Great job here, this little tool is one of those useful things that you don't know you need them until you need them ๐Ÿ˜€

This is more of a thought / question than anything else.

I was wondering whether it would be difficult to have Java (or JavaScript for that matter) inference of specs.

My use case would be to gradually introduce specs on an existing Java project for data validation, then generation of Clojure DT structure in a second phase and take over eventually ๐Ÿ˜€.

I m going to throw it there for now. Thanks for your work!

Spec provider browser tool

It might demonstration of spec provider to build a CLJS app where people can paste JSON or EDN into a form and have spec-provider generate the spec.

s/keys vs s/map-of

It'd be nice to have a way to indicate a spec key which should be treated as s/map-of instead of s/keys

Consider min/max constraints as well in the inferred spec

Currently, spec-provider captures the stats correctly for each number, like- min/max, etc. but does not consider the same while generating the spec. It would be good to provide an option to impose the same on the generated spec by default. As of now, you need to define it explicitly and then merge with the existing spec that is one added step for the users.

Some weird functionality.

I have such data structure:

{:contexts
 ({:importer.datamodel/global-id "01b4e69f86e5dd1d816e91da27edc08e",
   :importer.datamodel/type "province",
   :name "a1",
   :importer.datamodel/part-of "8cda1baed04b668a167d4ca28e3cef36"}
  {:importer.datamodel/global-id "8cda1baed04b668a167d4ca28e3cef36",
   :importer.datamodel/type "country",
   :name "AAA"}
  {:importer.datamodel/global-id "c78e5478e19f2d7c1b02088e53e8d8a4",
   :importer.datamodel/type "location",
   :importer.datamodel/center ["36." "2."],
   :importer.datamodel/part-of "01b4e69f86e5dd1d816e91da27edc08e"}
  {:importer.datamodel/global-id "88844f94f79c75acfcb957bb41386149",
   :importer.datamodel/type "organisation",
   :name "C"}
  {:importer.datamodel/global-id "102e96468e5d13058ab85c734aa4a949",
   :importer.datamodel/type "organisation",
   :name "A"}),
 :datasources
 ({:importer.datamodel/global-id "Source;ACLED",
   :name "ACLED",
   :url "https://www.acleddata.com"}),
 :iois
 ({:importer.datamodel/global-id "item-set;ACLED",
   :importer.datamodel/type "event",
   :datasource "Source;ACLED",
   :features
   ({:importer.datamodel/global-id
     "c74257292f584502f9be02c98829d9fda532a492e7dd41e06c31bbccc76a7ba0",
     :date "1997-01-04",
     :fulltext
     {:importer.datamodel/global-id "df5c7d6d075df3a7719ebdd39c6d4c7f",
      :text "bla"},
     :location-meanings
     ({:importer.datamodel/global-id
       "e5611219971164a15f06e07228fb7b51",
       :location "8cda1baed04b668a167d4ca28e3cef36",
       :contexts (),
       :importer.datamodel/type "position"}
      {:importer.datamodel/global-id
       "af36461d27ec1d8d28fd7f4a70ab7ce2",
       :location "c78e5478e19f2d7c1b02088e53e8d8a4",
       :contexts (),
       :importer.datamodel/type "position"}),
     :interaction-name "Violence",
     :importer.datamodel/type "description",
     :has-contexts
     ({:context "102e96468e5d13058ab85c734aa4a949",
       :context-association-type "actor",
       :context-association-name "actor-1",
       :priority "none"}
      {:context "88844f94f79c75acfcb957bb41386149",
       :context-association-type "actor",
       :context-association-name "actor-2",
       :priority "none"}),
     :facts
     ({:importer.datamodel/global-id
       "c46802ce6dcf33ca02ce113ffd9a855e",
       :importer.datamodel/type "integer",
       :name "fatalities",
       :value "16"}),
     :attributes
     ({:name "description",
       :importer.datamodel/type "string",
       :value "Violence"})}),
   :attributes (),
   :ioi-slice "per-item"})}

and I am running this code (sp/pprint-specs (sp/infer-specs data :importer.datamodel/data) 'data 's)

which generates me this spec

(spec/def :importer.datamodel/data
  (clojure.spec.alpha/coll-of
   (clojure.spec.alpha/or
    :collection
    (clojure.spec.alpha/coll-of
     (clojure.spec.alpha/keys
      :req
      [:importer.datamodel/global-id]
      :opt
      [:importer.datamodel/center
       :importer.datamodel/part-of
       :importer.datamodel/type]
      :opt-un
      [:importer.datamodel/attributes
       :importer.datamodel/datasource
       :importer.datamodel/features
       :importer.datamodel/ioi-slice
       :importer.datamodel/name
       :importer.datamodel/url]))
    :simple
    clojure.core/keyword?)))

But it looks like this one is not full and impossible to exercise. how to make it generate everything recursively?

summarize-leaf called with two args

summarize-or calls summarize-leaf called with two args which doesn't match up. It expects a single arg.

Picked this up trying to get the code to run on CLJS. The compiler was cross.

Support for `bigdec?`

Bigdec is treated as any?, maybe it's as easy as add it to provider, gonna check it when I have some time.

;; using
(provider/infer-specs data :eita/danado)

;; where data is (after pprint)
({:principal 2.589383192884270087M,
  :start
  #object[org.joda.time.DateTime 0x1168723c "2011-01-01T00:00:00.000Z"],
  :end
  #object[org.joda.time.DateTime 0x537ef638 "2011-01-01T00:00:00.000Z"]}
 {:principal -474.192376784456429913M,
  :start
    #object[org.joda.time.DateTime 0x2417893b "2011-01-02T00:00:00.000Z"]))

;; and inferred spec is
((clojure.spec.alpha/def :eita/end clojure.core/any?)
 (clojure.spec.alpha/def :eita/start clojure.core/any?)
 (clojure.spec.alpha/def :eita/principal clojure.core/any?)
 (clojure.spec.alpha/def
  :eita/danado
  (clojure.spec.alpha/keys
   :req-un
   [:eita/principal :eita/start]
   :opt-un
   [:eita/end])))

Also, as a bonus, how could I support dates of the type org.joda.time.DateTime?

Sets are broken

> (pprint-specs
    (infer-specs
      [#{:a}] :foo/stuff) *ns* 's)

(s/def :foo/stuff (s/or))

Support for Clojure 1.10

I ran the tests against Clojure 1.10 and it blew up mightily.

Errors like:

lein test :only spec-provider.trace-test/instrument-test

ERROR in (instrument-test) (alpha.clj:132)
Uncaught exception, not in assertion.
expected: nil
  actual: clojure.lang.ExceptionInfo: Call to #'spec-provider.stats/update-stats
 did not conform to spec.
{:clojure.spec.alpha/problems ({:path [:stats :clojure.spec.alpha/nil], :pred ni
l?, :val #:spec-provider.stats{:distinct-values #{}, :sample-count 1, :pred-map
{#object[clojure.core$sequential_QMARK_ 0x65e21ce3 "clojure.core$sequential_QMAR
K_@65e21ce3"] #:spec-provider.stats{:sample-count 1, :min-length 11, :max-length
 11}}, :elements-pos {0 #:spec-provider.stats{:distinct-values #{10}, :sample-co
unt 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$i
nteger_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 10, :max 10
}}}, 7 #:spec-provider.stats{:distinct-values #{80}, :sample-count 1, :pred-map
{#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cb
eac65"] #:spec-provider.stats{:sample-count 1, :min 80, :max 80}}}, 1 #:spec-pro
vider.stats{:distinct-values #{20}, :sample-count 1, :pred-map {#object[clojure.
core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-pr
ovider.stats{:sample-count 1, :min 20, :max 20}}}, 4 #:spec-provider.stats{:dist
inct-values #{50}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMAR
K_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sam
ple-count 1, :min 50, :max 50}}}, 6 #:spec-provider.stats{:distinct-values #{70}
, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "cl
ojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min
 70, :max 70}}}, 3 #:spec-provider.stats{:distinct-values #{40}, :sample-count 1
, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$intege
r_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 40, :max 40}}},
2 #:spec-provider.stats{:distinct-values #{30}, :sample-count 1, :pred-map {#obj
ect[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65
"] #:spec-provider.stats{:sample-count 1, :min 30, :max 30}}}, 9 #:spec-provider
.stats{:distinct-values #{100}, :sample-count 1, :pred-map {#object[clojure.core
$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provid
er.stats{:sample-count 1, :min 100, :max 100}}}, 5 #:spec-provider.stats{:distin
ct-values #{60}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_
 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sampl
e-count 1, :min 60, :max 60}}}, 10 #:spec-provider.stats{:distinct-values #{}, :
sample-count 1, :pred-map {#object[clojure.core$sequential_QMARK_ 0x65e21ce3 "cl
ojure.core$sequential_QMARK_@65e21ce3"] #:spec-provider.stats{:sample-count 1, :
min-length 2, :max-length 2}}, :elements-pos {0 #:spec-provider.stats{:distinct-
values #{110}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0
x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-
count 1, :min 110, :max 110}}}, 1 #:spec-provider.stats{:distinct-values #{"stri
ng"}, :sample-count 1, :pred-map {#object[clojure.core$string_QMARK___5395 0x736
309a9 "clojure.core$string_QMARK___5395@736309a9"] #:spec-provider.stats{:sample
-count 1, :min-length 6, :max-length 6}}}}}, 8 #:spec-provider.stats{:distinct-v
alues #{90}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7
cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-co
unt 1, :min 90, :max 90}}}}}, :via [], :in [0]} {:path [:stats :clojure.spec.alp
ha/pred :spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (cloju
re.core/or (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{}, :via
[:spec-provider.stats/stats :spec-provider.stats/distinct-values], :in [0 :spec-
provider.stats/distinct-values]} {:path [:stats :clojure.spec.alpha/pred :spec-p
rovider.stats/elements-pos 1 :spec-provider.stats/distinct-values], :pred (cloju
re.core/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.core/sequential?
%))), :val #{10}, :via [:spec-provider.stats/stats :spec-provider.stats/elements
-pos :spec-provider.stats/stats :spec-provider.stats/distinct-values], :in [0 :s
pec-provider.stats/elements-pos 0 1 :spec-provider.stats/distinct-values]} {:pat
h [:stats :clojure.spec.alpha/pred :spec-provider.stats/elements-pos 1 :spec-pro
vider.stats/distinct-values], :pred (clojure.core/fn [%] (clojure.core/or (cloju
re.core/nil? %) (clojure.core/sequential? %))), :val #{80}, :via [:spec-provider
.stats/stats :spec-provider.stats/elements-pos :spec-provider.stats/stats :spec-
provider.stats/distinct-values], :in [0 :spec-provider.stats/elements-pos 7 1 :s
pec-provider.stats/distinct-values]} {:path [:stats :clojure.spec.alpha/pred :sp
ec-provider.stats/elements-pos 1 :spec-provider.stats/distinct-values], :pred (c
lojure.core/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.core/sequenti
al? %))), :val #{20}, :via [:spec-provider.stats/stats :spec-provider.stats/elem
ents-pos :spec-provider.stats/stats :spec-provider.stats/distinct-values], :in [
0 :spec-provider.stats/elements-pos 1 1 :spec-provider.stats/distinct-values]} {
:path [:stats :clojure.spec.alpha/pred :spec-provider.stats/elements-pos 1 :spec
-provider.stats/distinct-values], :pred (clojure.core/fn [%] (clojure.core/or (c
lojure.core/nil? %) (clojure.core/sequential? %))), :val #{50}, :via [:spec-prov
ider.stats/stats :spec-provider.stats/elements-pos :spec-provider.stats/stats :s
pec-provider.stats/distinct-values], :in [0 :spec-provider.stats/elements-pos 4
1 :spec-provider.stats/distinct-values]} {:path [:stats :clojure.spec.alpha/pred
 :spec-provider.stats/elements-pos 1 :spec-provider.stats/distinct-values], :pre
d (clojure.core/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.core/sequ
ential? %))), :val #{70}, :via [:spec-provider.stats/stats :spec-provider.stats/
elements-pos :spec-provider.stats/stats :spec-provider.stats/distinct-values], :
in [0 :spec-provider.stats/elements-pos 6 1 :spec-provider.stats/distinct-values
]} {:path [:stats :clojure.spec.alpha/pred :spec-provider.stats/elements-pos 1 :
spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (clojure.core/o
r (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{40}, :via [:spec-
provider.stats/stats :spec-provider.stats/elements-pos :spec-provider.stats/stat
s :spec-provider.stats/distinct-values], :in [0 :spec-provider.stats/elements-po
s 3 1 :spec-provider.stats/distinct-values]} {:path [:stats :clojure.spec.alpha/
pred :spec-provider.stats/elements-pos 1 :spec-provider.stats/distinct-values],
:pred (clojure.core/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.core/
sequential? %))), :val #{30}, :via [:spec-provider.stats/stats :spec-provider.st
ats/elements-pos :spec-provider.stats/stats :spec-provider.stats/distinct-values
], :in [0 :spec-provider.stats/elements-pos 2 1 :spec-provider.stats/distinct-va
lues]} {:path [:stats :clojure.spec.alpha/pred :spec-provider.stats/elements-pos
 1 :spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (clojure.co
re/or (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{100}, :via [:
spec-provider.stats/stats :spec-provider.stats/elements-pos :spec-provider.stats
/stats :spec-provider.stats/distinct-values], :in [0 :spec-provider.stats/elemen
ts-pos 9 1 :spec-provider.stats/distinct-values]} {:path [:stats :clojure.spec.a
lpha/pred :spec-provider.stats/elements-pos 1 :spec-provider.stats/distinct-valu
es], :pred (clojure.core/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.
core/sequential? %))), :val #{60}, :via [:spec-provider.stats/stats :spec-provid
er.stats/elements-pos :spec-provider.stats/stats :spec-provider.stats/distinct-v
alues], :in [0 :spec-provider.stats/elements-pos 5 1 :spec-provider.stats/distin
ct-values]} {:path [:stats :clojure.spec.alpha/pred :spec-provider.stats/element
s-pos 1 :spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (cloju
re.core/or (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{}, :via
[:spec-provider.stats/stats :spec-provider.stats/elements-pos :spec-provider.sta
ts/stats :spec-provider.stats/distinct-values], :in [0 :spec-provider.stats/elem
ents-pos 10 1 :spec-provider.stats/distinct-values]} {:path [:stats :clojure.spe
c.alpha/pred :spec-provider.stats/elements-pos 1 :spec-provider.stats/elements-p
os 1 :spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (clojure.
core/or (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{110}, :via
[:spec-provider.stats/stats :spec-provider.stats/elements-pos :spec-provider.sta
ts/stats :spec-provider.stats/elements-pos :spec-provider.stats/stats :spec-prov
ider.stats/distinct-values], :in [0 :spec-provider.stats/elements-pos 10 1 :spec
-provider.stats/elements-pos 0 1 :spec-provider.stats/distinct-values]} {:path [
:stats :clojure.spec.alpha/pred :spec-provider.stats/elements-pos 1 :spec-provid
er.stats/elements-pos 1 :spec-provider.stats/distinct-values], :pred (clojure.co
re/fn [%] (clojure.core/or (clojure.core/nil? %) (clojure.core/sequential? %))),
 :val #{"string"}, :via [:spec-provider.stats/stats :spec-provider.stats/element
s-pos :spec-provider.stats/stats :spec-provider.stats/elements-pos :spec-provide
r.stats/stats :spec-provider.stats/distinct-values], :in [0 :spec-provider.stats
/elements-pos 10 1 :spec-provider.stats/elements-pos 1 1 :spec-provider.stats/di
stinct-values]} {:path [:stats :clojure.spec.alpha/pred :spec-provider.stats/ele
ments-pos 1 :spec-provider.stats/distinct-values], :pred (clojure.core/fn [%] (c
lojure.core/or (clojure.core/nil? %) (clojure.core/sequential? %))), :val #{90},
 :via [:spec-provider.stats/stats :spec-provider.stats/elements-pos :spec-provid
er.stats/stats :spec-provider.stats/distinct-values], :in [0 :spec-provider.stat
s/elements-pos 8 1 :spec-provider.stats/distinct-values]}), :clojure.spec.alpha/
spec #object[clojure.spec.alpha$regex_spec_impl$reify__2509 0x1ab1d93d "clojure.
spec.alpha$regex_spec_impl$reify__2509@1ab1d93d"], :clojure.spec.alpha/value (#:
spec-provider.stats{:distinct-values #{}, :sample-count 1, :pred-map {#object[cl
ojure.core$sequential_QMARK_ 0x65e21ce3 "clojure.core$sequential_QMARK_@65e21ce3
"] #:spec-provider.stats{:sample-count 1, :min-length 11, :max-length 11}}, :ele
ments-pos {0 #:spec-provider.stats{:distinct-values #{10}, :sample-count 1, :pre
d-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMAR
K_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 10, :max 10}}}, 7 #:sp
ec-provider.stats{:distinct-values #{80}, :sample-count 1, :pred-map {#object[cl
ojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:s
pec-provider.stats{:sample-count 1, :min 80, :max 80}}}, 1 #:spec-provider.stats
{:distinct-values #{20}, :sample-count 1, :pred-map {#object[clojure.core$intege
r_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stat
s{:sample-count 1, :min 20, :max 20}}}, 4 #:spec-provider.stats{:distinct-values
 #{50}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac
65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1
, :min 50, :max 50}}}, 6 #:spec-provider.stats{:distinct-values #{70}, :sample-c
ount 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$
integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 70, :max 7
0}}}, 3 #:spec-provider.stats{:distinct-values #{40}, :sample-count 1, :pred-map
 {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7c
beac65"] #:spec-provider.stats{:sample-count 1, :min 40, :max 40}}}, 2 #:spec-pr
ovider.stats{:distinct-values #{30}, :sample-count 1, :pred-map {#object[clojure
.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-p
rovider.stats{:sample-count 1, :min 30, :max 30}}}, 9 #:spec-provider.stats{:dis
tinct-values #{100}, :sample-count 1, :pred-map {#object[clojure.core$integer_QM
ARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:s
ample-count 1, :min 100, :max 100}}}, 5 #:spec-provider.stats{:distinct-values #
{60}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65
 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1,
:min 60, :max 60}}}, 10 #:spec-provider.stats{:distinct-values #{}, :sample-coun
t 1, :pred-map {#object[clojure.core$sequential_QMARK_ 0x65e21ce3 "clojure.core$
sequential_QMARK_@65e21ce3"] #:spec-provider.stats{:sample-count 1, :min-length
2, :max-length 2}}, :elements-pos {0 #:spec-provider.stats{:distinct-values #{11
0}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "
clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :m
in 110, :max 110}}}, 1 #:spec-provider.stats{:distinct-values #{"string"}, :samp
le-count 1, :pred-map {#object[clojure.core$string_QMARK___5395 0x736309a9 "cloj
ure.core$string_QMARK___5395@736309a9"] #:spec-provider.stats{:sample-count 1, :
min-length 6, :max-length 6}}}}}, 8 #:spec-provider.stats{:distinct-values #{90}
, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "cl
ojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min
 90, :max 90}}}}} [10 20 30 40 50 60 70 80 90 100 (110 "string" :kkk)] #:spec-pr
ovider.stats{:positional true}), :clojure.spec.alpha/fn spec-provider.stats/upda
te-stats, :clojure.spec.alpha/args (#:spec-provider.stats{:distinct-values #{},
:sample-count 1, :pred-map {#object[clojure.core$sequential_QMARK_ 0x65e21ce3 "c
lojure.core$sequential_QMARK_@65e21ce3"] #:spec-provider.stats{:sample-count 1,
:min-length 11, :max-length 11}}, :elements-pos {0 #:spec-provider.stats{:distin
ct-values #{10}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_
 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sampl
e-count 1, :min 10, :max 10}}}, 7 #:spec-provider.stats{:distinct-values #{80},
:sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "cloj
ure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 8
0, :max 80}}}, 1 #:spec-provider.stats{:distinct-values #{20}, :sample-count 1,
:pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_
QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 20, :max 20}}}, 4
#:spec-provider.stats{:distinct-values #{50}, :sample-count 1, :pred-map {#objec
t[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"]
 #:spec-provider.stats{:sample-count 1, :min 50, :max 50}}}, 6 #:spec-provider.s
tats{:distinct-values #{70}, :sample-count 1, :pred-map {#object[clojure.core$in
teger_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.
stats{:sample-count 1, :min 70, :max 70}}}, 3 #:spec-provider.stats{:distinct-va
lues #{40}, :sample-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7c
beac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-cou
nt 1, :min 40, :max 40}}}, 2 #:spec-provider.stats{:distinct-values #{30}, :samp
le-count 1, :pred-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.c
ore$integer_QMARK_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 30, :m
ax 30}}}, 9 #:spec-provider.stats{:distinct-values #{100}, :sample-count 1, :pre
d-map {#object[clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMAR
K_@7cbeac65"] #:spec-provider.stats{:sample-count 1, :min 100, :max 100}}}, 5 #:
spec-provider.stats{:distinct-values #{60}, :sample-count 1, :pred-map {#object[
clojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #
:spec-provider.stats{:sample-count 1, :min 60, :max 60}}}, 10 #:spec-provider.st
ats{:distinct-values #{}, :sample-count 1, :pred-map {#object[clojure.core$seque
ntial_QMARK_ 0x65e21ce3 "clojure.core$sequential_QMARK_@65e21ce3"] #:spec-provid
er.stats{:sample-count 1, :min-length 2, :max-length 2}}, :elements-pos {0 #:spe
c-provider.stats{:distinct-values #{110}, :sample-count 1, :pred-map {#object[cl
ojure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:s
pec-provider.stats{:sample-count 1, :min 110, :max 110}}}, 1 #:spec-provider.sta
ts{:distinct-values #{"string"}, :sample-count 1, :pred-map {#object[clojure.cor
e$string_QMARK___5395 0x736309a9 "clojure.core$string_QMARK___5395@736309a9"] #:
spec-provider.stats{:sample-count 1, :min-length 6, :max-length 6}}}}}, 8 #:spec
-provider.stats{:distinct-values #{90}, :sample-count 1, :pred-map {#object[cloj
ure.core$integer_QMARK_ 0x7cbeac65 "clojure.core$integer_QMARK_@7cbeac65"] #:spe
c-provider.stats{:sample-count 1, :min 90, :max 90}}}}} [10 20 30 40 50 60 70 80
 90 100 (110 "string" :kkk)] #:spec-provider.stats{:positional true}), :clojure.
spec.alpha/failure :instrument, :clojure.spec.test.alpha/caller {:file "trace.cl
j", :line 23, :var-scope spec-provider.trace/record-arg-values!, :local-fn fn}}
 at clojure.spec.test.alpha$spec_checking_fn$conform_BANG___3024.invoke (alpha.c
lj:132)
    clojure.spec.test.alpha$spec_checking_fn$fn__3026.doInvoke (alpha.clj:140)
    clojure.lang.RestFn.invoke (RestFn.java:436)
    spec_provider.trace$record_arg_values_BANG_$fn__3425.invoke (trace.clj:23)
    clojure.lang.AFn.applyToHelper (AFn.java:154)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$update_in$up__6838.invoke (core.clj:6185)
    clojure.core$update_in$up__6838.invoke (core.clj:6184)
    clojure.core$update_in$up__6838.invoke (core.clj:6184)
    clojure.core$update_in.invokeStatic (core.clj:6186)
    clojure.core$update_in.doInvoke (core.clj:6172)
    clojure.lang.RestFn.invoke (RestFn.java:445)
    clojure.lang.Atom.swap (Atom.java:65)
    clojure.core$swap_BANG_.invokeStatic (core.clj:2354)
    clojure.core$swap_BANG_.invoke (core.clj:2345)
    spec_provider.trace$record_arg_values_BANG_.invokeStatic (trace.clj:23)
    spec_provider.trace$record_arg_values_BANG_.invoke (trace.clj:19)
    spec_provider.trace_test$fn__3616$fn__3631$foo1__3637.doInvoke (trace_test.c
lj:119)
    clojure.lang.RestFn.invoke (RestFn.java:1449)
    spec_provider.trace_test$fn__3616$fn__3631.invoke (trace_test.clj:134)
    spec_provider.trace_test$fn__3616.invokeStatic (trace_test.clj:118)
    spec_provider.trace_test/fn (trace_test.clj:100)
    clojure.test$test_var$fn__9707.invoke (test.clj:717)
    clojure.test$test_var.invokeStatic (test.clj:717)
    clojure.test$test_var.invoke (test.clj:708)
    clojure.test$test_vars$fn__9733$fn__9738.invoke (test.clj:735)
    clojure.test$default_fixture.invokeStatic (test.clj:687)
    clojure.test$default_fixture.invoke (test.clj:683)
    clojure.test$test_vars$fn__9733.invoke (test.clj:735)
    clojure.test$default_fixture.invokeStatic (test.clj:687)
    clojure.test$default_fixture.invoke (test.clj:683)
    clojure.test$test_vars.invokeStatic (test.clj:731)
    clojure.test$test_all_vars.invokeStatic (test.clj:737)
    clojure.test$test_ns.invokeStatic (test.clj:758)
    clojure.test$test_ns.invoke (test.clj:743)
    user$eval1202$fn__1263.invoke (form-init8928585200070073552.clj:1)
    clojure.lang.AFn.applyToHelper (AFn.java:156)
    clojure.lang.AFn.applyTo (AFn.java:144)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.core$apply.invoke (core.clj:660)
    leiningen.core.injected$compose_hooks$fn__1132.doInvoke (form-init8928585200
070073552.clj:1)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:665)
    clojure.core$apply.invoke (core.clj:660)
    leiningen.core.injected$run_hooks.invokeStatic (form-init8928585200070073552
.clj:1)
    leiningen.core.injected$run_hooks.invoke (form-init8928585200070073552.clj:1
)
    leiningen.core.injected$prepare_for_hooks$fn__1137$fn__1138.doInvoke (form-i
nit8928585200070073552.clj:1)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.AFunction$1.doInvoke (AFunction.java:31)
    clojure.lang.RestFn.invoke (RestFn.java:408)
    clojure.core$map$fn__5851.invoke (core.clj:2755)
    clojure.lang.LazySeq.sval (LazySeq.java:42)
    clojure.lang.LazySeq.seq (LazySeq.java:51)
    clojure.lang.Cons.next (Cons.java:39)
    clojure.lang.RT.next (RT.java:709)
    clojure.core$next__5371.invokeStatic (core.clj:64)
    clojure.core$reduce1.invokeStatic (core.clj:944)
    clojure.core$reduce1.invokeStatic (core.clj:934)
    clojure.core$merge_with.invokeStatic (core.clj:3059)
    clojure.core$merge_with.doInvoke (core.clj:3051)
    clojure.lang.RestFn.applyTo (RestFn.java:139)
    clojure.core$apply.invokeStatic (core.clj:667)
    clojure.test$run_tests.invokeStatic (test.clj:768)
    clojure.test$run_tests.doInvoke (test.clj:768)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.core$apply.invokeStatic (core.clj:665)
    clojure.core$apply.invoke (core.clj:660)
    user$eval1202$fn__1275$fn__1308.invoke (form-init8928585200070073552.clj:1)
    user$eval1202$fn__1275$fn__1276.invoke (form-init8928585200070073552.clj:1)
    user$eval1202$fn__1275.invoke (form-init8928585200070073552.clj:1)
    user$eval1202.invokeStatic (form-init8928585200070073552.clj:1)
    user$eval1202.invoke (form-init8928585200070073552.clj:1)
    clojure.lang.Compiler.eval (Compiler.java:7176)
    clojure.lang.Compiler.eval (Compiler.java:7166)
    clojure.lang.Compiler.load (Compiler.java:7635)
    clojure.lang.Compiler.loadFile (Compiler.java:7573)
    clojure.main$load_script.invokeStatic (main.clj:452)
    clojure.main$init_opt.invokeStatic (main.clj:454)
    clojure.main$init_opt.invoke (main.clj:454)
    clojure.main$initialize.invokeStatic (main.clj:485)
    clojure.main$null_opt.invokeStatic (main.clj:519)
    clojure.main$null_opt.invoke (main.clj:516)
    clojure.main$main.invokeStatic (main.clj:598)
    clojure.main$main.doInvoke (main.clj:561)
    clojure.lang.RestFn.applyTo (RestFn.java:137)
    clojure.lang.Var.applyTo (Var.java:705)
    clojure.main.main (main.java:37)

goog.string/format used incorrectly

This require form doesn't look right:

#?(:cljs [goog.string.format :refer [format]])))

See https://clojurescript.org/reference/google-closure-library#requiring-a-function for a way to use format.

An alternative, since it appears to be only used once for string concatenation is to use str.

Repro:

deps.edn:

{:deps {org.clojure/clojurescript {:mvn/version "1.10.238"}
        spec-provider {:mvn/version "0.4.12"}
        org.clojure/test.check {:mvn/version "0.10.0-alpha2"}}}
$ clj -Srepro -m cljs.main -re node
ClojureScript 1.10.238
cljs.user=> (require 'spec-provider.provider)
nil
cljs.user=> (spec-provider.provider/infer-specs nil :foo)
repl:13
throw e__6388__auto__;
^

TypeError: goog.string.format.format is not a function
    at Function.spec_provider.provider.infer_specs.cljs$core$IFn$_invoke$arity$3 (/private/var/folders/gx/nymj3l7x4zq3gxb97v2zwzb40000gn/T/out7158618387314285623868872509503290/spec_provider/provider.js:424:54)
    at spec_provider$provider$infer_specs (/private/var/folders/gx/nymj3l7x4zq3gxb97v2zwzb40000gn/T/out7158618387314285623868872509503290/spec_provider/provider.js:408:43)
    at Function.spec_provider.provider.infer_specs.cljs$core$IFn$_invoke$arity$2 (/private/var/folders/gx/nymj3l7x4zq3gxb97v2zwzb40000gn/T/out7158618387314285623868872509503290/spec_provider/provider.js:418:43)
    at spec_provider$provider$infer_specs (/private/var/folders/gx/nymj3l7x4zq3gxb97v2zwzb40000gn/T/out7158618387314285623868872509503290/spec_provider/provider.js:404:43)
    at repl:1:114
    at repl:9:3
    at repl:14:4
    at Script.runInThisContext (vm.js:65:33)
    at Object.runInThisContext (vm.js:197:38)
    at Domain.<anonymous> ([stdin]:76:38)

Trust any defined specs when inferring

Just a thought. Relates to map-of vs keys issue.

Perhaps the spec inferring process could be passed a set of known specs (spec registry) and work on a "validate if present, fill in the blanks if not" manner.

Benefits here would be:

  • Allows fleshing out incomplete specs
  • Mechanism to inform the spec analysis (stepping over tricky things like map-of vs keys)

Incrementally add examples to spec-provider

Is it possible to add data to the stats incrementally, rather than passing all the data in upfront with infer-specs? I'm inspired by the FAQ that says: "...you can use a lazy sequence to stream your huge table through it if you feel that's necessary..."

Heterogenously namespaced keywords

Hi, thanks for putting together this fantastic library!

I'm having trouble figuring out how to generate specs for entity maps with heterogenously namespaced keywords. For example:

{:a/b :value
 :c/d "value"}

As best I can tell, spec-provider assumes that all keys in a map are either un-namespaced, or that they all have the same namespace. Am I missing something?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.