triannon 0.5.3 → 0.5.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: c2ebd733067b7971cd2548562f807c5feed3dc31
4
- data.tar.gz: 6809922b340468bc2201c67d7f4c45656a24ffad
3
+ metadata.gz: 4bc729d85bad88af4e64d97bb59bcb387cf24bcd
4
+ data.tar.gz: c64868417a72673f936d3ab09e465bd3027f5b28
5
5
  SHA512:
6
- metadata.gz: 2701115eac46a00f11d06484c403da5c30c5a4f05bea5188cc2a80b4cdec54e693b0f5bbb15b882f2327e3e1cc494f6a4fd0c3e6c2a842beb3ad48ad1c1a282b
7
- data.tar.gz: 808a6fa21bca5e4bf5a5426c3a7860af1a8134dfb58c7cddb76368351017e8b71a876007085805fecdbbf00a165a351cca508c982a96b038f7d99f4cd1553cee
6
+ metadata.gz: 1bc829a33741ab23ca40e69d981f1701b2ce0556618274f107727dee8403a7481d12cfe3eefb207d761d275ec7dcdbda9cc1f4cb0e488e67617154edd917e544
7
+ data.tar.gz: 2e8440b488dde0e2dfb9f826979da480552209614cd5150108fb6be57bf17b2f28ca525e387bd4178502e4cfc02e3fe1683bd1e47e07b332fbd3f2f2736ace9c
data/README.md CHANGED
@@ -44,6 +44,65 @@ Generate the root annotations container on the LDP server
44
44
  $ rake triannon:create_root_container
45
45
  ```
46
46
 
47
+ # Client Interactions with Triannon
48
+
49
+ ### Get a list of annos
50
+ NOTE: implementation of Annotation Lists is coming!
51
+ GET: http://(host)/
52
+ GET: http://(host)/annotations
53
+
54
+ ### Get a particular anno
55
+ GET: http://(host)/annotations/(anno_id)
56
+ * use HTTP Accept header with mime type to indicate desired format
57
+ ** default: jsonld
58
+ *** indicate desired context url in the HTTP Accept header thus:
59
+ **** Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
60
+ **** Accept: application/ld+json; profile="http://iiif.io/api/presentation/2/context.json"
61
+
62
+ ** also supports turtle, rdfxml, json, html
63
+ *** indicated desired context url for jsonld as json in the HTTP Link header thus:
64
+ **** Accept: application/json
65
+ **** Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
66
+ ***** note that the "type" part is optional and refers to the type of the rel, which is the reference for all json-ld contexts.
67
+ ** see https://github.com/sul-dlss/triannon/blob/master/app/controllers/triannon/annotations_controller.rb #show method for mime formats accepted
68
+
69
+ #### JSON-LD context
70
+ You can request IIIF or OA context for jsonld.
71
+
72
+ The correct way:
73
+ GET: http://(host)/annotations/(anno_id)
74
+ * use HTTP Accept header with mime type and context url:
75
+ ** Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
76
+ ** Accept: application/ld+json; profile="http://iiif.io/api/presentation/2/context.json"
77
+
78
+ You can also use either of these methods (with the correct HTTP Accept header):
79
+
80
+ GET: http://(host)/annotations/iiif/(anno_id)
81
+ GET: http://(host)/annotations/(anno_id)?jsonld_context=iiif
82
+
83
+ GET: http://(host)/annotations/oa/(anno_id)
84
+ GET: http://(host)/annotations/(anno_id)?jsonld_context=oa
85
+
86
+ Note that OA (Open Annotation) is the default context if none is specified.
87
+
88
+ ### Create an anno
89
+ POST: http://(host)/annotations
90
+ * the body of the HTTP request should contain the annotation, as jsonld, turtle, or rdfxml
91
+ * the Content-Type header should be the mime type matching the body
92
+ * the anno to be created should NOT already have an assigned @id
93
+ * to get a particular format back, use the HTTP Accept header
94
+ ** to get a particular context for jsonld, do one of the following:
95
+ **** Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
96
+ **** Accept: application/ld+json; profile="http://iiif.io/api/presentation/2/context.json"
97
+ ** to get a particular jsonld context for jsonld as json, specify it in the HTTP Link header thus:
98
+ **** Accept: application/json
99
+ **** Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
100
+ ***** note that the "type" part is optional and refers to the type of the rel, which is the reference for all json-ld contexts.
101
+
102
+ ### Delete an anno
103
+ DELETE: http://(host)/annotations/(anno_id)
104
+
105
+
47
106
  ## Running the application in development
48
107
 
49
108
  There is a bundled rake task for running the test app, but there is some one-time set up.
@@ -57,8 +116,7 @@ $ rake jetty:unzip
57
116
  ```
