CourseComparator 0.1.0__tar.gz → 0.1.2__tar.gz

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.
@@ -19,7 +19,7 @@ from .cc_classes import (
19
19
  CourseSetDelta,
20
20
  )
21
21
 
22
- from .cc_functions import init, EMPTY_SEMESTER
22
+ from .cc_functions import init, init_internet, EMPTY_SEMESTER
23
23
 
24
24
  __all__ = [
25
25
  "Course",
@@ -27,6 +27,7 @@ __all__ = [
27
27
  "CourseSet",
28
28
  "CourseSetDelta",
29
29
  "init",
30
+ "init_internet",
30
31
  "EMPTY_SEMESTER",
31
32
  ]
32
33
 
@@ -0,0 +1,310 @@
1
+ from typing import Callable
2
+ from .cc_classes import Course, CourseSet
3
+ import csv
4
+ import pickle
5
+ from pathlib import Path
6
+ import requests
7
+ import io
8
+ import time
9
+ from datetime import datetime, timedelta
10
+
11
+ # 全局唯一的0学期对象,表示刚入学的状态
12
+ EMPTY_SEMESTER = CourseSet()
13
+
14
+
15
+ def init(data_dir: str) -> Callable[[str, str, int], CourseSet]:
16
+ """
17
+ 创建课程数据加载器
18
+
19
+ Args:
20
+ data_dir: 数据目录的路径
21
+
22
+ Returns:
23
+ Callable[[str, str, int], CourseSet]: 加载器函数,接受专业名称、年份和学期作为参数,返回课程集合
24
+ """
25
+ # 确保数据目录存在
26
+ data_path = Path(data_dir)
27
+ if not data_path.exists():
28
+ raise FileNotFoundError(f"数据目录不存在: {data_dir}")
29
+
30
+ # 创建缓存目录
31
+ cache_dir = data_path / "__cc_cache__"
32
+ cache_dir.mkdir(exist_ok=True)
33
+
34
+ def loader(major: str, year: str, semester: int) -> CourseSet:
35
+ """
36
+ 加载指定专业、年份和学期的课程数据
37
+
38
+ Args:
39
+ major: 专业名称
40
+ year: 年份
41
+ semester: 学期数(0-8,0表示刚入学的状态)
42
+
43
+ Returns:
44
+ CourseSet: 指定学期的课程集合
45
+
46
+ Raises:
47
+ FileNotFoundError: 如果专业目录不存在
48
+ ValueError: 如果学期数不在有效范围内
49
+ """
50
+ # 处理 0 学期(刚入学的状态)
51
+ if semester == 0:
52
+ return EMPTY_SEMESTER
53
+
54
+ # 验证学期数
55
+ if semester < 0 or semester > 8:
56
+ raise ValueError(f"学期数必须在0-8之间,当前值: {semester}")
57
+
58
+ # 构建数据目录路径
59
+ major_dir = data_path / major / year
60
+ if not major_dir.exists():
61
+ raise FileNotFoundError(f"专业目录不存在: {major_dir}")
62
+
63
+ # 构建缓存文件路径
64
+ cache_file = cache_dir / f"{major}_{year}.pkl"
65
+
66
+ # 检查缓存是否存在且是最新的
67
+ if cache_file.exists():
68
+ # 获取缓存文件的修改时间
69
+ cache_mtime = cache_file.stat().st_mtime
70
+
71
+ # 检查数据文件是否有更新
72
+ data_files_updated = False
73
+ for i in range(1, 9):
74
+ csv_file = major_dir / f"{i}.csv"
75
+ if csv_file.exists() and csv_file.stat().st_mtime > cache_mtime:
76
+ data_files_updated = True
77
+ break
78
+
79
+ # 如果数据文件没有更新,直接使用缓存
80
+ if not data_files_updated:
81
+ with open(cache_file, "rb") as f:
82
+ course_sets = pickle.load(f)
83
+
84
+ # 如果学期数大于可用的学期数,返回最后一个学期的课程集合
85
+ if semester > len(course_sets):
86
+ return course_sets[-1]
87
+
88
+ # 计算前semester个学期的课程集合之和
89
+ result = CourseSet()
90
+ for i in range(semester):
91
+ result = result + course_sets[i]
92
+
93
+ return result
94
+
95
+ # 如果没有缓存或缓存已过期,重新加载数据
96
+ course_sets = []
97
+ for i in range(1, 9):
98
+ csv_file = major_dir / f"{i}.csv"
99
+ if not csv_file.exists():
100
+ # 如果某个学期的文件不存在,添加一个空的课程集合
101
+ course_sets.append(CourseSet())
102
+ continue
103
+
104
+ courses = []
105
+ with open(csv_file, "r", encoding="GBK") as f:
106
+ reader = csv.DictReader(f)
107
+ for row in reader:
108
+ course = Course(
109
+ course_code=row["course_code"],
110
+ course_name=row["course_name"],
111
+ credit=float(row["credit"]),
112
+ required=bool(int(row["required"])),
113
+ )
114
+ courses.append(course)
115
+
116
+ course_sets.append(CourseSet(courses))
117
+
118
+ # 保存缓存
119
+ with open(cache_file, "wb") as f:
120
+ pickle.dump(course_sets, f)
121
+
122
+ # 如果学期数大于可用的学期数,返回最后一个学期的课程集合
123
+ if semester > len(course_sets):
124
+ return course_sets[-1]
125
+
126
+ # 计算前semester个学期的课程集合之和
127
+ result = CourseSet()
128
+ for i in range(semester):
129
+ result = result + course_sets[i]
130
+
131
+ return result
132
+
133
+ return loader
134
+
135
+
136
+ def init_internet(
137
+ base_url: str, token: str, cache_dir: str
138
+ ) -> Callable[[str, str, int], CourseSet]:
139
+ """
140
+ 创建联网版本的数据加载器
141
+
142
+ Args:
143
+ base_url: 联网 url,类似于 data_dir
144
+ token: 认证令牌
145
+ cache_dir: 缓存目录
146
+
147
+ Returns:
148
+ Callable[[str, str, int], CourseSet]: 加载器函数,接受专业名称、年份和学期作为参数,返回课程集合
149
+ """
150
+ # 确保缓存目录存在
151
+ cache_path = Path(cache_dir)
152
+ if not cache_path.exists():
153
+ cache_path.mkdir(parents=True, exist_ok=True)
154
+
155
+ def loader(major: str, year: str, semester: int) -> CourseSet:
156
+ """
157
+ 加载指定专业、年份和学期的课程数据
158
+
159
+ Args:
160
+ major: 专业名称
161
+ year: 年份
162
+ semester: 学期数(0-8,0表示刚入学的状态)
163
+
164
+ Returns:
165
+ CourseSet: 指定学期的课程集合
166
+
167
+ Raises:
168
+ FileNotFoundError: 如果专业目录不存在
169
+ ValueError: 如果学期数不在有效范围内
170
+ """
171
+ # 处理 0 学期(刚入学的状态)
172
+ if semester == 0:
173
+ return EMPTY_SEMESTER
174
+
175
+ # 验证学期数
176
+ if semester < 0 or semester > 8:
177
+ raise ValueError(f"学期数必须在0-8之间,当前值: {semester}")
178
+
179
+ # 构建缓存文件路径
180
+ cache_file = cache_path / f"{major}_{year}.pkl"
181
+
182
+ # 检查缓存是否存在且是最新的
183
+ if cache_file.exists():
184
+ print(f"正在检查缓存文件: {cache_file}")
185
+ # 获取缓存文件的修改时间
186
+ cache_mtime = cache_file.stat().st_mtime
187
+
188
+ # 检查数据文件是否有更新
189
+ data_files_updated = False
190
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
191
+
192
+ print("正在检查网络文件是否有更新...")
193
+ for i in range(1, 9):
194
+ # 构建网络文件URL
195
+ url = f"{base_url}/{major}/{year}/{i}.csv"
196
+
197
+ try:
198
+ # 发送HEAD请求获取文件的Last-Modified头
199
+ print(f" 检查第{i}学期文件: {url}", end="\r")
200
+ response = requests.head(url, headers=headers)
201
+
202
+ # 如果文件存在且返回了Last-Modified头
203
+ if (
204
+ response.status_code == 200
205
+ and "Last-Modified" in response.headers
206
+ ):
207
+ # 解析Last-Modified头为时间戳
208
+ last_modified_str = response.headers["Last-Modified"]
209
+ # 解析GMT时间
210
+ gmt_time = datetime.strptime(
211
+ last_modified_str, "%a, %d %b %Y %H:%M:%S GMT"
212
+ )
213
+ # 转换为本地时间(东八区)
214
+ local_time = gmt_time.replace(tzinfo=None) + timedelta(hours=8)
215
+ # 转换为时间戳
216
+ last_modified_time = time.mktime(local_time.timetuple())
217
+
218
+ # 如果网络文件的修改时间比缓存文件新,则标记为需要更新
219
+ if last_modified_time > cache_mtime:
220
+ data_files_updated = True
221
+ break
222
+ except requests.exceptions.RequestException as e:
223
+ # 如果请求失败,假设文件可能已更新
224
+ print(f" 检查第{i}学期文件时出错: {e},假设文件可能已更新")
225
+ data_files_updated = True
226
+ break
227
+
228
+ # 如果数据文件没有更新,直接使用缓存
229
+ if not data_files_updated:
230
+ print("使用缓存数据...")
231
+ with open(cache_file, "rb") as f:
232
+ course_sets = pickle.load(f)
233
+
234
+ # 如果学期数大于可用的学期数,返回最后一个学期的课程集合
235
+ if semester > len(course_sets):
236
+ return course_sets[-1]
237
+
238
+ # 计算前semester个学期的课程集合之和
239
+ result = CourseSet()
240
+ for i in range(semester):
241
+ result = result + course_sets[i]
242
+
243
+ return result
244
+
245
+ # 如果没有缓存或缓存已过期,重新加载数据
246
+ print("正在从网络加载课程数据...")
247
+ course_sets = []
248
+ for i in range(1, 9):
249
+ # 构建网络文件URL
250
+ url = f"{base_url}/{major}/{year}/{i}.csv"
251
+
252
+ try:
253
+ # 发送HTTP请求获取CSV文件
254
+ print(f" 加载第{i}学期数据: {url}")
255
+ headers = {"Authorization": f"Bearer {token}"} if token else {}
256
+ response = requests.get(url, headers=headers)
257
+
258
+ # 检查响应状态
259
+ if response.status_code == 404:
260
+ # 如果文件不存在,添加一个空的课程集合
261
+ print(f" 第{i}学期文件不存在,使用空课程集合")
262
+ course_sets.append(CourseSet())
263
+ continue
264
+
265
+ response.raise_for_status() # 如果状态码不是200,抛出异常
266
+
267
+ # 从响应内容中读取CSV数据
268
+ csv_content = io.StringIO(response.text)
269
+ courses = []
270
+ reader = csv.DictReader(csv_content)
271
+ for row in reader:
272
+ course = Course(
273
+ course_code=row["course_code"],
274
+ course_name=row["course_name"],
275
+ credit=float(row["credit"]),
276
+ required=bool(int(row["required"])),
277
+ )
278
+ courses.append(course)
279
+
280
+ print(f" 成功加载第{i}学期数据,共{len(courses)}门课程")
281
+ course_sets.append(CourseSet(courses))
282
+
283
+ except requests.exceptions.RequestException as e:
284
+ # 如果请求失败,添加一个空的课程集合
285
+ print(f" 加载第{i}学期数据时出错: {e},使用空课程集合")
286
+ course_sets.append(CourseSet())
287
+
288
+ # 保存缓存
289
+ print("正在保存缓存数据...")
290
+ with open(cache_file, "wb") as f:
291
+ pickle.dump(course_sets, f)
292
+ print("缓存数据保存完成")
293
+
294
+ # 如果学期数大于可用的学期数,返回最后一个学期的课程集合
295
+ if semester > len(course_sets):
296
+ print(
297
+ f"请求的学期数({semester})大于可用的学期数({len(course_sets)}),返回最后一个学期的课程集合"
298
+ )
299
+ return course_sets[-1]
300
+
301
+ # 计算前semester个学期的课程集合之和
302
+ print(f"计算前{semester}个学期的课程集合之和...")
303
+ result = CourseSet()
304
+ for i in range(semester):
305
+ result = result + course_sets[i]
306
+
307
+ print(f"成功加载{len(result.courses)}门课程")
308
+ return result
309
+
310
+ return loader
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CourseComparator
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: 用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
5
5
  Author-email: Vincy SHI <vincy@vincy1230.net>
6
6
  License-Expression: MIT
@@ -23,9 +23,9 @@ Dynamic: license-file
23
23
 
24
24
  例如,人工智能专业 2021 级第 1 ~ 8 学期的课程数据位于,分别存于:
25
25
 
26
- `<数据集根目录>` / `人工智能` / `2021` / `1.csv`
27
- `<数据集根目录>` / `人工智能` / `2021` / `2.csv`
28
- ...
26
+ `<数据集根目录>` / `人工智能` / `2021` / `1.csv`
27
+ `<数据集根目录>` / `人工智能` / `2021` / `2.csv`
28
+ ...
29
29
  `<数据集根目录>` / `人工智能` / `2021` / `8.csv`
30
30
 
31
31
  csv 文件的格式如下:
@@ -58,6 +58,13 @@ import CourseComparator as cc
58
58
  # 传入数据集根目录,初始化数据加载器
59
59
  loader = cc.init("<数据集根目录>")
60
60
 
61
+ # 或:传入网络接口的 base_url 和 token,初始化为网络数据加载器
62
+ # loader = cc.init_internet(
63
+ # "<base_url>",
64
+ # "<your_token_here>",
65
+ # "./__cc_cache__",
66
+ # )
67
+
61
68
  # 获取旧的课程方案
62
69
  old_courses = loader("<专业>", "<届别>", <学期>)
63
70
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: CourseComparator
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: 用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。
5
5
  Author-email: Vincy SHI <vincy@vincy1230.net>
6
6
  License-Expression: MIT
@@ -23,9 +23,9 @@ Dynamic: license-file
23
23
 
24
24
  例如,人工智能专业 2021 级第 1 ~ 8 学期的课程数据位于,分别存于:
25
25
 
26
- `<数据集根目录>` / `人工智能` / `2021` / `1.csv`
27
- `<数据集根目录>` / `人工智能` / `2021` / `2.csv`
28
- ...
26
+ `<数据集根目录>` / `人工智能` / `2021` / `1.csv`
27
+ `<数据集根目录>` / `人工智能` / `2021` / `2.csv`
28
+ ...
29
29
  `<数据集根目录>` / `人工智能` / `2021` / `8.csv`
30
30
 
31
31
  csv 文件的格式如下:
@@ -58,6 +58,13 @@ import CourseComparator as cc
58
58
  # 传入数据集根目录,初始化数据加载器
59
59
  loader = cc.init("<数据集根目录>")
60
60
 
61
+ # 或:传入网络接口的 base_url 和 token,初始化为网络数据加载器
62
+ # loader = cc.init_internet(
63
+ # "<base_url>",
64
+ # "<your_token_here>",
65
+ # "./__cc_cache__",
66
+ # )
67
+
61
68
  # 获取旧的课程方案
62
69
  old_courses = loader("<专业>", "<届别>", <学期>)
63
70
 
@@ -12,9 +12,9 @@
12
12
 
13
13
  例如,人工智能专业 2021 级第 1 ~ 8 学期的课程数据位于,分别存于:
14
14
 
15
- `<数据集根目录>` / `人工智能` / `2021` / `1.csv`
16
- `<数据集根目录>` / `人工智能` / `2021` / `2.csv`
17
- ...
15
+ `<数据集根目录>` / `人工智能` / `2021` / `1.csv`
16
+ `<数据集根目录>` / `人工智能` / `2021` / `2.csv`
17
+ ...
18
18
  `<数据集根目录>` / `人工智能` / `2021` / `8.csv`
19
19
 
20
20
  csv 文件的格式如下:
@@ -47,6 +47,13 @@ import CourseComparator as cc
47
47
  # 传入数据集根目录,初始化数据加载器
48
48
  loader = cc.init("<数据集根目录>")
49
49
 
50
+ # 或:传入网络接口的 base_url 和 token,初始化为网络数据加载器
51
+ # loader = cc.init_internet(
52
+ # "<base_url>",
53
+ # "<your_token_here>",
54
+ # "./__cc_cache__",
55
+ # )
56
+
50
57
  # 获取旧的课程方案
51
58
  old_courses = loader("<专业>", "<届别>", <学期>)
52
59
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "CourseComparator"
7
- version = "0.1.0"
7
+ version = "0.1.2"
8
8
  description = "用于比较不同的专业、届别或年级之间的课程设置,供转专业、降级学生确认可冲抵及需补修的课程。"
9
9
  authors = [{ name = "Vincy SHI", email = "vincy@vincy1230.net" }]
10
10
  readme = "README.md"
@@ -1,130 +0,0 @@
1
- from typing import Callable
2
- from .cc_classes import Course, CourseSet
3
- import csv
4
- import pickle
5
- from pathlib import Path
6
-
7
-
8
- # 全局唯一的0学期对象,表示刚入学的状态
9
- EMPTY_SEMESTER = CourseSet()
10
-
11
-
12
- def init(data_dir: str) -> Callable[[str, str, int], CourseSet]:
13
- """
14
- 创建课程数据加载器
15
-
16
- Args:
17
- data_dir: 数据目录的路径
18
-
19
- Returns:
20
- Callable[[str, str, int], CourseSet]: 加载器函数,接受专业名称、年份和学期作为参数,返回课程集合
21
- """
22
- # 确保数据目录存在
23
- data_path = Path(data_dir)
24
- if not data_path.exists():
25
- raise FileNotFoundError(f"数据目录不存在: {data_dir}")
26
-
27
- # 创建缓存目录
28
- cache_dir = data_path / "__cc_cache__"
29
- cache_dir.mkdir(exist_ok=True)
30
-
31
- def loader(major: str, year: str, semester: int) -> CourseSet:
32
- """
33
- 加载指定专业、年份和学期的课程数据
34
-
35
- Args:
36
- major: 专业名称
37
- year: 年份
38
- semester: 学期数(0-8,0表示刚入学的状态)
39
-
40
- Returns:
41
- CourseSet: 指定学期的课程集合
42
-
43
- Raises:
44
- FileNotFoundError: 如果专业目录不存在
45
- ValueError: 如果学期数不在有效范围内
46
- """
47
- # 处理 0 学期(刚入学的状态)
48
- if semester == 0:
49
- return EMPTY_SEMESTER
50
-
51
- # 验证学期数
52
- if semester < 0 or semester > 8:
53
- raise ValueError(f"学期数必须在0-8之间,当前值: {semester}")
54
-
55
- # 构建数据目录路径
56
- major_dir = data_path / major / year
57
- if not major_dir.exists():
58
- raise FileNotFoundError(f"专业目录不存在: {major_dir}")
59
-
60
- # 构建缓存文件路径
61
- cache_file = cache_dir / f"{major}_{year}.pkl"
62
-
63
- # 检查缓存是否存在且是最新的
64
- if cache_file.exists():
65
- # 获取缓存文件的修改时间
66
- cache_mtime = cache_file.stat().st_mtime
67
-
68
- # 检查数据文件是否有更新
69
- data_files_updated = False
70
- for i in range(1, 9):
71
- csv_file = major_dir / f"{i}.csv"
72
- if csv_file.exists() and csv_file.stat().st_mtime > cache_mtime:
73
- data_files_updated = True
74
- break
75
-
76
- # 如果数据文件没有更新,直接使用缓存
77
- if not data_files_updated:
78
- with open(cache_file, "rb") as f:
79
- course_sets = pickle.load(f)
80
-
81
- # 如果学期数大于可用的学期数,返回最后一个学期的课程集合
82
- if semester > len(course_sets):
83
- return course_sets[-1]
84
-
85
- # 计算前semester个学期的课程集合之和
86
- result = CourseSet()
87
- for i in range(semester):
88
- result = result + course_sets[i]
89
-
90
- return result
91
-
92
- # 如果没有缓存或缓存已过期,重新加载数据
93
- course_sets = []
94
- for i in range(1, 9):
95
- csv_file = major_dir / f"{i}.csv"
96
- if not csv_file.exists():
97
- # 如果某个学期的文件不存在,添加一个空的课程集合
98
- course_sets.append(CourseSet())
99
- continue
100
-
101
- courses = []
102
- with open(csv_file, "r", encoding="GBK") as f:
103
- reader = csv.DictReader(f)
104
- for row in reader:
105
- course = Course(
106
- course_code=row["course_code"],
107
- course_name=row["course_name"],
108
- credit=float(row["credit"]),
109
- required=bool(int(row["required"])),
110
- )
111
- courses.append(course)
112
-
113
- course_sets.append(CourseSet(courses))
114
-
115
- # 保存缓存
116
- with open(cache_file, "wb") as f:
117
- pickle.dump(course_sets, f)
118
-
119
- # 如果学期数大于可用的学期数,返回最后一个学期的课程集合
120
- if semester > len(course_sets):
121
- return course_sets[-1]
122
-
123
- # 计算前semester个学期的课程集合之和
124
- result = CourseSet()
125
- for i in range(semester):
126
- result = result + course_sets[i]
127
-
128
- return result
129
-
130
- return loader