-
Notifications
You must be signed in to change notification settings - Fork 7
/
__init__.py
127 lines (96 loc) · 4.93 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
import requests
from datetime import datetime
from bs4 import BeautifulSoup
class Timetable:
def __init__(self):
self.url = 'https://banweb.banner.vt.edu/ssb/prod/HZSKVTSC.P_ProcRequest'
self.sleep_time = 1
self.base_request = { # base required request data
'BTN_PRESSED': 'FIND class sections',
'CAMPUS': '0', # blacksburg campus
'SCHDTYPE': '%'
}
self.data_keys = ['crn', 'code', 'name', 'lecture_type', 'credits', 'capacity',
'instructor', 'days', 'start_time', 'end_time', 'location', 'exam_type']
@property
def _default_term_year(self):
term_months = [1, 6, 7, 9] # Spring, Summer I, Summer II, Fall
current_year = datetime.today().year
current_month = datetime.today().month
term_month = max(key for key in term_months if key <= current_month)
return '%d%s' % (current_year, str(term_month).zfill(2))
def crn_lookup(self, crn_code, term_year=None, open_only=True):
section = self.refined_lookup(crn_code=crn_code, term_year=term_year, open_only=open_only)
return section[0] if section is not None else None
def class_lookup(self, subject_code, class_number, term_year=None, open_only=True):
return self.refined_lookup(subject_code=subject_code, class_number=class_number,
term_year=term_year, open_only=open_only)
def cle_lookup(self, cle_code, term_year=None, open_only=True):
return self.refined_lookup(cle_code=cle_code, term_year=term_year, open_only=open_only)
def subject_lookup(self, subject_code, term_year=None, open_only=True):
return self.refined_lookup(subject_code=subject_code, term_year=term_year, open_only=open_only)
def refined_lookup(self, crn_code=None, subject_code=None, class_number=None, cle_code=None,
term_year=None, open_only=True):
request_data = self.base_request.copy()
request_data['TERMYEAR'] = term_year if term_year is not None else self._default_term_year
if crn_code is not None:
if len(crn_code) < 3:
raise ValueError('Invalid CRN: must be longer than 3 characters')
request_data['crn'] = crn_code
if subject_code is not None:
request_data['subj_code'] = subject_code
if class_number is not None:
if len(class_number) != 4:
raise ValueError('Invalid Subject Number: must be 4 characters')
request_data['CRSE_NUMBER'] = class_number
if subject_code is None and class_number is not None:
raise ValueError('A subject code must be supplied with a class number')
request_data['CORE_CODE'] = 'AR%' if cle_code is None else cle_code
request_data['open_only'] = 'on' if open_only else ''
req = self._make_request(request_data)
sections = self._parse_table(req)
return None if sections is None or len(sections) == 0 else sections
def _parse_row(self, row):
entries = [entry.text.replace('\n', '').replace('-', ' ').strip() for entry in row.find_all('td')]
if len(entries) <= 1:
return None
return Section(**dict(zip(self.data_keys, entries)))
def _parse_table(self, html):
table = html.find('table', attrs={'class': 'dataentrytable'})
if table is None:
return None
rows = [row for row in table.find_all('tr') if row.attrs == {}]
sections = [self._parse_row(c) for c in rows if self._parse_row(c) is not None]
return None if not sections else sections
def _make_request(self, request_data):
r = requests.post(self.url, data=request_data)
if r.status_code != 200:
self.sleep_time *= 2
raise TimetableError('The VT Timetable is down or the request was bad. Status Code was: %d'
% r.status_code, self.sleep_time)
self.sleep_time = 1
return BeautifulSoup(r.content, 'html.parser')
class TimetableError(Exception):
def __init__(self, message, sleep_time):
super(TimetableError, self).__init__(message)
self.sleep_time = sleep_time
class Section:
def __init__(self, **kwargs):
self.__dict__.update(kwargs)
@staticmethod
def tuple_str(tup):
return str(tup).replace("'", "")
def __str__(self):
return '%s (%s) on %s at %s' % (getattr(self, 'name'), getattr(self, 'crn'),
getattr(self, 'days'),
Section.tuple_str((getattr(self, 'start_time'), getattr(self, 'end_time'))))
def __eq__(self, other):
if isinstance(other, Section):
return self.__dict__ == other.__dict__
return False
def __ne__(self, other):
return not self == other
def __repr__(self):
return str(self)
def __hash__(self):
return int(getattr(self, 'crn'))