kosapi_client 0.5.0

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.
Files changed (83) hide show
  1. checksums.yaml +7 -0
  2. data/bin/console +15 -0
  3. data/bin/setup +23 -0
  4. data/lib/kosapi_client.rb +22 -0
  5. data/lib/kosapi_client/api_client.rb +43 -0
  6. data/lib/kosapi_client/configuration.rb +23 -0
  7. data/lib/kosapi_client/entity.rb +19 -0
  8. data/lib/kosapi_client/entity/author.rb +17 -0
  9. data/lib/kosapi_client/entity/base_entity.rb +16 -0
  10. data/lib/kosapi_client/entity/base_person.rb +20 -0
  11. data/lib/kosapi_client/entity/boolean.rb +14 -0
  12. data/lib/kosapi_client/entity/course.rb +34 -0
  13. data/lib/kosapi_client/entity/course_event.rb +20 -0
  14. data/lib/kosapi_client/entity/data_mappings.rb +122 -0
  15. data/lib/kosapi_client/entity/enum.rb +11 -0
  16. data/lib/kosapi_client/entity/exam.rb +25 -0
  17. data/lib/kosapi_client/entity/id.rb +13 -0
  18. data/lib/kosapi_client/entity/link.rb +54 -0
  19. data/lib/kosapi_client/entity/ml_string.rb +42 -0
  20. data/lib/kosapi_client/entity/parallel.rb +20 -0
  21. data/lib/kosapi_client/entity/person.rb +10 -0
  22. data/lib/kosapi_client/entity/result_page.rb +46 -0
  23. data/lib/kosapi_client/entity/student.rb +25 -0
  24. data/lib/kosapi_client/entity/teacher.rb +14 -0
  25. data/lib/kosapi_client/entity/teacher_timetable_slot.rb +16 -0
  26. data/lib/kosapi_client/entity/timetable_slot.rb +16 -0
  27. data/lib/kosapi_client/hash_utils.rb +17 -0
  28. data/lib/kosapi_client/http_client.rb +36 -0
  29. data/lib/kosapi_client/kosapi_client.rb +45 -0
  30. data/lib/kosapi_client/kosapi_response.rb +32 -0
  31. data/lib/kosapi_client/oauth2_http_adapter.rb +36 -0
  32. data/lib/kosapi_client/request_builder.rb +59 -0
  33. data/lib/kosapi_client/request_builder_delegator.rb +52 -0
  34. data/lib/kosapi_client/resource.rb +12 -0
  35. data/lib/kosapi_client/resource/course_events_builder.rb +13 -0
  36. data/lib/kosapi_client/resource/courses_builder.rb +12 -0
  37. data/lib/kosapi_client/resource/exams_builder.rb +13 -0
  38. data/lib/kosapi_client/resource/parallels_builder.rb +20 -0
  39. data/lib/kosapi_client/resource/teachers_builder.rb +16 -0
  40. data/lib/kosapi_client/resource_mapper.rb +20 -0
  41. data/lib/kosapi_client/response_converter.rb +65 -0
  42. data/lib/kosapi_client/response_links.rb +40 -0
  43. data/lib/kosapi_client/response_preprocessor.rb +48 -0
  44. data/lib/kosapi_client/url_builder.rb +24 -0
  45. data/lib/kosapi_client/version.rb +3 -0
  46. data/spec/integration/course_events_spec.rb +13 -0
  47. data/spec/integration/courses_spec.rb +28 -0
  48. data/spec/integration/exams_spec.rb +20 -0
  49. data/spec/integration/parallels_spec.rb +68 -0
  50. data/spec/kosapi_client/api_client_spec.rb +22 -0
  51. data/spec/kosapi_client/configuration_spec.rb +30 -0
  52. data/spec/kosapi_client/entity/base_entity_spec.rb +20 -0
  53. data/spec/kosapi_client/entity/base_person_spec.rb +19 -0
  54. data/spec/kosapi_client/entity/boolean_spec.rb +23 -0
  55. data/spec/kosapi_client/entity/course_event_spec.rb +30 -0
  56. data/spec/kosapi_client/entity/data_mappings_spec.rb +154 -0
  57. data/spec/kosapi_client/entity/enum_spec.rb +20 -0
  58. data/spec/kosapi_client/entity/id_spec.rb +13 -0
  59. data/spec/kosapi_client/entity/link_spec.rb +89 -0
  60. data/spec/kosapi_client/entity/ml_string_spec.rb +52 -0
  61. data/spec/kosapi_client/entity/parallel_spec.rb +18 -0
  62. data/spec/kosapi_client/entity/result_page_spec.rb +42 -0
  63. data/spec/kosapi_client/entity/teacher_timetable_slot_spec.rb +19 -0
  64. data/spec/kosapi_client/entity/timetable_slot_spec.rb +22 -0
  65. data/spec/kosapi_client/hash_utils_spec.rb +20 -0
  66. data/spec/kosapi_client/http_client_spec.rb +30 -0
  67. data/spec/kosapi_client/kosapi_client_spec.rb +57 -0
  68. data/spec/kosapi_client/kosapi_response_spec.rb +96 -0
  69. data/spec/kosapi_client/oauth2_http_adapter_spec.rb +25 -0
  70. data/spec/kosapi_client/request_builder_delegator_spec.rb +72 -0
  71. data/spec/kosapi_client/request_builder_spec.rb +80 -0
  72. data/spec/kosapi_client/resource/courses_builder_spec.rb +20 -0
  73. data/spec/kosapi_client/resource/parallels_builder_spec.rb +49 -0
  74. data/spec/kosapi_client/resource_mapper_spec.rb +33 -0
  75. data/spec/kosapi_client/response_converter_spec.rb +58 -0
  76. data/spec/kosapi_client/response_links_spec.rb +52 -0
  77. data/spec/kosapi_client/response_preprocessor_spec.rb +51 -0
  78. data/spec/kosapi_client/url_builder_spec.rb +44 -0
  79. data/spec/spec_helper.rb +40 -0
  80. data/spec/support/client_helpers.rb +11 -0
  81. data/spec/support/helpers.rb +7 -0
  82. data/spec/support/shared_examples_for_fluent_api.rb +7 -0
  83. metadata +401 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 46750e332cf19232187a8a9e5c66b9fa128e246a