58
117
  ##### Set up a Triannon flavored Solr
59
118
  ```console
60
- $ cp config/solr/solr.xml jetty/solr
61
- $ cp config/solr/triannon-core jetty/solr
119
+ rake triannon:solr_jetty_setup
62
120
  ```
63
121
 
64
122
  ##### Set up a runnable Rails app that uses triannon gem
data/Rakefile CHANGED
@@ -69,6 +69,12 @@ namespace :triannon do
69
69
  desc 'run test rails console w triannon but no jetty'
70
70
  task :console => :console_no_jetty
71
71
 
72
+
73
+ desc "set up triannon solr in test jetty"
74
+ task :solr_jetty_setup do
75
+ `cp -r config/solr/triannon-core jetty/solr`
76
+ `cp config/solr/solr.xml jetty/solr`
77
+ end
72
78
  end
73
79
 
74
80
 
@@ -3,7 +3,7 @@ require_dependency "triannon/application_controller"
3
3
  module Triannon
4
4
  class AnnotationsController < ApplicationController
5
5
  before_action :default_format_jsonld, only: [:show]
6
- before_action :set_annotation, only: [:show, :edit, :update, :destroy]
6
+ before_action :set_annotation, only: [:show, :update, :destroy]
7
7
  rescue_from Triannon::ExternalReferenceError, with: :ext_ref_error
8
8
 
9
9
  # GET /annotations
@@ -13,8 +13,16 @@ module Triannon
13
13
 
14
14
  # GET /annotations/1
15
15
  def show
16
+ # TODO: json.set! "@context", Triannon::JsonldContext::OA_DATED_CONTEXT_URL - would this work?
16
17
  respond_to do |format|
17
- format.jsonld { render_jsonld_per_context (params[:jsonld_context]) }
18
+ format.jsonld {
19
+ context_url = context_url_from_accept ? context_url_from_accept : context_url_from_link
20
+ if context_url && context_url == Triannon::JsonldContext::IIIF_CONTEXT_URL
21
+ render_jsonld_per_context("iiif", "application/ld+json")
22
+ else
23
+ render_jsonld_per_context(params[:jsonld_context], "application/ld+json")
24
+ end
25
+ }
18
26
  format.ttl {
19
27
  accept_return_type = mime_type_from_accept(["application/x-turtle", "text/turtle"])
20
28
  render :body => @annotation.graph.to_ttl, content_type: accept_return_type if accept_return_type }
@@ -23,7 +31,13 @@ module Triannon
23
31
  render :body => @annotation.graph.to_rdfxml, content_type: accept_return_type if accept_return_type }
24
32
  format.json {
25
33
  accept_return_type = mime_type_from_accept(["application/json", "text/x-json", "application/jsonrequest"])
26
- render_jsonld_per_context(params[:jsonld_context], accept_return_type) }
34
+ context_url = context_url_from_link ? context_url_from_link : context_url_from_accept
35
+ if context_url && context_url == Triannon::JsonldContext::IIIF_CONTEXT_URL
36
+ render_jsonld_per_context("iiif", accept_return_type)
37
+ else
38
+ render_jsonld_per_context(params[:jsonld_context], accept_return_type)
39
+ end
40
+ }
27
41
  format.xml {
28
42
  accept_return_type = mime_type_from_accept(["application/xml", "text/xml", "application/x-xml"])
29
43
  render :xml => @annotation.graph.to_rdfxml, content_type: accept_return_type if accept_return_type }
