bright 0.1.0 → 1.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/LICENSE.txt +1 -1
- data/bright.gemspec +7 -5
- data/lib/bright/address.rb +6 -8
- data/lib/bright/connection.rb +52 -44
- data/lib/bright/contact.rb +53 -0
- data/lib/bright/cursor_response_collection.rb +45 -0
- data/lib/bright/email_address.rb +7 -0
- data/lib/bright/errors.rb +13 -3
- data/lib/bright/helpers/blank_helper.rb +55 -0
- data/lib/bright/helpers/boolean_parser_helper.rb +13 -0
- data/lib/bright/phone_number.rb +20 -0
- data/lib/bright/response_collection.rb +20 -19
- data/lib/bright/school.rb +13 -4
- data/lib/bright/sis_apis/aeries.rb +36 -35
- data/lib/bright/sis_apis/base.rb +31 -5
- data/lib/bright/sis_apis/bright_sis.rb +305 -0
- data/lib/bright/sis_apis/infinite_campus.rb +376 -18
- data/lib/bright/sis_apis/power_school.rb +112 -69
- data/lib/bright/sis_apis/skyward.rb +276 -0
- data/lib/bright/sis_apis/synergy.rb +2 -2
- data/lib/bright/sis_apis/tsis.rb +44 -42
- data/lib/bright/student.rb +57 -14
- data/lib/bright/version.rb +1 -1
- data/lib/bright.rb +28 -14
- metadata +50 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: dd99510122d136894a8e1ba1fdfb6bea0c424f355624ca2c68178143fcd7965d
|
4
|
+
data.tar.gz: 16b8599ad55c91cf1f925a76da5d49690c49e2315ca63b925b71d31e421de45c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e4499da391786116fef2ef77fbf283fd67f4c4f26f84277f2bf75825d9b2444712f7df8b7bb99ba3279f4c10612addbaaa47dda84ffda2f8e9b4240539d80939
|
7
|
+
data.tar.gz: b544283e756c7114aac543d7b7a3c4d6acf87de7b5c58593a96bcafa619628444a7eaa91b0256dada1e28519914d381fc022563dce10814b5e8d0dbccca74736
|
data/LICENSE.txt
CHANGED
data/bright.gemspec
CHANGED
@@ -7,9 +7,9 @@ Gem::Specification.new do |spec|
|
|
7
7
|
spec.name = "bright"
|
8
8
|
spec.version = Bright::VERSION
|
9
9
|
spec.authors = ["Arux Software"]
|
10
|
-
spec.email = ["
|
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
|
10
|
+
spec.email = ["hello@arux.software"]
|
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 Arux Software. 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
13
|
spec.homepage = ""
|
14
14
|
spec.license = "MIT"
|
15
15
|
|
@@ -20,7 +20,9 @@ Gem::Specification.new do |spec|
|
|
20
20
|
|
21
21
|
spec.add_runtime_dependency "httpi", "~> 2.1"
|
22
22
|
spec.add_runtime_dependency "json", ">= 0"
|
23
|
+
spec.add_runtime_dependency "oauth", ">= 0.5.4"
|
24
|
+
spec.add_runtime_dependency "parallel", "~> 1.2"
|
23
25
|
|
24
|
-
spec.add_development_dependency "bundler", "
|
25
|
-
spec.add_development_dependency "rake", "~>
|
26
|
+
spec.add_development_dependency "bundler", ">= 2.2.10"
|
27
|
+
spec.add_development_dependency "rake", "~> 12.3.3"
|
26
28
|
end
|
data/lib/bright/address.rb
CHANGED
@@ -1,17 +1,15 @@
|
|
1
1
|
module Bright
|
2
2
|
class Address < Model
|
3
|
-
@attribute_names = [:street, :apt, :city, :state, :postal_code, :
|
3
|
+
@attribute_names = [:street, :apt, :city, :state, :postal_code, :latitude, :longitude, :type]
|
4
4
|
attr_accessor *@attribute_names
|
5
|
-
|
6
|
-
alias lat
|
5
|
+
|
6
|
+
alias lat latitude
|
7
7
|
alias lng longitude
|
8
|
-
|
8
|
+
|
9
9
|
def geographical_coordinates
|
10
|
-
if self.
|
11
|
-
"#{self.
|
10
|
+
if self.latitude and self.longitude
|
11
|
+
"#{self.latitude},#{self.longitude}"
|
12
12
|
end
|
13
13
|
end
|
14
14
|
end
|
15
15
|
end
|
16
|
-
|
17
|
-
|
data/lib/bright/connection.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'uri'
|
2
2
|
require 'httpi'
|
3
3
|
require 'benchmark'
|
4
|
+
require 'securerandom'
|
4
5
|
|
5
6
|
module Bright
|
6
7
|
class Connection
|
@@ -30,72 +31,79 @@ module Bright
|
|
30
31
|
@ssl_version = nil
|
31
32
|
@proxy_address = nil
|
32
33
|
@proxy_port = nil
|
34
|
+
|
35
|
+
if Bright.devmode && !@logger
|
36
|
+
@logger = Logger.new(STDOUT)
|
37
|
+
@logger.level = Logger::INFO
|
38
|
+
end
|
39
|
+
|
33
40
|
end
|
34
41
|
|
35
42
|
def request(method, body, headers = {})
|
36
43
|
request_start = Time.now.to_f
|
44
|
+
info "connection_http_method=#{method.to_s.upcase} connection_uri=#{endpoint} headers=#{headers.inspect}", tag
|
45
|
+
|
46
|
+
result = nil
|
37
47
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
|
68
|
-
end
|
48
|
+
if !Bright.devmode
|
49
|
+
HTTPI.log = false
|
50
|
+
end
|
51
|
+
|
52
|
+
realtime = Benchmark.realtime do
|
53
|
+
request = HTTPI::Request.new(endpoint.to_s)
|
54
|
+
request.headers = headers
|
55
|
+
request.body = body if body
|
56
|
+
request.auth.ssl.verify_mode = :none if !@verify_peer
|
57
|
+
configure_proxy(request)
|
58
|
+
configure_timeouts(request)
|
59
|
+
|
60
|
+
result = case method
|
61
|
+
when :get
|
62
|
+
raise ArgumentError, "GET requests do not support a request body" if body
|
63
|
+
HTTPI.get(request)
|
64
|
+
when :post
|
65
|
+
debug(body) if Bright.devmode
|
66
|
+
HTTPI.post(request)
|
67
|
+
when :put
|
68
|
+
debug(body) if Bright.devmode
|
69
|
+
HTTPI.put(request)
|
70
|
+
when :patch
|
71
|
+
debug(body) if Bright.devmode
|
72
|
+
HTTPI.patch(request)
|
73
|
+
when :delete
|
74
|
+
HTTPI.delete(request)
|
75
|
+
else
|
76
|
+
raise ArgumentError, "Unsupported request method #{method.to_s.upcase}"
|
69
77
|
end
|
78
|
+
end
|
70
79
|
|
71
|
-
|
72
|
-
|
73
|
-
result
|
80
|
+
if Bright.devmode
|
81
|
+
info("--> %d (%d %.4fs)" % [result.code, result.body ? result.body.length : 0, realtime], tag)
|
82
|
+
debug(result.body)
|
74
83
|
end
|
84
|
+
|
85
|
+
handle_response(result)
|
86
|
+
|
75
87
|
ensure
|
76
88
|
info "connection_request_total_time=%.4fs" % [Time.now.to_f - request_start], tag
|
77
89
|
end
|
78
90
|
|
79
91
|
private
|
92
|
+
|
80
93
|
def configure_proxy(http)
|
81
94
|
http.proxy = "#{proxy_address}:#{proxy_port}" if proxy_address
|
82
95
|
end
|
83
|
-
|
96
|
+
|
84
97
|
def configure_timeouts(http)
|
85
98
|
http.open_timeout = open_timeout
|
86
99
|
http.read_timeout = read_timeout
|
87
100
|
end
|
88
101
|
|
89
102
|
def handle_response(response)
|
90
|
-
if @ignore_http_status
|
91
|
-
return response
|
103
|
+
if @ignore_http_status or !response.error?
|
104
|
+
return response
|
92
105
|
else
|
93
|
-
|
94
|
-
when 200...300
|
95
|
-
response.body
|
96
|
-
else
|
97
|
-
raise ResponseError.new(response)
|
98
|
-
end
|
106
|
+
raise ResponseError.new(response, endpoint.to_s)
|
99
107
|
end
|
100
108
|
end
|
101
109
|
|
@@ -116,4 +124,4 @@ module Bright
|
|
116
124
|
logger.send(level, message) if logger
|
117
125
|
end
|
118
126
|
end
|
119
|
-
end
|
127
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Bright
|
4
|
+
class Contact < Model
|
5
|
+
@attribute_names = [:client_id, :api_id, :first_name, :middle_name, :last_name, :nick_name,
|
6
|
+
:birth_date, :gender, :relationship_type,
|
7
|
+
:hispanic_ethnicity, :race, :image,
|
8
|
+
:sis_student_id, :state_student_id, :last_modified]
|
9
|
+
attr_accessor *@attribute_names
|
10
|
+
|
11
|
+
def self.attribute_names
|
12
|
+
@attribute_names
|
13
|
+
end
|
14
|
+
|
15
|
+
attr_accessor :phone_numbers, :addresses, :email_address
|
16
|
+
|
17
|
+
def phone_numbers=(array)
|
18
|
+
if array.size <= 0 or array.first.is_a?(PhoneNumber)
|
19
|
+
@phone_numbers = array
|
20
|
+
elsif array.first.is_a?(Hash)
|
21
|
+
@phone_numbers = array.collect{|a| PhoneNumber.new(a)}
|
22
|
+
end
|
23
|
+
@phone_numbers ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
def phone_numbers
|
27
|
+
@phone_numbers ||= []
|
28
|
+
end
|
29
|
+
|
30
|
+
def addresses=(array)
|
31
|
+
if array.size <= 0 or array.first.is_a?(Address)
|
32
|
+
@addresses = array
|
33
|
+
elsif array.first.is_a?(Hash)
|
34
|
+
@addresses = array.collect{|a| Address.new(a)}
|
35
|
+
end
|
36
|
+
@addresses ||= []
|
37
|
+
end
|
38
|
+
|
39
|
+
def addresses
|
40
|
+
@addresses ||= []
|
41
|
+
end
|
42
|
+
|
43
|
+
def email_address=(email)
|
44
|
+
if email.is_a?(EmailAddress)
|
45
|
+
@email_address = email
|
46
|
+
elsif email.is_a?(Hash)
|
47
|
+
@email_address = EmailAddress.new(email)
|
48
|
+
end
|
49
|
+
@email_address
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
class CursorResponseCollection < ResponseCollection
|
2
|
+
|
3
|
+
attr_accessor :collected_objects
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@collected_objects = [options[:seed_page]].flatten
|
7
|
+
@per_page = options[:per_page].to_i
|
8
|
+
@load_more_call = options[:load_more_call]
|
9
|
+
@next_cursor = options[:next_cursor]
|
10
|
+
end
|
11
|
+
|
12
|
+
def each
|
13
|
+
while (!@next_cursor.blank?) do
|
14
|
+
objects_hsh = load_more_call.call(@next_cursor)
|
15
|
+
objects = objects_hsh[:objects]
|
16
|
+
@next_cursor = objects_hsh[:next_cursor]
|
17
|
+
objects.each do |obj|
|
18
|
+
yield obj
|
19
|
+
end
|
20
|
+
@collected_objects += objects
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def last
|
25
|
+
self.to_a
|
26
|
+
return @collected_objects.last
|
27
|
+
end
|
28
|
+
|
29
|
+
def loaded_results
|
30
|
+
@collected_objects.flatten
|
31
|
+
end
|
32
|
+
|
33
|
+
def total
|
34
|
+
self.to_a
|
35
|
+
self.loaded_results.size
|
36
|
+
end
|
37
|
+
|
38
|
+
alias size total
|
39
|
+
alias length total
|
40
|
+
|
41
|
+
def empty?
|
42
|
+
self.to_a.empty?
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
data/lib/bright/errors.rb
CHANGED
@@ -1,15 +1,25 @@
|
|
1
1
|
module Bright
|
2
2
|
class ResponseError < StandardError
|
3
3
|
attr_reader :response
|
4
|
+
attr_reader :uri
|
4
5
|
|
5
|
-
def initialize(response,
|
6
|
+
def initialize(response, uri = nil)
|
6
7
|
@response = response
|
7
|
-
@
|
8
|
+
@uri = uri
|
8
9
|
end
|
9
10
|
|
10
11
|
def to_s
|
11
|
-
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}"
|
12
|
+
"Failed with #{response.code} #{response.message if response.respond_to?(:message)}".strip
|
12
13
|
end
|
14
|
+
|
15
|
+
def body
|
16
|
+
response.body
|
17
|
+
end
|
18
|
+
|
19
|
+
def server_error?
|
20
|
+
(500..599).include?(response&.code.to_i)
|
21
|
+
end
|
22
|
+
|
13
23
|
end
|
14
24
|
|
15
25
|
class UnknownAttributeError < NoMethodError
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
class Object
|
4
|
+
def blank?
|
5
|
+
respond_to?(:empty?) ? !!empty? : !self
|
6
|
+
end
|
7
|
+
|
8
|
+
def present?
|
9
|
+
!blank?
|
10
|
+
end
|
11
|
+
|
12
|
+
def presence
|
13
|
+
self if present?
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
class NilClass
|
18
|
+
def blank?
|
19
|
+
true
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class FalseClass
|
24
|
+
def blank?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class TrueClass
|
30
|
+
def blank?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Array
|
36
|
+
alias_method :blank?, :empty?
|
37
|
+
end
|
38
|
+
|
39
|
+
class Hash
|
40
|
+
alias_method :blank?, :empty?
|
41
|
+
end
|
42
|
+
|
43
|
+
class String
|
44
|
+
BLANK_RE = /\A[[:space:]]*\z/
|
45
|
+
|
46
|
+
def blank?
|
47
|
+
BLANK_RE === self
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class Numeric
|
52
|
+
def blank?
|
53
|
+
false
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Kernel
|
2
|
+
def Boolean(string)
|
3
|
+
return true if string == true || string =~ /^true$/i || string == "1" || string == 1 || string.to_s.downcase == "yes"
|
4
|
+
return false if string == false || string.nil? || string =~ /^false$/i || string == "0" || string == 0 || string.to_s.downcase == "no" || string.blank?
|
5
|
+
raise ArgumentError.new("invalid value for Boolean: \"#{string}\"")
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class Object
|
10
|
+
def to_bool
|
11
|
+
Boolean(self)
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module Bright
|
2
|
+
class PhoneNumber < Model
|
3
|
+
@attribute_names = [:phone_number, :extension, :type]
|
4
|
+
attr_accessor *@attribute_names
|
5
|
+
TYPES = ["Cell", "Home", "Work", "Other"]
|
6
|
+
|
7
|
+
def phone_number=(number)
|
8
|
+
number_a = number.to_s.split(/x|X/)
|
9
|
+
if number_a.size == 2
|
10
|
+
@extension = number_a.last.gsub(/[^0-9]/, "").strip
|
11
|
+
end
|
12
|
+
@phone_number = number_a.first.gsub(/[^0-9]/, "").strip
|
13
|
+
end
|
14
|
+
|
15
|
+
def extension=(number)
|
16
|
+
@extension = number.gsub(/[^0-9]/, "").strip
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -1,11 +1,15 @@
|
|
1
|
+
require 'parallel'
|
2
|
+
|
1
3
|
class ResponseCollection
|
2
4
|
include Enumerable
|
3
|
-
|
5
|
+
|
4
6
|
attr_accessor :paged_objects
|
5
7
|
attr_accessor :total
|
6
8
|
attr_accessor :per_page
|
7
9
|
attr_accessor :load_more_call
|
8
|
-
|
10
|
+
|
11
|
+
DEFAULT_NO_THREADS = 4
|
12
|
+
|
9
13
|
# seed_page, total, per_page, load_more_call
|
10
14
|
def initialize(options = {})
|
11
15
|
@paged_objects = {0 => options[:seed_page]}
|
@@ -13,27 +17,24 @@ class ResponseCollection
|
|
13
17
|
@per_page = options[:per_page].to_i
|
14
18
|
@pages = @per_page > 0 ? (@total.to_f / @per_page.to_f).ceil : 0
|
15
19
|
@load_more_call = options[:load_more_call]
|
20
|
+
@no_threads = options[:no_threads] || DEFAULT_NO_THREADS
|
16
21
|
end
|
17
|
-
|
22
|
+
|
18
23
|
def each
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
else
|
28
|
-
next_page_thread = nil
|
29
|
-
end
|
24
|
+
Parallel.each(0..@pages, in_threads: @no_threads) do |current_page|
|
25
|
+
objects = if @paged_objects[current_page].present?
|
26
|
+
@paged_objects[current_page]
|
27
|
+
else
|
28
|
+
load_more_call.call(current_page)
|
29
|
+
end
|
30
|
+
objects = [objects].flatten.compact
|
31
|
+
@paged_objects[current_page] = objects if objects.present?
|
30
32
|
objects.each do |obj|
|
31
33
|
yield obj
|
32
34
|
end
|
33
|
-
@paged_objects[next_page_no] = next_page_thread.value if next_page_thread
|
34
35
|
end
|
35
36
|
end
|
36
|
-
|
37
|
+
|
37
38
|
def last
|
38
39
|
last_page_no = @pages - 1
|
39
40
|
if load_more_call and (last_page = @paged_objects[last_page_no]).nil?
|
@@ -41,15 +42,15 @@ class ResponseCollection
|
|
41
42
|
end
|
42
43
|
last_page.last
|
43
44
|
end
|
44
|
-
|
45
|
+
|
45
46
|
def loaded_results
|
46
47
|
@paged_objects.values.flatten
|
47
48
|
end
|
48
|
-
|
49
|
+
|
49
50
|
alias size total
|
50
51
|
alias length total
|
51
52
|
|
52
53
|
def empty?
|
53
54
|
total <= 0
|
54
55
|
end
|
55
|
-
end
|
56
|
+
end
|
data/lib/bright/school.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
module Bright
|
2
2
|
class School < Model
|
3
|
-
@attribute_names = [:api_id, :name, :number]
|
3
|
+
@attribute_names = [:api_id, :name, :number, :state_id, :low_grade, :high_grade, :last_modified]
|
4
4
|
attr_accessor *@attribute_names
|
5
|
-
attr_accessor :address
|
6
|
-
|
5
|
+
attr_accessor :address, :phone_number
|
6
|
+
|
7
7
|
def address=(address)
|
8
8
|
if address.is_a?(Address)
|
9
9
|
@address = address
|
@@ -12,6 +12,15 @@ module Bright
|
|
12
12
|
end
|
13
13
|
@address
|
14
14
|
end
|
15
|
-
|
15
|
+
|
16
|
+
def phone_number=(phone_number)
|
17
|
+
if phone_number.is_a?(PhoneNumber)
|
18
|
+
@phone_number = phone_number
|
19
|
+
elsif phone_number.is_a?(Hash)
|
20
|
+
@phone_number = PhoneNumber.new(phone_number)
|
21
|
+
end
|
22
|
+
@phone_number
|
23
|
+
end
|
24
|
+
|
16
25
|
end
|
17
26
|
end
|