4
+ data.tar.gz: 8d9a60a7399406d633ddb03bbadd51bc151b8372
5
+ SHA512:
6
+ metadata.gz: 424aeb28f513261f67bdb98783d22fcf6dbc536f80f652386c9d666b5a9d437e49c92951d4bce03c71dd9ddeb8c518a2f1c23e760eba260a6fce530f1a3c03d7
7
+ data.tar.gz: 8b55b7666492503cf2d382475b4dead2d3f4dc0a9e4aff95df143339e9c3e5613a582a6d77555c1c3d261406baa4880142e2d29ec68a410477902474335e23fe
@@ -0,0 +1,15 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "dotenv"
5
+ require "kosapi_client"
6
+
7
+ Dotenv.load
8
+
9
+ KOSapiClient.configure do |c|
10
+ c.client_id = ENV['KOSAPI_OAUTH_CLIENT_ID']
11
+ c.client_secret = ENV['KOSAPI_OAUTH_CLIENT_SECRET']
12
+ end
13
+
14
+ require "irb"
15
+ IRB.start
@@ -0,0 +1,23 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ echo "You will need OAuth credentials to access KOSapi and run integration tests."
6
+ echo "Register new project at https://auth.fit.cvut.cz/manager/ with readonly access to KOSapi
7
+ and enter client ID and secret for your default service account."
8
+
9
+ echo -n "Client ID (KOSAPI_OAUTH_CLIENT_ID): "
10
+ read client_id
11
+ echo -n "Client secret (KOSAPI_OAUTH_CLIENT_SECRET): "
12
+ read client_secret
13
+
14
+ script_path=$(cd `dirname "${BASH_SOURCE[0]}"` && pwd)
15
+ env_file=$(realpath "$script_path/../.env")
16
+ echo "Writing credentials to $env_file"
17
+ cat << EOF > "$env_file"
18
+ KOSAPI_OAUTH_CLIENT_ID=$client_id
19
+ KOSAPI_OAUTH_CLIENT_SECRET=$client_secret
20
+ EOF
21
+
22
+ echo "Running bundle install"
23
+ bundle install
@@ -0,0 +1,22 @@
1
+ require 'corefines'
2
+ require 'uri_template'
3
+ require 'escape_utils'
4
+
5
+ require 'kosapi_client/entity'
6
+ require 'kosapi_client/resource'
7
+
8
+ require 'kosapi_client/response_links'
9
+ require 'kosapi_client/configuration'
10
+ require 'kosapi_client/url_builder'
11
+ require 'kosapi_client/request_builder'
12
+ require 'kosapi_client/request_builder_delegator'
13
+ require 'kosapi_client/resource_mapper'
14
+ require 'kosapi_client/kosapi_client'
15
+ require 'kosapi_client/api_client'
16
+ require 'kosapi_client/hash_utils'
17
+ require 'kosapi_client/http_client'
18
+ require 'kosapi_client/kosapi_response'
19
+ require 'kosapi_client/response_preprocessor'
20
+ require 'kosapi_client/oauth2_http_adapter'
21
+ require 'kosapi_client/response_converter'
22
+ require 'kosapi_client/version'
@@ -0,0 +1,43 @@
1
+ using Corefines::String::camelcase
2
+
3
+ module KOSapiClient
4
+
5
+ class ApiClient
6
+ include ResourceMapper
7
+
8
+ # accessible resources definition
9
+ resource :courses
10
+ resource :course_events
11
+ resource :parallels
12
+ resource :exams
13
+ resource :teachers
14
+
15
+ attr_reader :http_client
16
+
17
+ ##
18
+ # Creates a new KOSapi client.
19
+ #
20
+ def initialize(config = Configuration.new)
21
+ http_adapter = OAuth2HttpAdapter.new(config.credentials, config.base_url)
22
+ @http_client = HTTPClient.new(http_adapter)
23
+ end
24
+
25
+ def create_builder(resource_name)
26
+ builder_name = "#{resource_name}_builder".camelcase(:upper).to_sym
27
+ builder_class = find_builder_class(builder_name)
28
+ builder_class.new(resource_name.to_s.camelcase(:lower), @http_client)
29
+ end
30
+
31
+ private
32
+ def find_builder_class(builder_name)
33
+ KOSapiClient::Resource.constants.each do |m|
34
+ constant = KOSapiClient::Resource.const_get(m)
35
+ if constant.is_a?(Class) && m == builder_name
36
+ return constant
37
+ end
38
+ end
39
+ RequestBuilder
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,23 @@
1
+ module KOSapiClient
2
+ class Configuration < Struct.new(:client_id, :client_secret, :base_url)
3
+
4
+ DEFAULT_OPTIONS = {
5
+ base_url: 'https://kosapi.fit.cvut.cz/api/3'
6
+ }
7
+
8
+ def initialize(options = {})
9
+ DEFAULT_OPTIONS.merge(options).each do |option, value|
10
+ self[option] = value
11
+ end
12
+ end
13
+
14
+ def credentials
15
+ if client_id && client_secret
16
+ {client_id: client_id, client_secret: client_secret}
17
+ else
18
+ {}
19
+ end
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ require 'kosapi_client/entity/boolean'
2
+ require 'kosapi_client/entity/link'
3
+ require 'kosapi_client/entity/id'
4
+ require 'kosapi_client/entity/author'
5
+ require 'kosapi_client/entity/enum'
6
+ require 'kosapi_client/entity/ml_string'
7
+ require 'kosapi_client/entity/data_mappings'
8
+ require 'kosapi_client/entity/base_entity'
9
+ require 'kosapi_client/entity/result_page'
10
+ require 'kosapi_client/entity/course_event'
11
+ require 'kosapi_client/entity/course'
12
+ require 'kosapi_client/entity/teacher_timetable_slot'
13
+ require 'kosapi_client/entity/timetable_slot'
14
+ require 'kosapi_client/entity/parallel'
15
+ require 'kosapi_client/entity/base_person'
16
+ require 'kosapi_client/entity/person'
17
+ require 'kosapi_client/entity/teacher'
18
+ require 'kosapi_client/entity/student'
19
+ require 'kosapi_client/entity/exam'
@@ -0,0 +1,17 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Author
4
+
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+
11
+ def self.parse(contents)
12
+ new(contents[:atom_name])
13
+ end
14
+ end
15
+ end
16
+ end
17
+
@@ -0,0 +1,16 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class BaseEntity
4
+
5
+ include DataMappings
6
+
7
+ map_data :id, String, namespace: :atom
8
+ map_data :title, String, namespace: :atom
9
+ map_data :updated, Time, namespace: :atom
10
+ map_data :link, Link, namespace: :atom
11
+ map_data :author, Author, namespace: :atom
12
+
13
+ end
14
+ end
15
+ end
16
+
@@ -0,0 +1,20 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class BasePerson < BaseEntity
4
+
5
+ map_data :first_name
6
+ map_data :last_name
7
+ map_data :personal_number, Integer
8
+ map_data :titles_post
9
+ map_data :titles_pre
10
+ map_data :username
11
+
12
+ def full_name
13
+ prefix = "#{titles_pre} " if titles_pre
14
+ suffix = " #{titles_post}" if titles_post
15
+ "#{prefix}#{first_name} #{last_name}#{suffix}"
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,14 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Boolean
4
+
5
+ def self.parse(str)
6
+ return true if str == 'true'
7
+ return false if str == 'false'
8
+ raise "Boolean parsing failed, invalid string: #{str}"
9
+ end
10
+
11
+ end
12
+ end
13
+ end
14
+
@@ -0,0 +1,34 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Course < BaseEntity
4
+
5
+ map_data :allowed_enrollment_count, Integer
6
+ map_data :approval_date, Time
7
+ map_data :classes_lang, Enum
8
+ map_data :classes_type, [Enum]
9
+ map_data :code
10
+ map_data :completion, Enum
11
+ map_data :credits, Integer
12
+ map_data :department, Link
13
+ map_data :description, MLString
14
+ map_data :homepage
15
+ map_data :keywords, MLString
16
+ map_data :lectures_contents, MLString
17
+ map_data :literature, MLString
18
+ map_data :name, MLString
19
+ map_data :note, MLString
20
+ map_data :objectives, MLString
21
+ map_data :programme_type, Enum
22
+ map_data :range
23
+ map_data :requirements, MLString
24
+ map_data :season, Enum
25
+ map_data :state, Enum
26
+ map_data :study_form, Enum
27
+ map_data :superior_course, Link
28
+ map_data :subcourses, Link
29
+ map_data :tutorials_contents, MLString
30
+ map_data :instance #todo
31
+
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,20 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class CourseEvent < BaseEntity
4
+
5
+ map_data :target_branch, [Link]
6
+ map_data :capacity, Integer
7
+ map_data :course, Link
8
+ map_data :creator, Link
9
+ map_data :end_date, Time
10
+ map_data :name, MLString
11
+ map_data :note, MLString
12
+ map_data :occupied, Integer
13
+ map_data :room, Link
14
+ map_data :semester, Link
15
+ map_data :signin_deadline, Time
16
+ map_data :start_date, Time
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,122 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ module DataMappings
4
+
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ def to_hash
10
+ result = {}
11
+ self.class.attr_mappings.each_key { |k| result[k] = convert_value(send(k)) }
12
+ result
13
+ end
14
+
15
+ private
16
+ def convert_value(val)
17
+ if val.respond_to? :to_hash
18
+ val.to_hash
19
+ elsif val.is_a?(Array)
20
+ val.map { |it| convert_value(it) }
21
+ else
22
+ val
23
+ end
24
+ end
25
+
26
+ module ClassMethods
27
+
28
+ def map_data(name, type=String, opts = {})
29
+ attr_accessor name
30
+ opts[:type] = type
31
+ @data_mappings ||= {}
32
+ @data_mappings[name] = opts
33
+ end
34
+
35
+ def attr_mappings
36
+ if self.superclass.respond_to? :attr_mappings
37
+ parent_mappings = self.superclass.attr_mappings
38
+ end
39
+ (parent_mappings || {}).merge(@data_mappings)
40
+ end
41
+
42
+ # Parses composed domain type from hash response structure.
43
+ #
44
+ # @param [Hash] content hash structure from API response corresponding to single domain object
45
+ # @return [BaseEntity] parsed domain object
46
+ def parse(content, context = {})
47
+ instance = new()
48
+ set_mapped_attributes(instance, content)
49
+ instance
50
+ end
51
+
52
+ # Creates new domain object instance and sets values
53
+ # of mapped domain object attributes from source hash.
54
+ # Attributes are mapped by .map_data method.
55
+ def set_mapped_attributes(instance, source_hash)
56
+ if self.superclass.respond_to? :set_mapped_attributes
57
+ self.superclass.set_mapped_attributes(instance, source_hash)
58
+ end
59
+ raise "Missing data mappings for entity #{self}" unless @data_mappings
60
+ @data_mappings.each do |name, options|
61
+ set_mapped_attribute(instance, name, source_hash, options)
62
+ end
63
+ end
64
+
65
+ private
66
+ def set_mapped_attribute(instance, name, source_hash, mapping_options)
67
+ namespace = mapping_options[:namespace]
68
+ src_element = mapping_options[:element] || name
69
+ if namespace
70
+ key = "#{namespace}_#{src_element}".to_sym
71
+ else
72
+ key = src_element
73
+ end
74
+ value = retrieve_value(source_hash, key, mapping_options)
75
+ if value.nil?
76
+ raise "Missing value for attribute #{name}" if mapping_options[:required]
77
+ if mapping_options[:type].is_a?(Array)
78
+ value = []
79
+ else
80
+ return
81
+ end
82
+ else
83
+ value = convert_type(value, mapping_options[:type])
84
+ end
85
+ instance.send("#{name}=".to_sym, value)
86
+ end
87
+
88
+ def convert_type(value, type)
89
+ return value.to_i if type == Integer
90
+ return value if type == String
91
+ return convert_array(value, type.first) if type.is_a?(Array)
92
+
93
+ return type.parse(value) if type.respond_to? :parse
94
+ raise "Unknown type #{type} to convert value #{value} to."
95
+ end
96
+
97
+ # Converts values of array type to proper domain objects.
98
+ # It checks whether the value is really an array, because
99
+ # when API returns a single value it does not get parsed
100
+ # into an array.
101
+ def convert_array(values, type)
102
+ if values.is_a?(Array)
103
+ values.map { |it| convert_type(it, type) }
104
+ else
105
+ [ convert_type(values, type) ]
106
+ end
107
+ end
108
+
109
+ def retrieve_value(source_hash, key, mapping_options)
110
+ path = mapping_options[:path]
111
+ if path
112
+ parent_element = source_hash[path]
113
+ else
114
+ parent_element = source_hash
115
+ end
116
+ parent_element[key] if parent_element
117
+ end
118
+
119
+ end
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,11 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Enum
4
+
5
+ def self.parse(contents)
6
+ contents.downcase.to_sym
7
+ end
8
+
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module KOSapiClient
2
+ module Entity
3
+ class Exam < BaseEntity
4
+
5
+ map_data :cancel_deadline, Time
6
+ map_data :capacity, Integer
7
+ map_data :course, Link
8
+ map_data :department, Link
9
+ map_data :end_date, Time
10
+ map_data :examiner, Link
11
+ map_data :examiners, [Link], element: :teacher, path: :examiners
12
+ map_data :note
13
+ map_data :occupied, Integer
14
+ map_data :resit, Boolean
15
+ map_data :room, Link
16
+ map_data :semester, Link
17
+ map_data :signin_deadline, Date
18
+ map_data :start_date, Time
19
+ map_data :substitutes, Enum
20
+ map_data :superior, Link
21
+ map_data :term_type, Enum
22
+
23
+ end
24
+ end
25
+ end