@@ -44,7 +58,9 @@ module Triannon
44
58
  # POST /annotations
45
59
  def create
46
60
  # FIXME: this is probably a bad way of allowing app form to be used as well as direct post requests
47
- if params["annotation"]
61
+ # see https://github.com/sul-dlss/triannon/issues/90 -- prob just want to fix the form to do a POST
62
+ # note that need to check for empty? if HTTP Header Content-Type is json (but not jsonld).
63
+ if params["annotation"] && !params["annotation"].empty?
48
64
  # it's from app html form
49
65
  params.require(:annotation).permit(:data)
50
66
  if params["annotation"]["data"]
@@ -52,13 +68,43 @@ module Triannon
52
68
  end
53
69
  else
54
70
  # it's a direct post request
55
- @annotation = Annotation.new(:data => request.body.read)
71
+ content_type = request.headers["Content-Type"]
72
+ @annotation = Annotation.new({:data => request.body.read, :expected_content_type => content_type})
56
73
  end
57
74
 
58
75
  if @annotation.save
59
- redirect_to @annotation, status: 201, notice: 'Annotation was successfully created.'
76
+ default_format_jsonld # NOTE: this must be here and not in before_filter or we get Missing template errors
77
+ respond_to do |format|
78
+ format.jsonld {
79
+ context_url = context_url_from_link ? context_url_from_link : context_url_from_accept
80
+ if context_url && context_url == Triannon::JsonldContext::IIIF_CONTEXT_URL
81
+ render :json => @annotation.jsonld_iiif, status: 201, content_type: "application/ld+json", notice: "Annotation #{@annotation.id} was successfully created."
82
+ else
83
+ render :json => @annotation.jsonld_oa, status: 201, content_type: "application/ld+json", notice: "Annotation #{@annotation.id} was successfully created."
84
+ end
85
+ }
86
+ format.ttl {
87
+ accept_return_type = mime_type_from_accept(["application/x-turtle", "text/turtle"])
88
+ render :body => @annotation.graph.to_ttl, status: 201, notice: "Annotation #{@annotation.id} was successfully created.", content_type: accept_return_type if accept_return_type }
89
+ format.rdfxml {
90
+ accept_return_type = mime_type_from_accept(["application/rdf+xml", "text/rdf+xml", "text/rdf"])
91
+ render :body => @annotation.graph.to_rdfxml, status: 201, notice: "Annotation #{@annotation.id} was successfully created.", content_type: accept_return_type if accept_return_type }
92
+ format.json {
93
+ accept_return_type = mime_type_from_accept(["application/json", "text/x-json", "application/jsonrequest"])
94
+ context_url = context_url_from_link ? context_url_from_link : context_url_from_accept
95
+ if context_url && context_url == Triannon::JsonldContext::IIIF_CONTEXT_URL
96
+ render :json => @annotation.jsonld_iiif, status: 201, notice: "Annotation #{@annotation.id} was successfully created.", content_type: accept_return_type if accept_return_type
97
+ else
98
+ render :json => @annotation.jsonld_oa, status: 201, notice: "Annotation #{@annotation.id} was successfully created.", content_type: accept_return_type if accept_return_type
99
+ end
100
+ }
101
+ format.xml {
102
+ accept_return_type = mime_type_from_accept(["application/xml", "text/xml", "application/x-xml"])
103
+ render :body => @annotation.graph.to_rdfxml, status: 201, notice: "Annotation #{@annotation.id} was successfully created.", content_type: accept_return_type if accept_return_type }
104
+ format.html { render :show, location: @annotation, status: 201, content_type: "text/html", notice: "Annotation #{@annotation.id} was successfully created." }
105
+ end
60
106
  else
61
- render :new
107
+ render :new, status: 400
62
108
  end
63
109
  end
64
110
 
@@ -77,62 +123,108 @@ module Triannon
77
123
  @annotation.destroy
78
124
  redirect_to annotations_url, status: 204, notice: 'Annotation was successfully destroyed.'
