bright 0.1.0

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: 381169bba4d0de609bb9650455e7d8e4568d9de4
4
+ data.tar.gz: 53b5f910b2a5655a194b6a913f75c5a67b1753c5
5
+ SHA512:
6
+ metadata.gz: 1d8fe64474322364c96ebb6f51d30a944b7cb0ba1e3f207f5dba7cb99cb415cbb01a197155c5af9d4f8444354277debc97713c5ea2011a10eba671ce8068ec45
7
+ data.tar.gz: 8f0a9ea2b0f0299deb7bb3be69713d42b3d315f120af8a045882befa92f992436eae8b5b7690a93db8258de13bfb073ca0b2c3369cc0b3cfaea121ea0c15cfe4
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in bright.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Stephen Heuer
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,31 @@
1
+ # Bright
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'bright'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install bright
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/bright/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'bright/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "bright"
8
+ spec.version = Bright::VERSION
9
+ spec.authors = ["Arux Software"]
10
+ spec.email = ["sheuer@aruxsoftware.com"]
11
+ spec.summary = "Framework and tools for dealing with Student Information Systems"
12
+ spec.description = "Bright is a simple Student Information System API abstraction library used in and sponsored by FeePay. It is written by Stephen Heuer, Steven Novotny, and contributors. The aim of the project is to abstract as many parts as possible away from the user to offer a consistent interface across all supported Student Information System APIs."
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "httpi", "~> 2.1"
22
+ spec.add_runtime_dependency "json", ">= 0"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.7"
25
+ spec.add_development_dependency "rake", "~> 10.0"
26
+ end
@@ -0,0 +1,20 @@
1
+ require "bright/version"
2
+ require "bright/errors"
3
+
4
+ require "bright/model"
5
+ require "bright/student"
6
+ require "bright/address"
7
+ require "bright/enrollment"
8
+ require "bright/school"
9
+
10
+ require "bright/connection"
11
+ require "bright/response_collection"
12
+
13
+ require "bright/sis_apis/base.rb"
14
+ require "bright/sis_apis/tsis.rb"
15
+ require "bright/sis_apis/power_school.rb"
16
+ require "bright/sis_apis/aeries.rb"
17
+
18
+ module Bright
19
+
20
+ end
@@ -0,0 +1,17 @@
1
+ module Bright
2
+ class Address < Model
3
+ @attribute_names = [:street, :apt, :city, :state, :postal_code, :lattitude, :longitude, :type]
4
+ attr_accessor *@attribute_names
5
+
6
+ alias lat lattitude
7
+ alias lng longitude
8
+
9
+ def geographical_coordinates
10
+ if self.lattitude and self.longitude
11
+ "#{self.lattitude},#{self.longitude}"
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+
@@ -0,0 +1,119 @@
1
+ require 'uri'
2
+ require 'httpi'
3
+ require 'benchmark'
4
+
5
+ module Bright
6
+ class Connection
7
+ OPEN_TIMEOUT = 60
8
+ READ_TIMEOUT = 60
9
+ VERIFY_PEER = true
10
+
11
+ attr_accessor :endpoint
12
+ attr_accessor :open_timeout
13
+ attr_accessor :read_timeout
14
+ attr_accessor :verify_peer
15
+ attr_accessor :ssl_version
16
+ attr_accessor :pem
17
+ attr_accessor :pem_password
18
+ attr_accessor :logger
19
+ attr_accessor :tag
20
+ attr_accessor :ignore_http_status
21
+ attr_accessor :proxy_address
22
+ attr_accessor :proxy_port
23
+
24
+ def initialize(endpoint)
25
+ @endpoint = endpoint.is_a?(URI) ? endpoint : URI.parse(endpoint)
26
+ @open_timeout = OPEN_TIMEOUT
27
+ @read_timeout = READ_TIMEOUT
28
+ @verify_peer = VERIFY_PEER
29
+ @ignore_http_status = false
30
+ @ssl_version = nil
31
+ @proxy_address = nil
32
+ @proxy_port = nil
33
+ end
34
+
35
+ def request(method, body, headers = {})
36
+ request_start = Time.now.to_f
37
+
38
+ begin
39
+ info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint}", tag
40
+
41
+ result = nil
42
+
43
+ realtime = Benchmark.realtime do
44
+ request = HTTPI::Request.new(endpoint.to_s)
45
+ request.headers = headers
46
+ request.body = body if body
47
+ request.auth.ssl.verify_mode = :none if !@verify_peer
48
+ configure_proxy(request)
49
+ configure_timeouts(request)
50
+
51
+ result = case method
52
+ when :get
53
+ raise ArgumentError, "GET requests do not support a request body" if body
54
+ HTTPI.get(request)
55
+ when :post
56
+ debug body
57
+ HTTPI.post(request)
58
+ when :put
59
+ debug body
60
+ HTTPI.put(request)
61
+ when :patch
62
+ debug body
63
+ HTTPI.patch(request)
64
+ when :delete
65
+ HTTPI.delete(request)
66
+ else
67
+ raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
68
+ end
69
+ end
70
+
71
+ info "--> %d (%d %.4fs)" % [result.code, result.body ? result.body.length : 0, realtime], tag
72
+ debug result.body
73
+ result
74
+ end
75
+ ensure
76
+ info "connection_request_total_time=%.4fs" % [Time.now.to_f - request_start], tag
77
+ end
78
+
79
+ private
80
+ def configure_proxy(http)
81
+ http.proxy = "#{proxy_address}:#{proxy_port}" if proxy_address
82
+ end
83
+
84
+ def configure_timeouts(http)
85
+ http.open_timeout = open_timeout
86
+ http.read_timeout = read_timeout
87
+ end
88
+
89
+ def handle_response(response)
90
+ if @ignore_http_status then
91
+ return response.body
92
+ else
93
+ case response.code.to_i
94
+ when 200...300
95
+ response.body
96
+ else
97
+ raise ResponseError.new(response)
98
+ end
99
+ end
100
+ end
101
+
102
+ def debug(message, tag = nil)
103
+ log(:debug, message, tag)
104
+ end
105
+
106
+ def info(message, tag = nil)
107
+ log(:info, message, tag)
108
+ end
109
+
110
+ def error(message, tag = nil)
111
+ log(:error, message, tag)
112
+ end
113
+
114
+ def log(level, message, tag)
115
+ message = "[#{tag}] #{message}" if tag
116
+ logger.send(level, message) if logger
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,7 @@
1
+ module Bright
2
+ class Enrollment < Model
3
+ @attribute_names = [:entry_date, :entry_comment, :exit_date, :exit_comment, :grade]
4
+ attr_accessor *@attribute_names
5
+ attr_accessor :student, :school
6
+ end
7
+ end
@@ -0,0 +1,24 @@
1
+ module Bright
2
+ class ResponseError < StandardError
3
+ attr_reader :response
4
+
5
+ def initialize(response, message = nil)
6
+ @response = response
7
+ @message = message
8
+ end
9
+
10
+ def to_s
11
+ "Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
12
+ end
13
+ end
14
+
15
+ class UnknownAttributeError < NoMethodError
16
+ attr_reader :record, :attribute
17
+
18
+ def initialize(record, attribute)
19
+ @record = record
20
+ @attribute = attribute
21
+ super("unknown attribute '#{attribute}' for #{@record.class}.")
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,47 @@
1
+ module Bright
2
+ class Model
3
+ @attribute_names = []
4
+
5
+ def initialize(attributes={})
6
+ assign_attributes(attributes) if attributes
7
+
8
+ super()
9
+ end
10
+
11
+ def assign_attributes(new_attributes)
12
+ if !new_attributes.is_a?(Hash)
13
+ raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
14
+ end
15
+ return if new_attributes.empty?
16
+
17
+ attributes = Hash[new_attributes.collect{|k,v| [k.to_sym, v]}]
18
+ _assign_attributes(attributes)
19
+ end
20
+
21
+ def self.attribute_names
22
+ @attribute_names
23
+ end
24
+
25
+ def to_json
26
+ Hash[self.class.attribute_names.collect do |n|
27
+ [n, self.send(n)]
28
+ end].to_json
29
+ end
30
+
31
+ private
32
+
33
+ def _assign_attributes(attributes)
34
+ attributes.each do |k, v|
35
+ _assign_attribute(k, v)
36
+ end
37
+ end
38
+
39
+ def _assign_attribute(k, v)
40
+ if respond_to?("#{k}=")
41
+ public_send("#{k}=", v)
42
+ else
43
+ raise UnknownAttributeError.new(self, k)
44
+ end
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,55 @@
1
+ class ResponseCollection
2
+ include Enumerable
3
+
4
+ attr_accessor :paged_objects
5
+ attr_accessor :total
6
+ attr_accessor :per_page
7
+ attr_accessor :load_more_call
8
+
9
+ # seed_page, total, per_page, load_more_call
10
+ def initialize(options = {})
11
+ @paged_objects = {0 => options[:seed_page]}
12
+ @total = options[:total].to_i
13
+ @per_page = options[:per_page].to_i
14
+ @pages = @per_page > 0 ? (@total.to_f / @per_page.to_f).ceil : 0
15
+ @load_more_call = options[:load_more_call]
16
+ end
17
+
18
+ def each
19
+ current_page = -1
20
+ while (current_page += 1) < @pages do
21
+ objects = [@paged_objects[current_page]].flatten.compact
22
+ next_page_no = current_page + 1
23
+ if load_more_call and @paged_objects[next_page_no].nil? and next_page_no < @pages
24
+ next_page_thread = Thread.new do
25
+ load_more_call.call(next_page_no)
26
+ end
27
+ else
28
+ next_page_thread = nil
29
+ end
30
+ objects.each do |obj|
31
+ yield obj
32
+ end
33
+ @paged_objects[next_page_no] = next_page_thread.value if next_page_thread
34
+ end
35
+ end
36
+
37
+ def last
38
+ last_page_no = @pages - 1
39
+ if load_more_call and (last_page = @paged_objects[last_page_no]).nil?
40
+ last_page = @paged_objects[last_page_no] = load_more_call.call(last_page_no)
41
+ end
42
+ last_page.last
43
+ end
44
+
45
+ def loaded_results
46
+ @paged_objects.values.flatten
47
+ end
48
+
49
+ alias size total
50
+ alias length total
51
+
52
+ def empty?
53
+ total <= 0
54
+ end
55
+ end
@@ -0,0 +1,17 @@
1
+ module Bright
2
+ class School < Model
3
+ @attribute_names = [:api_id, :name, :number]
4
+ attr_accessor *@attribute_names
5
+ attr_accessor :address
6
+
7
+ def address=(address)
8
+ if address.is_a?(Address)
9
+ @address = address
10
+ elsif address.is_a?(Hash)
11
+ @address = Address.new(address)
12
+ end
13
+ @address
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,145 @@
1
+ module Bright
2
+ module SisApi
3
+ class Aeries < Base
4
+ DATE_FORMAT = '%Y-%m-%dT%H:%M:%S'
5
+
6
+ @@description = "Connects to the Aeries API for accessing student information"
7
+ @@doc_url = "http://www.aeries.com/downloads/docs.1234/TechnicalSpecs/Aeries_API_Documentation.pdf"
8
+ @@api_version = ""
9
+
10
+ attr_accessor :connection_options
11
+
12
+ def initialize(options = {})
13
+ self.connection_options = options[:connection] || {}
14
+ # {
15
+ # :certficate => "",
16
+ # :uri => ""
17
+ # }
18
+ end
19
+
20
+ def get_student_by_api_id(api_id)
21
+ get_students({:api_id => api_id, :limit => 1}).first
22
+ end
23
+
24
+ def get_student(params)
25
+ get_students(params.merge(:limit => 1)).first
26
+ end
27
+
28
+ def get_students(params)
29
+ if params.has_key?(:school) or params.has_key?(:school_api_id)
30
+ school_api_id = params.delete(:school) || params.delete(:school_api_id)
31
+ students = get_students_by_school(school_api_id, params)
32
+ else
33
+ threads = []
34
+ get_schools.each do |school|
35
+ threads << Thread.new do
36
+ get_students_by_school(school, params)
37
+ end
38
+ end
39
+ students = threads.collect(&:value).flatten.compact
40
+ end
41
+ filter_students_by_params(students, params)
42
+ end
43
+
44
+ def get_students_by_school(school, params = {})
45
+ school_api_id = school.is_a?(School) ? school.api_id : school
46
+ if params.has_key?(:api_id)
47
+ path = "api/schools/#{school_api_id}/students/#{params[:api_id]}"
48
+ elsif params.has_key?(:sis_student_id)
49
+ path = "api/schools/#{school_api_id}/students/sn/#{params[:sis_student_id]}"
50
+ else
51
+ path = "api/schools/#{school_api_id}/students"
52
+ end
53
+ students_response_hash = self.request(:get, path, self.map_student_search_params(params))
54
+ students_response_hash.collect{|shsh| Student.new(convert_to_student_data(shsh))}
55
+ end
56
+
57
+ def create_student(student, additional_params = {})
58
+ raise NotImplementedError
59
+ end
60
+
61
+ def update_student(student, additional_params = {})
62
+ raise NotImplementedError
63
+ end
64
+
65
+ def get_schools(params = {})
66
+ schools_response_hash = self.request(:get, 'api/v2/schools', params)
67
+
68
+ schools_response_hash.collect{|h| School.new(convert_to_school_data(h))}
69
+ end
70
+
71
+ def request(method, path, params = {})
72
+ uri = "#{self.connection_options[:uri]}/#{path}"
73
+ body = nil
74
+ if method == :get
75
+ query = URI.encode_www_form(params)
76
+ uri += "?#{query}"
77
+ else
78
+ body = JSON.dump(params)
79
+ end
80
+
81
+ headers = self.headers_for_auth
82
+
83
+ connection = Bright::Connection.new(uri)
84
+ response = connection.request(method, body, headers)
85
+
86
+ if !response.error?
87
+ response_hash = JSON.parse(response.body)
88
+ else
89
+ puts "#{response.inspect}"
90
+ puts "#{response.body}"
91
+ end
92
+ response_hash
93
+ end
94
+
95
+ protected
96
+
97
+ def map_student_search_params(attrs)
98
+ attrs
99
+ end
100
+
101
+ def convert_to_student_data(attrs)
102
+ cattrs = {}
103
+
104
+ cattrs[:first_name] = attrs["FirstName"]
105
+ cattrs[:middle_name] = attrs["MiddleName"]
106
+ cattrs[:last_name] = attrs["LastName"]
107
+
108
+ cattrs[:api_id] = attrs["PermanentID"]
109
+ cattrs[:sis_student_id] = attrs["StudentNumber"]
110
+ cattrs[:state_student_id] = attrs["StateStudentID"]
111
+
112
+ cattrs[:gender] = attrs["Sex"]
113
+ if attrs["Birthdate"]
114
+ begin
115
+ cattrs[:birth_date] = Date.strptime(attrs["Birthdate"], DATE_FORMAT)
116
+ rescue => e
117
+ puts "#{e.inspect} #{bd}"
118
+ end
119
+ end
120
+
121
+ #SchoolCode
122
+
123
+ cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
124
+ end
125
+
126
+ def convert_to_school_data(attrs)
127
+ cattrs = {}
128
+
129
+ cattrs[:api_id] = attrs["SchoolCode"]
130
+ cattrs[:name] = attrs["Name"]
131
+ cattrs[:number] = attrs["SchoolCode"]
132
+
133
+ cattrs.reject{|k,v| v.respond_to?(:empty?) ? v.empty? : v.nil?}
134
+ end
135
+
136
+ def headers_for_auth
137
+ {
138
+ 'AERIES-CERT' => self.connection_options[:certificate],
139
+ 'Content-Type' => "application/json"
140
+ }
141
+ end
142
+
143
+ end
144
+ end
145
+ end