google_analytics_page_view_ranking 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8ee8e42ced170bad7d73cdcb7f638fa21690bcbc
4
+ data.tar.gz: 8225c0d614fb23af95d4527d71278cb90c16d320
5
+ SHA512:
6
+ metadata.gz: 6a9e386c30289bfb2311ecb6e995d0eae5c99ef3c9c4e7268749dd3839d258d4535fec5eea7b48fed71ff3c57d326f6406d667e774479365d8bd36e6b747fd9e
7
+ data.tar.gz: b916ee55475fdf039997a5760266c219e742f0dcba82598fd3600c573e0246b9bebc715baf868f4fc1da766cdc4d99a7ec1284b8155d0a1b20a1bd959205d4f3
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.2.2
5
+ before_install: gem install bundler -v 1.12.5
@@ -0,0 +1,49 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, and in the interest of
4
+ fostering an open and welcoming community, we pledge to respect all people who
5
+ contribute through reporting issues, posting feature requests, updating
6
+ documentation, submitting pull requests or patches, and other activities.
7
+
8
+ We are committed to making participation in this project a harassment-free
9
+ experience for everyone, regardless of level of experience, gender, gender
10
+ identity and expression, sexual orientation, disability, personal appearance,
11
+ body size, race, ethnicity, age, religion, or nationality.
12
+
13
+ Examples of unacceptable behavior by participants include:
14
+
15
+ * The use of sexualized language or imagery
16
+ * Personal attacks
17
+ * Trolling or insulting/derogatory comments
18
+ * Public or private harassment
19
+ * Publishing other's private information, such as physical or electronic
20
+ addresses, without explicit permission
21
+ * Other unethical or unprofessional conduct
22
+
23
+ Project maintainers have the right and responsibility to remove, edit, or
24
+ reject comments, commits, code, wiki edits, issues, and other contributions
25
+ that are not aligned to this Code of Conduct, or to ban temporarily or
26
+ permanently any contributor for other behaviors that they deem inappropriate,
27
+ threatening, offensive, or harmful.
28
+
29
+ By adopting this Code of Conduct, project maintainers commit themselves to
30
+ fairly and consistently applying these principles to every aspect of managing
31
+ this project. Project maintainers who do not follow or enforce the Code of
32
+ Conduct may be permanently removed from the project team.
33
+
34
+ This code of conduct applies both within project spaces and in public spaces
35
+ when an individual is representing the project or its community.
36
+
37
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
38
+ reported by contacting a project maintainer at matsumura.aki@gmail.com. All
39
+ complaints will be reviewed and investigated and will result in a response that
40
+ is deemed necessary and appropriate to the circumstances. Maintainers are
41
+ obligated to maintain confidentiality with regard to the reporter of an
42
+ incident.
43
+
44
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
45
+ version 1.3.0, available at
46
+ [http://contributor-covenant.org/version/1/3/0/][version]
47
+
48
+ [homepage]: http://contributor-covenant.org
49
+ [version]: http://contributor-covenant.org/version/1/3/0/
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in google_analytics_page_view_ranking.gemspec
4
+ gemspec
@@ -0,0 +1,140 @@
1
+ # GoogleAnalyticsPageViewRanking
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/google_analytics_page_view_ranking`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'google_analytics_page_view_ranking'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install google_analytics_page_view_ranking
22
+
23
+ ## Usage
24
+
25
+ ### Setup Google Client id and secret token
26
+ Create google development project on this page
27
+ https://console.developers.google.com/
28
+ Active 'Google Analytics API'
29
+
30
+ Set CALLBACK URL example https://www.example.com/oauth2callback
31
+
32
+ ### Get access_token and refresh_token
33
+
34
+ Open this page and login analytics account
35
+
36
+ ````
37
+ https://accounts.google.com/o/oauth2/auth
38
+ ?response_type=code
39
+ &access_type=offline
40
+ &client_id=[YOUR CLIENT ID]
41
+ &redirect_uri=[CALLBACK URL]
42
+ &scope=https://www.googleapis.com/auth/analytics.readonly
43
+ ````
44
+
45
+ Copy redirect url and get token
46
+
47
+ https://www.example.com/oauth2callback?code=[TOKEN]
48
+
49
+ Get access_token and refresh_token
50
+
51
+ ````
52
+ curl -d client_id=[YOUR CLIENT ID] -d client_secret=[YOUR CLIENT SECRET] -d redirect_uri=[CALLBACK URL] -d grant_type=authorization_code -d code=[TOKEN] https://accounts.google.com/o/oauth2/token
53
+ ````
54
+
55
+ Response will
56
+
57
+ ````
58
+ {
59
+ "access_token" : "ACCESS TOKEN",
60
+ "token_type" : "Bearer",
61
+ "expires_in" : 3600,
62
+ "refresh_token" : "REFRESH TOKEN"
63
+ }%
64
+ ````
65
+
66
+ And set tokens on settings.yml
67
+
68
+ ````
69
+ google:
70
+ client_id: '[GOOGLE CLIENT ID]'
71
+ client_secret: '[GOOGLE CLIENT SECRET]'
72
+ oauth2_access_token: '[ACCESS TOKEN]'
73
+ oauth2_refresh_token: '[REFRESH TOKEN]'
74
+ analytics_property_id: '[ANALYTICS PROPERTY ID]' # ex) UA-XXXXXXXX-1
75
+ ````
76
+
77
+ ### Setup your model
78
+
79
+ ````
80
+ class Article < ActiveRecord::Base
81
+ include GoogleAnalyticsPageViewRanking::Ranking
82
+ end
83
+ ````
84
+
85
+ Fetch data from analytics and create record. Open rails console.
86
+
87
+ ````
88
+ > Article.refresh_daily_ranking!
89
+ > Article.refresh_weekly_ranking!
90
+ > Article.refresh_monthly_ranking!
91
+ ````
92
+
93
+ Get Record with ranking order.
94
+
95
+ ````
96
+ > Article.daily_ranking
97
+ > Article.weekly_ranking
98
+ > Article.monthly_ranking
99
+ ````
100
+
101
+ ### Set daily task
102
+
103
+ ````
104
+ GoogleAnalyticsPageViewRanking.refresh_all_rankings
105
+ ````
106
+
107
+ ### Options
108
+
109
+ 基本設定では、 `/articles/10` というURLを前提にAnalyticsからデータを取得する実装になっている。
110
+ `/categories/4/articles/10` というようなURLの場合は以下のメソッドをオーバーライドする
111
+
112
+ ````
113
+ class Article < ActiveRecord::Base
114
+ # ...
115
+
116
+ # Override for GoogleAnalyticsPageViewRanking
117
+ def self.google_analytics_page_path_regex
118
+ "^/categories/[0-9]+/articles/[0-9]+$"
119
+ end
120
+ end
121
+ ````
122
+
123
+ また、レコードの指定が数字のIDではない場合は、以下のメソッドをオーバーライドする
124
+
125
+ ````
126
+ def self.google_analytics_find_item(value)
127
+ target_id = value.page_path.split('/').last
128
+ find_by(slug: target_id)
129
+ end
130
+ ````
131
+
132
+ ## Development
133
+
134
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
135
+
136
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
137
+
138
+ ## Contributing
139
+
140
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/google_analytics_page_view_ranking. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "google_analytics_page_view_ranking"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'google_analytics_page_view_ranking/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "google_analytics_page_view_ranking"
8
+ spec.version = GoogleAnalyticsPageViewRanking::VERSION
9
+ spec.authors = ["Akihiro MATSUMURA"]
10
+ spec.email = ["matsumura.aki@gmail.com"]
11
+
12
+ spec.summary = %q{}
13
+ spec.description = %q{}
14
+ spec.homepage = ""
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'oauth2'
22
+ spec.add_dependency 'garb'
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.12"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ spec.add_development_dependency "rspec", "~> 3.0"
27
+ end
@@ -0,0 +1,8 @@
1
+ Description:
2
+ Migration for GoogleAnalyticsPageViewRanking
3
+
4
+ Example:
5
+ rails generate migration Thing
6
+
7
+ This will create:
8
+ db/migrate/xxx_add_page_views.rb
@@ -0,0 +1,17 @@
1
+ require 'rails/generators'
2
+ require 'rails/generators/migration'
3
+
4
+ module GoogleAnalyticsPageViewRanking
5
+ class MigrationGenerator < Rails::Generators::Base
6
+ include Rails::Generators::Migration
7
+ source_root File.expand_path('../templates', __FILE__)
8
+
9
+ def self.next_migration_number(path)
10
+ Time.now.utc.strftime("%Y%m%d%H%M%S")
11
+ end
12
+
13
+ def create_model_file
14
+ migration_template "create_page_views.rb", "db/migrate/create_page_views.rb"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,14 @@
1
+ class CreatePageViews < ActiveRecord::Migration
2
+ def change
3
+ create_table :page_views do |t|
4
+ t.string :item_type
5
+ t.integer :item_id
6
+ t.string :period_type
7
+ t.integer :page_view
8
+
9
+ t.timestamps
10
+ end
11
+
12
+ add_index :page_views, [:item_type, :item_id, :period_type], unique: true
13
+ end
14
+ end
@@ -0,0 +1,38 @@
1
+ require 'google_analytics_page_view_ranking/version'
2
+ require 'google_analytics_page_view_ranking/page_view'
3
+ require 'google_analytics_page_view_ranking/ranking'
4
+
5
+ module GoogleAnalyticsPageViewRanking
6
+ @@target_classes = []
7
+
8
+ def self.add_target_class(klass)
9
+ @@target_classes << klass unless @@target_classes.include?(klass)
10
+ end
11
+
12
+ def self.target_classes
13
+ @@target_classes
14
+ end
15
+
16
+ def self.refresh_all_rankings
17
+ GoogleAnalyticsPageViewRanking::PageView.transaction do
18
+ GoogleAnalyticsPageViewRanking::PageView.delete_all
19
+ self.target_classes.each do |klass|
20
+ log "[GoogleAnalyticsPageViewRanking] Start #{klass}"
21
+ log "[GoogleAnalyticsPageViewRanking] Start #{klass}: daily"
22
+ klass.refresh_daily_ranking!
23
+ log "[GoogleAnalyticsPageViewRanking] Start #{klass}: weekly"
24
+ klass.refresh_weekly_ranking!
25
+ log "[GoogleAnalyticsPageViewRanking] Start #{klass}: monthly"
26
+ klass.refresh_monthly_ranking!
27
+ end
28
+ end
29
+ end
30
+
31
+ def self.log(str)
32
+ if defined?(Rails)
33
+ Rails.logger.info str
34
+ else
35
+ puts str
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,55 @@
1
+ require 'garb'
2
+ require 'oauth2'
3
+
4
+ module GoogleAnalyticsPageViewRanking
5
+ class GoogleAnalytics
6
+ def initialize
7
+ garb_login
8
+ set_target_property(Settings.google.analytics_property_id)
9
+ if @profile.nil?
10
+ fail('Failed get target property')
11
+ end
12
+ self
13
+ end
14
+
15
+ def set_target_property(property_id)
16
+ @profile = Garb::Management::Profile.all.detect do |p|
17
+ p.web_property_id == property_id
18
+ end
19
+ end
20
+
21
+ def ranking(page_path_regex, start_date, end_date, limit)
22
+ @profile.pageviews(
23
+ limit: limit,
24
+ sort: :pageviews.desc,
25
+ start_date: start_date,
26
+ end_datd: end_date,
27
+ filters: { :page_path.contains => page_path_regex }
28
+ )
29
+ end
30
+
31
+ def garb_login
32
+ if (args = [Settings.google.client_id, Settings.google.client_secret, Settings.google.oauth2_access_token, Settings.google.oauth2_refresh_token]).all?(&:present?)
33
+ garb_oauth_login(*args)
34
+ else
35
+ fail 'NO AUTH ENV is given'
36
+ end
37
+ end
38
+
39
+ def garb_oauth_login(client_id, client_secret, access_token, refresh_token)
40
+ client = OAuth2::Client.new(
41
+ client_id, client_secret,
42
+ site: 'https://accounts.google.com',
43
+ authorize_url: '/o/oauth2/auth',
44
+ token_url: '/o/oauth2/token'
45
+ )
46
+ Garb::Session.access_token = OAuth2::AccessToken.from_hash(client, access_token: access_token, refresh_token: refresh_token).refresh!
47
+ end
48
+
49
+ class Pageviews
50
+ extend Garb::Model
51
+ metrics :pageviews
52
+ dimensions :page_path
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,11 @@
1
+ module GoogleAnalyticsPageViewRanking
2
+ class PageView < ActiveRecord::Base
3
+ extend Enumerize
4
+
5
+ belongs_to :item, polymorphic: true
6
+
7
+ enumerize :period_type, in: [:daily, :weekly, :monthly]
8
+
9
+ validates :item_type, uniqueness: { scope: [:item_id, :period_type] }
10
+ end
11
+ end
@@ -0,0 +1,72 @@
1
+ require 'active_support/concern'
2
+ require 'google_analytics_page_view_ranking/google_analytics'
3
+
4
+ module GoogleAnalyticsPageViewRanking
5
+ module Ranking
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ has_many :rankings, class_name: 'GoogleAnalyticsPageViewRanking::PageView', as: :item, dependent: :destroy
10
+
11
+ scope :ranking, -> { joins(:rankings).order('page_views.page_view desc') }
12
+ scope :daily_ranking, -> { ranking.where(page_views: {period_type: 'daily'}) }
13
+ scope :weekly_ranking, -> { ranking.where(page_views: {period_type: 'weekly'}) }
14
+ scope :monthly_ranking, -> { ranking.where(page_views: {period_type: 'monthly'}) }
15
+
16
+ scope :ranking_list, -> { order(page_views: :desc) }
17
+
18
+ scope :top_ranking, -> { limit(15) }
19
+ scope :top_ranking_widget, -> { limit(3) }
20
+
21
+ GoogleAnalyticsPageViewRanking.add_target_class(self)
22
+ end
23
+
24
+ module ClassMethods
25
+ def refresh_daily_ranking!
26
+ start_date = Date.yesterday
27
+ end_date = Date.yesterday
28
+ refresh_ranking!(:daily, start_date, end_date)
29
+ end
30
+
31
+ def refresh_weekly_ranking!
32
+ start_date = 1.week.ago
33
+ end_date = Time.current
34
+ refresh_ranking!(:weekly, start_date, end_date)
35
+ end
36
+
37
+ def refresh_monthly_ranking!
38
+ start_date = 1.month.ago
39
+ end_date = Time.current
40
+ refresh_ranking!(:monthly, start_date, end_date)
41
+ end
42
+
43
+ def refresh_ranking!(period_type, start_date, end_date)
44
+ analytics = GoogleAnalyticsPageViewRanking::GoogleAnalytics.new
45
+ analytics.ranking(google_analytics_page_path_regex, start_date, end_date, google_analytics_fetch_limit).results.each do |value|
46
+ if target = google_analytics_find_item(value)
47
+ page_view = target.rankings.build
48
+ page_view.period_type = period_type
49
+ page_view.page_view = value.pageviews
50
+ unless page_view.save
51
+ GoogleAnalyticsPageViewRanking.log "[GoogleAnalyticsPageViewRanking] failed to save #{self.class_name}: #{value}"
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ def google_analytics_page_path_regex
58
+ item_type = model_name.element.to_sym
59
+ "^/#{item_type.to_s.pluralize}/[0-9]+$"
60
+ end
61
+
62
+ def google_analytics_find_item(value)
63
+ target_id = value.page_path.slice(/\d+\z/)
64
+ find_by(id: target_id)
65
+ end
66
+
67
+ def google_analytics_fetch_limit
68
+ 30
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,3 @@
1
+ module GoogleAnalyticsPageViewRanking
2
+ VERSION = "0.0.2"
3
+ end
metadata ADDED
@@ -0,0 +1,131 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: google_analytics_page_view_ranking
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ platform: ruby
6
+ authors:
7
+ - Akihiro MATSUMURA
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2016-08-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: oauth2
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: garb
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.12'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.12'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ description: ''
84
+ email:
85
+ - matsumura.aki@gmail.com
86
+ executables: []
87
+ extensions: []
88
+ extra_rdoc_files: []
89
+ files:
90
+ - ".gitignore"
91
+ - ".rspec"
92
+ - ".travis.yml"
93
+ - CODE_OF_CONDUCT.md
94
+ - Gemfile
95
+ - README.md
96
+ - Rakefile
97
+ - bin/console
98
+ - bin/setup
99
+ - google_analytics_page_view_ranking.gemspec
100
+ - lib/generators/google_analytics_page_view_ranking/migration/USAGE
101
+ - lib/generators/google_analytics_page_view_ranking/migration/migration_generator.rb
102
+ - lib/generators/google_analytics_page_view_ranking/migration/templates/create_page_views.rb
103
+ - lib/google_analytics_page_view_ranking.rb
104
+ - lib/google_analytics_page_view_ranking/google_analytics.rb
105
+ - lib/google_analytics_page_view_ranking/page_view.rb
106
+ - lib/google_analytics_page_view_ranking/ranking.rb
107
+ - lib/google_analytics_page_view_ranking/version.rb
108
+ homepage: ''
109
+ licenses: []
110
+ metadata: {}
111
+ post_install_message:
112
+ rdoc_options: []
113
+ require_paths:
114
+ - lib
115
+ required_ruby_version: !ruby/object:Gem::Requirement
116
+ requirements:
117
+ - - ">="
118
+ - !ruby/object:Gem::Version
119
+ version: '0'
120
+ required_rubygems_version: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ requirements: []
126
+ rubyforge_project:
127
+ rubygems_version: 2.4.5
128
+ signing_key:
129
+ specification_version: 4
130
+ summary: ''
131
+ test_files: []