79
125
  end
126
+
127
+ private
80
128
 
81
- private
82
- # Use callbacks to share common setup or constraints between actions.
83
- def set_annotation
84
- @annotation = Annotation.find(params[:id])
129
+ def set_annotation
130
+ @annotation = Annotation.find(params[:id])
131
+ end
132
+
133
+ # set format to jsonld if it isn't already set
134
+ def default_format_jsonld
135
+ if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
136
+ request.format = "jsonld"
85
137
  end
86
-
87
- def default_format_jsonld
88
- if ((!request.accept || request.accept.empty?) && (!params[:format] || params[:format].empty?))
89
- request.format = "jsonld"
138
+ end
139
+
140
+ # find first mime type from request.accept that matches return mime type
141
+ def mime_type_from_accept(return_mime_types)
142
+ @mime_type_from_accept ||= begin
143
+ if request.accept && request.accept.is_a?(String)
144
+ accept_mime_types = request.accept.split(',')
145
+ accept_mime_types.each { |mime_type|
146
+ mime_str = mime_type.split("; profile=").first.strip
147
+ if return_mime_types.include? mime_str
148
+ return mime_str
149
+ end
150
+ }
90
151
  end
91
152
  end
153
+ end
92
154
 
93
- # find first mime type from request.accept that matches return mime type
94
- def mime_type_from_accept(return_mime_types)
95
- @mime_type_from_accept ||= begin
96
- if request.accept && request.accept.is_a?(String)
97
- accepted_formats = request.accept.split(',')
98
- accepted_formats.each { |accepted_format|
99
- if return_mime_types.include? accepted_format
100
- return accepted_format
101
- end
102
- }
155
+ # parse the Accept HTTP header for the value of profile if it is a request for jsonld or json
156
+ # e.g. Accept: application/ld+json; profile="http://www.w3.org/ns/oa-context-20130208.json"
157
+ # @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
158
+ def context_url_from_accept
159
+ if request.format == "jsonld" || request.format == "json"
160
+ accept_str = request.accept
161
+ if accept_str && accept_str.split("profile=") && accept_str.split("profile=").last
162
+ context_url = accept_str.split("profile=").last.strip
163
+ context_url = context_url[1, context_url.size] if context_url.start_with?('"')
164
+ context_url = context_url[0, context_url.size-1] if context_url.end_with?('"')
165
+ case context_url
166
+ when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
167
+ Triannon::JsonldContext::OA_CONTEXT_URL,
168
+ Triannon::JsonldContext::IIIF_CONTEXT_URL
169
+ context_url
170
+ else
171
+ nil
103
172
  end
104
173
  end
105
174
  end
175
+ end
106
176
 
107
- # handle Triannon::ExternalReferenceError
108
- def ext_ref_error(exception)
109
- render plain: exception.message, status: 403
110
- end
111
-
112
- # render json_ld respecting requested context
113
- # @param [String] req_context set to "iiif" or "oa". Default is OA
114
- # @param [String] mime_type the mime type to be set in the Content-Type header of the HTTP response
115
- def render_jsonld_per_context (req_context, mime_type=nil)
116
- case req_context
117
- when "iiif", "IIIF"
118
- if mime_type
119
- render :json => @annotation.jsonld_iiif, content_type: mime_type
177
+ # parse the Accept HTTP Link for the value of rel if it is a request for jsonld or json
178
+ # e.g. Link: http://www.w3.org/ns/oa.json; rel="http://www.w3.org/ns/json-ld#context"; type="application/ld+json"
179
+ # note that the "type" part is optional
180
+ # @return [String] url for jsonld @context or nil if missing or non-jsonld/json format
181
+ def context_url_from_link
182
+ if request.format == "jsonld" || request.format == "json"
183
+ link_str = request.headers["Link"]
184
+ if link_str && link_str.split("; rel=") && link_str.split("; rel=").first
185
+ context_url = link_str.split("; rel=").first.strip
186
+ case context_url
187
+ when Triannon::JsonldContext::OA_DATED_CONTEXT_URL,
188
+ Triannon::JsonldContext::OA_CONTEXT_URL,
189
+ Triannon::JsonldContext::IIIF_CONTEXT_URL
190
+ context_url
120
191
  else
121
- render :json => @annotation.jsonld_iiif
122
- end
123
- when "oa", "OA"
124
- if mime_type
125
- render :json => @annotation.jsonld_oa, content_type: mime_type
126
- else
127
- render :json => @annotation.jsonld_oa
128
- end
192
+ nil
193
+ end
194
+ end
195
+ end
196
+ end
197
+
198
+ # handle Triannon::ExternalReferenceError
199
+ def ext_ref_error(exception)
200
+ render plain: exception.message, status: 403
201
+ end
202
+
203
+ # render json_ld respecting requested context
204
+ # @param [String] req_context set to "iiif" or "oa". Default is oa
205
+ # @param [String] mime_type the mime type to be set in the Content-Type header of the HTTP response
206
+ def render_jsonld_per_context (req_context, mime_type=nil)
207
+ case req_context
208
+ when "iiif", "IIIF"
209
+ if mime_type
210
+ render :json => @annotation.jsonld_iiif, content_type: mime_type
129
211
  else
130
- if mime_type
131
- render :json => @annotation.jsonld_oa, content_type: mime_type
132
- else
133
- render :json => @annotation.jsonld_oa
134
- end
135
- end
212
+ render :json => @annotation.jsonld_iiif
213
+ end
214
+ when "oa", "OA"
215
+ if mime_type
216
+ render :json => @annotation.jsonld_oa, content_type: mime_type
217
+ else
218
+ render :json => @annotation.jsonld_oa
219
+ end
220
+ else
221
+ if mime_type
222
+ render :json => @annotation.jsonld_oa, content_type: mime_type
223
+ else
224
+ render :json => @annotation.jsonld_oa
225
+ end
136
226
  end
137
- end
138
- end
227
+ end
228
+
229
+ end # class AnnotationsController
230
+ end # module Triannon
@@ -6,7 +6,7 @@ module Triannon
6
6
  after_save :solr_save
7
7
  after_destroy :solr_delete
8
8
 
9
- attr_accessor :id, :data
9
+ attr_accessor :id, :data, :expected_content_type
10
10
 
11
11
  validates_each :data do |record, attr, value|
12
12
  record.errors.add attr, 'less than 30 chars' if value.to_s.length < 30
@@ -45,7 +45,7 @@ module Triannon
45
45
  _run_save_callbacks do
46
46
  # check if valid?
47
47
  graph
48
- @id = Triannon::LdpWriter.create_anno self
48
+ @id = Triannon::LdpWriter.create_anno self if graph && graph.size > 2
49
49
  end
50
50
  end
51
51
 
@@ -113,28 +113,69 @@ protected
113
113
 
114
114
  private
115
115
 
116
- # loads RDF::Graph from data attribute. If data is in json-ld, converts it to turtle.
116
+ # loads RDF::Graph from data attribute. If data is in json-ld or rdfxml, converts it to turtle.
117
117
  def data_to_graph
118
118
  if data
119
119
  data.strip!
120
- case data
121
- when /\A\{.+\}\Z/m # (Note: \A and \Z and m are needed instead of ^$ due to \n in data)
122
- g ||= RDF::Graph.new << JSON::LD::API.toRdf(json_ld) if json_ld
123
- self.data = g.dump(:ttl) if g
124
- when /\A<.+>\Z/m # (Note: \A and \Z and m are needed instead of ^$ due to \n in data)
125
- g = RDF::Graph.new
126
- g.from_rdfxml(data)
127
- g = nil if g.size == 0
128
- when /\.\Z/ # (Note: \Z is needed instead of $ due to \n in data)
129
- # turtle ends in period
130
- g = RDF::Graph.new
131
- g.from_ttl(data)
132
- g = nil if g.size == 0
120
+ if expected_content_type
121
+ case Mime::Type.lookup(expected_content_type).symbol
122
+ when :jsonld, :json
123
+ g = jsonld_to_graph
124
+ when :ttl
125
+ g = ttl_to_graph
126
+ when :rdfxml, :xml
127
+ g = rdfxml_to_graph
128
+ else
129
+ g = nil
130
+ end
131
+ else # infer the content type from the content itself
132
+ case data
133
+ # \A and \Z and m are needed instead of ^$ due to \n in data
134
+ when /\A\{.+\}\Z/m
135
+ g = jsonld_to_graph
136
+ when /\A<.+>\Z/m
137
+ g = rdfxml_to_graph
138
+ when /\.\Z/ # turtle ends in period
139
+ g = ttl_to_graph
140
+ else
141
+ g = nil
142
+ end
133
143
  end
134
144
  end
135
145
  g
136
146
  end
137
147
 
148
+ # create and load an RDF::Graph object from turtle in data attrib
149
+ # @return [RDF::Graph] populated RDF::Graph object, or nil
150
+ def ttl_to_graph
151
+ g = RDF::Graph.new.from_ttl(data)
152
+ g = nil if g && g.size == 0
153
+ g
154
+ end
155
+
156
+ # create and load an RDF::Graph object from jsonld in data attrib
157
+ # SIDE EFFECT: converts data to turtle for LdpWriter
158
+ # @return [RDF::Graph] populated RDF::Graph object, or nil
159
+ def jsonld_to_graph
160
+ # need to do this to avoid external lookup of jsonld context
161
+ g ||= RDF::Graph.new << JSON::LD::API.toRdf(json_ld) if json_ld
162
+ g = nil if g && g.size == 0
163
+ self.data = g.dump(:ttl) if g # LdpWriter expects ttl
164
+ g
165
+ end
166
+
167
+ # create and load an RDF::Graph object from rdfxml in data attrib
168
+ # SIDE EFFECT: converts data to turtle for LdpWriter
169
+ # @return [RDF::Graph] populated RDF::Graph object, or nil
170
+ def rdfxml_to_graph
171
+ g = RDF::Graph.new.from_rdfxml(data)
172
+ g = nil if g && g.size == 0
173
+ self.data = g.dump(:ttl) if g # LdpWriter expects ttl
174
+ g
175
+ end
176
+
177
+ # avoid external lookup of jsonld context by putting it inline
178
+ # @return [Hash] the parsed json after the context is put inline
138
179
  def json_ld
139
180
  if data.match(/"@context"\s*\:\s*"http\:\/\/www\.w3\.org\/ns\/oa-context-20130208\.json"/)
140
181
  data.sub!("\"http://www.w3.org/ns/oa-context-20130208.json\"", Triannon::JsonldContext.oa_context)
@@ -35,9 +35,9 @@ module Triannon
35
35
 
36
36
  # @return json-ld representation of graph with OpenAnnotation context as a url
37
37
  def jsonld_oa
38
- inline_context = @graph.dump(:jsonld, :context => Triannon::JsonldContext::OA_CONTEXT_URL)
38
+ inline_context = @graph.dump(:jsonld, :context => Triannon::JsonldContext::OA_DATED_CONTEXT_URL)
39
39
  hash_from_json = JSON.parse(inline_context)
40
- hash_from_json["@context"] = Triannon::JsonldContext::OA_CONTEXT_URL
40
+ hash_from_json["@context"] = Triannon::JsonldContext::OA_DATED_CONTEXT_URL
41
41
  hash_from_json.to_json
42
42
  end
43
43
 
@@ -1,3 +1,3 @@
1
1
  module Triannon
2
- VERSION = "0.5.3"
2
+ VERSION = "0.5.4"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: triannon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.3
4
+ version: 0.5.4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Beer
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2015-02-10 00:00:00.000000000 Z
13
+ date: 2015-03-10 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rails
@@ -315,7 +315,7 @@ files:
315
315
  - lib/triannon/version.rb
316
316
  homepage:
317
317
  licenses:
318
- - Apache 2
318
+ - Apache-2.0
319
319
  metadata: {}
320
320
  post_install_message:
321
321
  rdoc_options: []