diff --git a/Domains/FullStack/MiniProjects/.DS_Store b/Domains/FullStack/MiniProjects/.DS_Store new file mode 100644 index 00000000..7280eb49 Binary files /dev/null and b/Domains/FullStack/MiniProjects/.DS_Store differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/.DS_Store b/Domains/FullStack/MiniProjects/QuizMaster/.DS_Store new file mode 100644 index 00000000..02f5ab6c Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/.DS_Store differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/Procfile b/Domains/FullStack/MiniProjects/QuizMaster/Procfile new file mode 100644 index 00000000..8001d1a5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/Procfile @@ -0,0 +1 @@ +web: gunicorn app:app \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/README.md b/Domains/FullStack/MiniProjects/QuizMaster/README.md new file mode 100644 index 00000000..7f2c0c3e --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/README.md @@ -0,0 +1,23 @@ +# Quiz Master application +**Contributor:** ekxnsh22005 + + +The following project is a quizmaster app that has an admin dashboard and user dashboard. + +The admin dashboard is hardcoded with the username and password as "admin" + +The admin can perform the following actions: +1. Add Subject +2. Add Chapter under Subject +3. Add Quiz with timer and MCQs and caqn set the date +4. Add Questions for the quiz +5. Search for users and see their history +6. See some graphical statistics about the users + +The user can perform the following actions: +1. Attempt quiz +2. View quiz history +3. Analyze the right and wrong options +4. See some graphical statistics + +The following website is built using HTML, CSS (Bootstrap), Flask and SQLite. \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/__pycache__/app.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/__pycache__/app.cpython-312.pyc new file mode 100644 index 00000000..e76f1a06 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/__pycache__/app.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/__pycache__/models.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/__pycache__/models.cpython-312.pyc new file mode 100644 index 00000000..aa58f820 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/__pycache__/models.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/app.py b/Domains/FullStack/MiniProjects/QuizMaster/app.py new file mode 100644 index 00000000..0c9dde3d --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/app.py @@ -0,0 +1,14 @@ +from flask import Flask, render_template +app = Flask(__name__) + +import controller.config +import models +import controller.api +import controller.routes as routes + + +print(app.url_map) + + +if __name__ == '__main__': + app.run(debug=True) \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/.DS_Store b/Domains/FullStack/MiniProjects/QuizMaster/controller/.DS_Store new file mode 100644 index 00000000..575bb2df Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/controller/.DS_Store differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/api.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/api.cpython-312.pyc new file mode 100644 index 00000000..14dda851 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/api.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/config.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/config.cpython-312.pyc new file mode 100644 index 00000000..c53d6309 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/config.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/routes.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/routes.cpython-312.pyc new file mode 100644 index 00000000..c7f6ed73 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/controller/__pycache__/routes.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/api.py b/Domains/FullStack/MiniProjects/QuizMaster/controller/api.py new file mode 100644 index 00000000..54182c52 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/controller/api.py @@ -0,0 +1,61 @@ +from flask_restful import Api, Resource +from app import app +from models import db, User, Subject, Chapter +from flask import request + +api = Api(app) + +class SubjectResource(Resource): + def get(self): + subjects = Subject.query.all() + return {'subjects': + [{ + 'id': subject.id, + 'name': subject.subject_name, + 'description': subject.subject_description + } for subject in subjects]} + + def post(self): + data = request.get_json() + subject_name = data.get('name') + subject_description = data.get('description') + + existing_subject = Subject.query.filter_by(subject_name=subject_name).first() + if existing_subject: + return {'message': 'Subject already exists.'}, 400 + + new_subject = Subject(subject_name=subject_name, subject_description=subject_description) + db.session.add(new_subject) + db.session.commit() + + return {'message': 'Subject created successfully!', 'id': new_subject.id}, 201 + + +class ChapterResource(Resource): + def get(self): + chapters = Chapter.query.all() + return {'chapters': + [{ + 'id': chapter.id, + 'name': chapter.chapter_name, + 'subject_id': chapter.subject_id + } for chapter in chapters]} + + def post(self): + data = request.get_json() + chapter_name = data.get('name') + subject_id = data.get('subject_id') + + existing_chapter = Chapter.query.filter_by(chapter_name=chapter_name, subject_id=subject_id).first() + if existing_chapter: + return {'message': 'Chapter already exists under this subject.'}, 400 + + new_chapter = Chapter(chapter_name=chapter_name, subject_id=subject_id) + db.session.add(new_chapter) + db.session.commit() + + return {'message': 'Chapter created successfully!', 'id': new_chapter.id}, 201 + + +api.add_resource(SubjectResource, '/api/subject') +api.add_resource(ChapterResource, '/api/chapter') \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/config.py b/Domains/FullStack/MiniProjects/QuizMaster/controller/config.py new file mode 100644 index 00000000..fb9380ea --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/controller/config.py @@ -0,0 +1,7 @@ +from dotenv import load_dotenv +import os +from app import app +load_dotenv() +app.config['SECRET_KEY'] = os.getenv('SECRET_KEY') +app.config['SQLALCHEMY_DATABASE_URI'] = os.getenv('SQLALCHEMY_DATABASE_URI') +app.config['SQLALCHEMY_DATABASE_MODIFICATIONS'] = os.getenv('SQLALCHEMY_DATABASE_MODIFICATIONS') \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/controller/routes.py b/Domains/FullStack/MiniProjects/QuizMaster/controller/routes.py new file mode 100644 index 00000000..6131a52e --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/controller/routes.py @@ -0,0 +1,923 @@ +from flask import render_template, request, flash, redirect, url_for, session, jsonify +from app import app +from models import db, User, Subject, Chapter, Quiz, Question, Scores +from werkzeug.security import generate_password_hash, check_password_hash +from functools import wraps +from datetime import datetime, timedelta +from sqlalchemy import Date, cast, func +import pytz +from datetime import datetime +ist = pytz.timezone("Asia/Kolkata") +ist_time = datetime.now(pytz.utc).astimezone(ist) + +def auth_required(func): + @wraps(func) + def inner(*args, **kwargs): + if 'user_id' not in session: + flash("Please login to continue") + return redirect(url_for('login')) + if session.get('is_admin'): + return redirect(url_for('admin')) + return func(*args, **kwargs) + inner.__name__ = func.__name__ + return inner + +def admin_required(func): + @wraps(func) + def inner(*args, **kwargs): + if 'user_id' not in session: + flash("Please login to continue") + return redirect(url_for('login')) + user = User.query.get(session['user_id']) + if not user.is_admin: + flash("You are not authorized to access this page") + return redirect(url_for('index')) + return func(*args, **kwargs) + inner.__name__ = func.__name__ + return inner + +@app.route('/') +@auth_required +def index(): + user = User.query.get(session['user_id']) + if user.is_admin: + return redirect(url_for('admin')) + + current_time = datetime.now() + attempted_quiz_ids = {score.quiz_id for score in Scores.query.filter_by(user_id=user.id).all()} + upcoming_quizzes = Quiz.query.filter(Quiz.date_of_quiz > current_time).all() + available_quizzes = Quiz.query.filter( + Quiz.date_of_quiz <= current_time, + Quiz.id.notin_(attempted_quiz_ids) + ).all() + attempted_quizzes = Quiz.query.filter(Quiz.id.in_(attempted_quiz_ids)).all() + + quiz_question_counts = {} + for quiz in available_quizzes + upcoming_quizzes + attempted_quizzes: + quiz_question_counts[quiz.id] = Question.query.filter_by(quiz_id=quiz.id).count() + + return render_template( + 'index.html', + available_quizzes=available_quizzes, + upcoming_quizzes=upcoming_quizzes, + attempted_quizzes=attempted_quizzes, + user=user, + quiz_question_counts=quiz_question_counts + ) + + +@app.route('/login') +def login(): + return render_template('login.html') + +@app.route('/login', methods=['POST']) +def login_user(): + username = request.form['username'] + password = request.form['password'] + if not username or not password: + flash("Please fill out all these fields") + return redirect(url_for('login')) + + user = User.query.filter_by(username=username).first() + if not user: + flash("Username does not exist") + return redirect(url_for('login')) + + if not check_password_hash(user.passhash, password): + flash("Invalid password") + return redirect(url_for('login')) + + session['user_id'] = user.id + session['is_admin'] = user.is_admin + if user.is_admin: + return redirect(url_for('admin')) + else: + return redirect(url_for('index')) + + +@app.route('/register') +def register(): + return render_template('register.html') + +@app.route('/register', methods=['POST']) +def register_user(): + name = request.form['name'] + username = request.form['username'] + password = request.form['password'] + confirm_password = request.form['confirm_password'] + if not username or not password or not confirm_password or not name: + flash("Please fill out all these fields") + return redirect(url_for('register')) + + if password != confirm_password: + flash("Passwords do not match") + return redirect(url_for('register')) + + user = User.query.filter_by(username=username).first() + if user: + flash("Username already exists") + return redirect(url_for('register')) + + password_hash = generate_password_hash(password) + + new_user = User(username=username, passhash=password_hash, name=name) + db.session.add(new_user) + db.session.commit() + return redirect(url_for('login')) + +@app.route('/profile') +@auth_required +def profile(): + user = User.query.get(session['user_id']) + return render_template('profile.html', user=user) + +@app.route('/profile', methods=['POST']) +@auth_required +def profile_post(): + name = request.form['name'] + username = request.form['username'] + cpassword = request.form['cpassword'] + password = request.form['password'] + + if not username or not cpassword or not password: + flash("Please fill out all the required fields") + return redirect(url_for('profile')) + + user = User.query.get(session['user_id']) + if not check_password_hash(user.passhash, cpassword): + flash("Current password is incorrect") + return redirect(url_for('profile')) + + if username != user.username: + new_username = User.query.filter_by(username=username).first() + if new_username: + flash("Username already exists") + return redirect(url_for('profile')) + + new_password_hash = generate_password_hash(password) + user.name = name + user.username = username + user.passhash = new_password_hash + db.session.commit() + flash("Profile updated successfully") + return redirect(url_for('profile')) + +@app.route('/admin/profile') +@admin_required +def admin_profile(): + user = User.query.get(session['user_id']) + return render_template('profile.html', user=user) + +@app.route('/admin/profile', methods=['POST']) +@admin_required +def admin_profile_post(): + name = request.form['name'] + username = request.form['username'] + cpassword = request.form['cpassword'] + password = request.form['password'] + + if not username or not cpassword or not password: + flash("Please fill out all the required fields") + return redirect(url_for('admin_profile')) + + user = User.query.get(session['user_id']) + if not check_password_hash(user.passhash, cpassword): + flash("Current password is incorrect") + return redirect(url_for('admin_profile')) + + if username != user.username: + new_username = User.query.filter_by(username=username).first() + if new_username: + flash("Username already exists") + return redirect(url_for('admin_profile')) + + new_password_hash = generate_password_hash(password) + user.name = name + user.username = username + user.passhash = new_password_hash + db.session.commit() + flash("Profile updated successfully") + return redirect(url_for('admin_profile')) + + +@app.route('/logout') +@auth_required +def logout(): + session.pop('user_id') + flash("Logged out successfully") + return redirect(url_for('login')) + +@app.route('/admin/logout') +@admin_required +def admin_logout(): + session.pop('user_id') + flash("Logged out successfully") + return redirect(url_for('login')) + + +@app.route('/admin') +@admin_required +def admin(): + subjects = Subject.query.all() + return render_template('admin.html', subjects=subjects) + +@app.route('/admin/subject/add') +@admin_required +def add_subject(): + return render_template('subject/add.html') + +@app.route('/admin/subject/add', methods=['POST']) +@admin_required +def add_subject_post(): + name = request.form['name'] + description = request.form['description'] + if not name: + flash("Please fill out the subject name") + return redirect(url_for('add_subject')) + subject = Subject.query.filter_by(subject_name=name).first() + if subject: + flash("Subject already exists") + return redirect(url_for('add_subject')) + new_subject = Subject(subject_name=name, subject_description=description) + db.session.add(new_subject) + db.session.commit() + flash("Subject added successfully") + return redirect(url_for('admin')) + +@app.route('/admin/chapter/add/') +@admin_required +def add_chapter(subject_id): + subject = Subject.query.get(subject_id) + if not subject: + flash("Subject does not exist") + return redirect(url_for('admin')) + return render_template('chapter/add.html', subject=subject) + +@app.route('/admin/chapter/add/', methods=['POST']) +@admin_required +def add_chapter_post(subject_id): + name = request.form['name'] + description = request.form['description'] + if not name or not subject_id: + flash("Please fill out the chapter name") + return redirect(url_for('add_chapter', subject_id=subject_id)) + chapter = Chapter.query.filter_by(chapter_name=name, subject_id=subject_id).first() + if chapter: + flash("Chapter already exists") + return redirect(url_for('add_chapter')) + new_chapter = Chapter(chapter_name=name, chapter_description=description, subject_id=subject_id) + db.session.add(new_chapter) + db.session.commit() + flash("Chapter added successfully") + return redirect(url_for('admin')) + +@app.route('/admin/chapter//edit') +@admin_required +def edit_chapter(chapter_id): + chapter = Chapter.query.get(chapter_id) + if not chapter: + flash("Chapter does not exist") + return redirect(url_for('admin')) + return render_template('chapter/edit.html', chapter=chapter) + +@app.route('/admin/chapter//edit', methods=['POST']) +@admin_required +def edit_chapter_post(chapter_id): + chapter = Chapter.query.get(chapter_id) + if not chapter: + flash("Chapter does not exist") + return redirect(url_for('admin')) + name = request.form['name'] + description = request.form['description'] + if not name: + flash("Please fill out the chapter name") + return redirect(url_for('edit_chapter', id=id)) + chapter.chapter_name = name + chapter.chapter_description = description + db.session.commit() + flash("Chapter updated successfully") + return redirect(url_for('admin')) + +@app.route('/admin/chapter//delete') +@admin_required +def delete_chapter(chapter_id): + chapter = Chapter.query.get(chapter_id) + if not chapter: + flash("Chapter does not exist") + return redirect(url_for('admin')) + return render_template('chapter/delete.html', chapter=chapter) + +@app.route('/admin/chapter//delete', methods=['POST']) +@admin_required +def delete_chapter_post(chapter_id): + chapter = Chapter.query.get(chapter_id) + if not chapter: + flash("Chapter does not exist") + return redirect(url_for('admin')) + db.session.delete(chapter) + db.session.commit() + flash("Chapter deleted successfully") + return redirect(url_for('admin')) + +@app.route('/admin/subject//edit') +@admin_required +def edit_subject(id): + subject = Subject.query.get(id) + if not subject: + flash("Subject does not exist") + return redirect(url_for('admin')) + return render_template('subject/edit.html', subject=subject) + +@app.route('/admin/subject//edit', methods=['POST']) +@admin_required +def edit_subject_post(id): + subject = Subject.query.get(id) + if not subject: + flash("Subject does not exist") + return redirect(url_for('admin')) + name = request.form['name'] + description = request.form['description'] + if not name: + flash("Please fill out the subject name") + return redirect(url_for('edit_subject', id=id)) + subject.subject_name = name + subject.subject_description = description + db.session.commit() + flash("Subject updated successfully") + return redirect(url_for('admin')) + +@app.route('/admin/subject//delete') +@admin_required +def delete_subject(id): + subject = Subject.query.get(id) + if not subject: + flash("Subject does not exist") + return redirect(url_for('admin')) + return render_template('subject/delete.html', subject=subject) + +@app.route('/admin/subject//delete', methods=['POST']) +@admin_required +def delete_subject_post(id): + subject = Subject.query.get(id) + + if not subject: + return jsonify({'message': 'Subject not found!'}), 404 + + for chapter in subject.chapters: + for quiz in chapter.quizzes: + db.session.query(Scores).filter_by(quiz_id=quiz.id).delete() + db.session.delete(quiz) + + db.session.delete(subject) + db.session.commit() + return redirect(url_for('admin')) + +@app.route('/admin/quiz') +@admin_required +def quiz(): + quizzes = Quiz.query.all() + return render_template('quiz.html', quizzes=quizzes) + +@app.route('/admin/quiz/add') +@admin_required +def add_quiz(): + chapters = Chapter.query.all() + subjects = Subject.query.all() + return render_template('quiz/add.html', chapters=chapters, subjects=subjects) + +@app.route('/admin/quiz/add', methods=['POST']) +@admin_required +def add_quiz_post(): + name = request.form['name'] + remarks = request.form['remarks'] + chapter_id = request.form.get('chapter_id') + date_of_quiz = request.form.get('date_of_quiz') + time_duration = request.form.get('time_duration') + if not name or not chapter_id or not date_of_quiz or not time_duration: + flash("Please fill out the necessary details") + return redirect(url_for('add_quiz')) + quiz = Quiz.query.filter_by(quiz_name=name).first() + if quiz: + flash("Quiz already exists") + return redirect(url_for('add_quiz')) + try: + date_of_quiz = datetime.strptime(date_of_quiz, "%Y-%m-%d") + hours, minutes, seconds = map(int, time_duration.split(':')) + time_duration = timedelta(hours=hours, minutes=minutes, seconds=seconds) + except ValueError: + flash("Invalid date or time format") + return redirect(url_for('add_quiz')) + new_quiz = Quiz(quiz_name=name, remarks=remarks, chapter_id=int(chapter_id), date_of_quiz=date_of_quiz, time_duration=time_duration) + db.session.add(new_quiz) + db.session.commit() + flash("Quiz added successfully") + return redirect(url_for('quiz')) + +@app.route('/admin/quiz//edit') +@admin_required +def edit_quiz(id): + quiz = Quiz.query.get(id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + chapters = Chapter.query.all() + subjects = Subject.query.all() + return render_template('quiz/edit.html', quiz=quiz, chapters=chapters, subjects=subjects) + +@app.route('/admin/quiz//edit', methods=['POST']) +@admin_required +def edit_quiz_post(id): + quiz = Quiz.query.get(id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + + name = request.form['name'] + remarks = request.form['remarks'] + subject_id = request.form.get('subject_id') + chapter_id = request.form.get('chapter_id') + date_of_quiz = request.form.get('date_of_quiz') + time_duration = request.form.get('time_duration') + + if not name or not subject_id or not chapter_id or not date_of_quiz or not time_duration: + flash("Please fill out all the necessary details") + return redirect(url_for('edit_quiz', id=id)) + + selected_chapter = Chapter.query.get(chapter_id) + if not selected_chapter or int(selected_chapter.subject_id) != int(subject_id): + flash("Selected chapter does not belong to the selected subject") + return redirect(url_for('edit_quiz', id=id)) + + quiz.quiz_name = name + quiz.remarks = remarks + quiz.chapter_id = int(chapter_id) + + try: + parsed_date = datetime.strptime(date_of_quiz, "%Y-%m-%d") + parts = time_duration.split(':') + hours = int(parts[0]) + minutes = int(parts[1]) + seconds = int(parts[2]) + time_duration_obj = timedelta(hours=hours, minutes=minutes, seconds=seconds) + except ValueError: + flash("Invalid date or time format") + return redirect(url_for('edit_quiz', id=id)) + + quiz.date_of_quiz = parsed_date + quiz.time_duration = time_duration_obj + + db.session.commit() + flash("Quiz updated successfully") + return redirect(url_for('quiz')) + +@app.route('/admin/quiz//delete') +@admin_required +def delete_quiz(id): + quiz = Quiz.query.get(id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + return render_template('quiz/delete.html', quiz=quiz, chapters = Chapter.query.all(), subjects = Subject.query.all()) + +@app.route('/admin/quiz//delete', methods=['POST']) +@admin_required +def delete_quiz_post(id): + quiz = Quiz.query.get(id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + db.session.delete(quiz) + db.session.commit() + flash("Quiz deleted successfully") + return redirect(url_for('quiz')) + +@app.route('/admin/quiz//details') +@admin_required +def quiz_details(id): + quiz = Quiz.query.get(id) + chapters = Chapter.query.all() + subjects = Subject.query.all() + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + return render_template('quiz/details.html', quiz=quiz, chapters=chapters, subjects=subjects) + +@app.route('/admin/question/add/') +@admin_required +def add_question(quiz_id): + quiz = Quiz.query.get(quiz_id) + subject = Subject.query.get(quiz.chapter.subject_id) + chapter = Chapter.query.get(quiz.chapter_id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + last_question = Question.query.filter_by(quiz_id=quiz_id).order_by(Question.question_number.desc()).first() + next_question_number = (last_question.question_number + 1) if last_question else 1 + + return render_template('question/add.html', quiz=quiz, subject=subject, chapter=chapter, next_question_number=next_question_number) + +@app.route('/admin/question/add/', methods=['POST']) +@admin_required +def add_question_post(quiz_id): + quiz = Quiz.query.get(quiz_id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('quiz')) + question_number = request.form.get('question_number') + question_title = request.form.get('question_title') + question_statement = request.form.get('question_statement') + option1 = request.form.get('option1') + option2 = request.form.get('option2') + option3 = request.form.get('option3') + option4 = request.form.get('option4') + correct_option = request.form.get('correct_option') + if not question_number or not question_statement or not option1 or not option2 or not option3 or not option4 or not correct_option: + flash("Please fill out all the fields") + return redirect(url_for('add_question', quiz_id=quiz_id)) + question = Question.query.filter_by(question_number=question_number, quiz_id=quiz_id).first() + if question: + flash("Question already exists") + return redirect(url_for('add_question', quiz_id=quiz_id)) + new_question = Question(question_number=question_number, question_title = question_title, question_statement=question_statement, option1=option1, option2=option2, option3=option3, option4=option4, correct_option=correct_option, quiz_id=quiz_id) + db.session.add(new_question) + db.session.commit() + flash("Question added successfully") + return redirect(url_for('quiz', id=quiz_id)) + +@app.route('/admin/question//edit') +@admin_required +def edit_question(id): + question = Question.query.get(id) + if not question: + flash("Question does not exist") + return redirect(url_for('quiz')) + quiz = Quiz.query.get(question.quiz_id) + subject = Subject.query.get(quiz.chapter.subject_id) + chapter = Chapter.query.get(quiz.chapter_id) + return render_template('question/edit.html', question=question, quiz=quiz, subject=subject, chapter=chapter) + +@app.route('/admin/question//edit', methods=['POST']) +@admin_required +def edit_question_post(id): + question = Question.query.get(id) + if not question: + flash("Question does not exist") + return redirect(url_for('quiz')) + question_number = request.form.get('question_number') + question_title = request.form.get('question_title') + question_statement = request.form.get('question_statement') + option1 = request.form.get('option1') + option2 = request.form.get('option2') + option3 = request.form.get('option3') + option4 = request.form.get('option4') + correct_option = request.form.get('correct_option') + if not question_number or not question_statement or not option1 or not option2 or not option3 or not option4 or not correct_option: + flash("Please fill out all the fields") + return redirect(url_for('edit_question', id=id)) + question.question_number = question_number + question.question_title = question_title + question.question_statement = question_statement + question.option1 = option1 + question.option2 = option2 + question.option3 = option3 + question.option4 = option4 + question.correct_option = correct_option + db.session.commit() + flash("Question updated successfully") + return redirect(url_for('quiz', id=question.quiz_id)) + +@app.route('/admin/question//delete') +@admin_required +def delete_question(id): + question = Question.query.get(id) + if not question: + flash("Question does not exist") + return redirect(url_for('quiz')) + quiz_id = question.quiz_id + quiz = Quiz.query.get(quiz_id) + chapter = Chapter.query.get(quiz.chapter_id) + subject = Subject.query.get(chapter.subject_id) + return render_template('question/delete.html', question=question, quiz_id=quiz_id, subject=subject, chapter=chapter) + +@app.route('/admin/question//delete', methods=['POST']) +@admin_required +def delete_question_post(id): + question = Question.query.get(id) + if not question: + flash("Question does not exist") + return redirect(url_for('quiz')) + quiz_id = question.quiz_id + db.session.delete(question) + db.session.commit() + flash("Question deleted successfully") + return redirect(url_for('quiz', id=quiz_id)) + +@app.route('/quiz/') +@auth_required +def user_quiz(id): + quiz = Quiz.query.get(id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('index')) + subject = Subject.query.get(quiz.chapter.subject_id) + chapter = Chapter.query.get(quiz.chapter_id) + current_date = datetime.now() + return render_template('quiz_user/view.html', quiz=quiz, subject=subject, chapter=chapter, current_date=current_date) + +@app.route('/quiz//start') +@auth_required +def start_quiz(id): + quiz = Quiz.query.get(id) + if not quiz: + flash("Quiz does not exist") + return redirect(url_for('index')) + questions = Question.query.filter_by(quiz_id=id).all() + subject = Subject.query.get(quiz.chapter.subject_id) + chapter = Chapter.query.get(quiz.chapter_id) + quiz.time_duration = quiz.time_duration.total_seconds() + return render_template('quiz_user/start.html', quiz=quiz, questions=questions, subject=subject, chapter=chapter) + +@app.route('/quiz//submit', methods=['POST']) +@auth_required +def submit_quiz(id): + quiz = Quiz.query.get_or_404(id) + questions = Question.query.filter_by(quiz_id=id).all() + user_id = session['user_id'] + + user_answers_dict = {} + score = 0 + + for question in questions: + user_choice = request.form.get(f"q{question.id}") + user_answers_dict[str(question.id)] = user_choice + + if user_choice == question.correct_option: + score += 1 + + total_questions = len(questions) + + if not user_answers_dict: + flash("Your answers were not captured. Please try again.", "danger") + return redirect(url_for('quiz', id=id)) + + new_score = Scores( + user_id=user_id, + quiz_id=id, + total_scored=score, + time_stamp_of_event=datetime.now(ist), + user_answers=user_answers_dict + ) + + db.session.add(new_score) + db.session.commit() + + flash(f"Quiz Submitted Successfully! You scored {score}/{total_questions}.", "success") + return redirect(url_for('user_scores')) + + + +@app.route('/scores') +@auth_required +def user_scores(): + user = User.query.get(session['user_id']) + user_scores = Scores.query.filter_by(user_id=user.id).all() + quizzes = {quiz.id: {"name": quiz.quiz_name, "total_score": len(quiz.questions)} for quiz in Quiz.query.filter(Quiz.id.in_([score.quiz_id for score in user_scores])).all()} + + return render_template('scores/score.html', user_scores=user_scores, quizzes=quizzes, user=user) + + +@app.route('/scores//details') +@auth_required +def score_details(id): + user_id = session.get('user_id') + score_entry = Scores.query.filter_by(id=id, user_id=user_id).first() + + if not score_entry: + flash("You don't have access to this score.", "danger") + return redirect(url_for('user_scores')) + + quiz_id = score_entry.quiz_id + quiz = Quiz.query.get(quiz_id) + total_questions = Question.query.filter_by(quiz_id=quiz_id).count() + user_answers_dict = score_entry.user_answers if score_entry.user_answers else {} + questions = Question.query.filter_by(quiz_id=quiz_id).all() + + user_answers = [] + for question in questions: + user_answer = user_answers_dict.get(str(question.id), "Not Answered") + correct_answer = question.correct_option + user_answers.append({ + "question_text": question.question_statement, + "user_answer": user_answer, + "correct_answer": correct_answer, + "is_correct": user_answer == correct_answer + }) + + return render_template('scores/quiz_details.html', + score=score_entry, + quiz=quiz, + user_answers=user_answers, + total_questions=total_questions) + +@app.route('/summary') +@auth_required +def user_summary(): + user_id = session.get('user_id') + + if not user_id: + return redirect(url_for('login')) + + user = User.query.get(user_id) + if not user: + return "User not found", 404 + + subject_stats = db.session.query( + Subject.subject_name, + (func.sum(Scores.total_scored) * 100.0 / func.sum( + db.session.query(func.count(Question.id)).filter(Question.quiz_id == Scores.quiz_id).as_scalar() + )).label("avg_percentage") + ).join(Chapter, Chapter.subject_id == Subject.id) \ + .join(Quiz, Quiz.chapter_id == Chapter.id) \ + .join(Scores, Scores.quiz_id == Quiz.id) \ + .filter(Scores.user_id == user_id) \ + .group_by(Subject.subject_name) \ + .all() + + subject_stats = [ + {"subject_name": s.subject_name, "avg_percentage": round(s.avg_percentage, 2) if s.avg_percentage else 0} + for s in subject_stats + ] + + quiz_attempts = db.session.query( + func.strftime('%Y-%m', Scores.time_stamp_of_event).label("month"), + func.count(Scores.id).label("attempts") + ).filter(Scores.user_id == user_id) \ + .group_by("month") \ + .order_by("month") \ + .all() + + formatted_quiz_attempts = [] + for entry in quiz_attempts: + if entry.month: + formatted_date = datetime.strptime(entry.month, "%Y-%m").strftime("%B, %Y") + else: + formatted_date = "Unknown Date" + formatted_quiz_attempts.append({"month": formatted_date, "attempts": entry.attempts}) + + return render_template('summary/user_summary.html', subject_stats=subject_stats, quiz_attempts=formatted_quiz_attempts) + +@app.route('/debug-session') +def debug_session(): + return jsonify(dict(session)) + +@app.route('/admin/summary') +@admin_required +def admin_summary(): + subjects = Subject.query.all() + subject_labels = [subject.subject_name for subject in subjects] + subject_stats = [] + + for subject in subjects: + quizzes = Quiz.query.join(Chapter).filter(Chapter.subject_id == subject.id).all() + total_marks_per_user = {} + + for quiz in quizzes: + total_marks = db.session.query(func.count(Question.id)) \ + .filter(Question.quiz_id == quiz.id) \ + .scalar() or 1 + + quiz_scores = db.session.query(Scores.user_id, func.sum(Scores.total_scored)) \ + .filter(Scores.quiz_id == quiz.id) \ + .group_by(Scores.user_id) \ + .all() + + for user_id, total_scored in quiz_scores: + if user_id not in total_marks_per_user: + total_marks_per_user[user_id] = {"scored": 0, "total": 0} + + total_marks_per_user[user_id]["scored"] += total_scored + total_marks_per_user[user_id]["total"] += total_marks * db.session.query(func.count(Scores.id)).filter(Scores.quiz_id == quiz.id, Scores.user_id == user_id).scalar() or 1 + + top_scorer = None + top_percentage = 0 + total_percentage_sum = 0 + attempt_count = len(total_marks_per_user) + + for user_id, marks in total_marks_per_user.items(): + percentage = (marks["scored"] / marks["total"]) * 100 if marks["total"] > 0 else 0 + total_percentage_sum += percentage + + if percentage > top_percentage: + top_percentage = percentage + top_scorer = User.query.get(user_id) + + avg_percentage = total_percentage_sum / attempt_count if attempt_count > 0 else 0 + + subject_stats.append({ + "subject_name": subject.subject_name, + "top_scorer": top_scorer.name if top_scorer else "No attempts", + "top_percentage": round(top_percentage, 2), + "avg_percentage": round(avg_percentage, 2) + }) + + return render_template('summary/admin_summary.html', + subject_stats=subject_stats, + subject_labels=subject_labels) + +@app.route('/search/user') +@auth_required +def user_search(): + query = request.args.get('q', '').strip() + + if not query: + return render_template('search_results.html', results=[], query=query) + + results = [] + subjects = Subject.query.filter(Subject.subject_name.ilike(f"%{query}%")).all() + quizzes = Quiz.query.filter(Quiz.quiz_name.ilike(f"%{query}%")).all() + + results.extend([ + {"type": "Subject", "name": subject.subject_name, "id": subject.id} for subject in subjects + ]) + results.extend([ + {"type": "Quiz", "name": quiz.quiz_name, "id": quiz.id} for quiz in quizzes + ]) + + return render_template('search/search_results.html', results=results, query=query) + +@app.route('/quiz/', methods=['GET']) +@auth_required +def view_quiz(quiz_id): + quiz = Quiz.query.get_or_404(quiz_id) + questions = Question.query.filter_by(quiz_id=quiz_id).all() + subject = Subject.query.get(quiz.chapter.subject_id) + chapter = Chapter.query.get(quiz.chapter_id) + current_date = datetime.now() + return render_template('quiz.html', quiz=quiz, questions=questions, subject=subject, chapter=chapter, current_date=current_date) + +@app.route('/subject/', methods=['GET']) +@auth_required +def view_subject(subject_id): + subject = Subject.query.get_or_404(subject_id) + return render_template('search/subject_details.html', subject=subject) + +@app.route('/admin/user/') +@admin_required +def admin_view_user(user_id): + user = User.query.get_or_404(user_id) + user_scores = db.session.query(Scores).filter(Scores.user_id == user_id).all() + total_quizzes = len(user_scores) + highest_score = max((s.total_scored for s in user_scores), default=0) + + total_score_sum = 0 + total_marks_sum = 0 + quiz_attempts = [] + + for s in user_scores: + total_marks = db.session.query(func.count(Question.id)).filter(Question.quiz_id == s.quiz_id).scalar() or 1 + total_marks_sum += total_marks + total_score_sum += s.total_scored + + quiz_attempts.append({ + "quiz_name": s.quiz.quiz_name, + "subject_name": s.quiz.chapter.subject.subject_name, + "score": s.total_scored, + "total_marks": total_marks, + "percentage": round((s.total_scored / total_marks) * 100, 2) + }) + + average_score = (total_score_sum / total_marks_sum * 100) if total_marks_sum > 0 else 0 + + return render_template( + 'search/user_details.html', + user=user, + scores=quiz_attempts, + total_quizzes=total_quizzes, + average_score=round(average_score, 2), + highest_score=highest_score + ) + +@app.route('/admin/search') +@admin_required +def admin_search(): + query = request.args.get('q', '').strip() + + if not query: + return render_template('search_results.html', results=[], query=query) + + results = [] + users = User.query.filter(User.name.ilike(f"%{query}%")).all() + subjects = Subject.query.filter(Subject.subject_name.ilike(f"%{query}%")).all() + quizzes = Quiz.query.filter(Quiz.quiz_name.ilike(f"%{query}%")).all() + + results.extend([ + {"type": "User", "name": user.name, "id": user.id} for user in users + ]) + results.extend([ + {"type": "Subject", "name": subject.subject_name, "id": subject.id} for subject in subjects + ]) + results.extend([ + {"type": "Quiz", "name": quiz.quiz_name, "id": quiz.id} for quiz in quizzes + ]) + + return render_template('search/search_results.html', results=results, query=query) \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/instance/db.sqlite3 b/Domains/FullStack/MiniProjects/QuizMaster/instance/db.sqlite3 new file mode 100644 index 00000000..f1d62f10 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/instance/db.sqlite3 differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/models.py b/Domains/FullStack/MiniProjects/QuizMaster/models.py new file mode 100644 index 00000000..71ca44dc --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/models.py @@ -0,0 +1,86 @@ +from app import app +from flask_sqlalchemy import SQLAlchemy +from werkzeug.security import generate_password_hash, check_password_hash +from sqlalchemy.dialects.postgresql import JSONB + +db = SQLAlchemy(app) + +class User(db.Model): + id = db.Column(db.Integer, primary_key=True) + username = db.Column(db.String(92), unique=True) + passhash = db.Column(db.String(256), nullable=False) + name = db.Column(db.String(64), nullable=True) + dob = db.Column(db.DateTime, nullable=True) + qualification = db.Column(db.String(256), nullable=True) + is_admin = db.Column(db.Boolean, nullable=False, default=False) + +class Subject(db.Model): + id = db.Column(db.Integer, primary_key=True) + subject_name = db.Column(db.String(92), unique=True, nullable=False) + subject_description = db.Column(db.String(256), nullable=True) + +class Chapter(db.Model): + id = db.Column(db.Integer, primary_key=True) + chapter_name = db.Column(db.String(92), unique=True, nullable=False) + chapter_description = db.Column(db.String(256), nullable=True) + subject_id = db.Column(db.Integer, db.ForeignKey('subject.id'), nullable=False) + subject = db.relationship('Subject', backref=db.backref('chapters', lazy=True, cascade='all, delete')) + + quizzes = db.relationship('Quiz', back_populates='chapter', lazy=True, cascade='all, delete-orphan') + + def get_questions_count(self): + return sum(len(quiz.questions) for quiz in self.quizzes) + +class Quiz(db.Model): + id = db.Column(db.Integer, primary_key=True) + quiz_name = db.Column(db.String(92), unique=True, nullable=False) + remarks = db.Column(db.String(256), nullable=True) + chapter_id = db.Column(db.Integer, db.ForeignKey('chapter.id'), nullable=False) + chapter = db.relationship('Chapter', back_populates='quizzes') + date_of_quiz = db.Column(db.DateTime, nullable=False) + time_duration = db.Column(db.Interval, nullable=False) + questions = db.relationship('Question', back_populates='quiz', lazy=True, cascade='all, delete-orphan') + scores = db.relationship('Scores', back_populates='quiz', lazy=True, cascade='all, delete-orphan') + +class Question(db.Model): + id = db.Column(db.Integer, primary_key=True) + question_number = db.Column(db.Integer, nullable=False) + question_title = db.Column(db.String(256), nullable=False) + question_statement = db.Column(db.Text, nullable=False) + option1 = db.Column(db.String(256), nullable=False) + option2 = db.Column(db.String(256), nullable=False) + option3 = db.Column(db.String(256), nullable=False) + option4 = db.Column(db.String(256), nullable=False) + correct_option = db.Column(db.String(1), nullable=False) + quiz_id = db.Column(db.Integer, db.ForeignKey('quiz.id'), nullable=False) + quiz = db.relationship('Quiz', back_populates='questions') + + @staticmethod + def get_next_question_number(quiz_id): + last_question = Question.query.filter_by(quiz_id=quiz_id).order_by(Question.question_number.desc()).first() + if last_question: + return last_question.question_number + 1 + return 1 + +class Scores(db.Model): + __tablename__ = 'scores' + + id = db.Column(db.Integer, primary_key=True) + user_id = db.Column(db.Integer, db.ForeignKey('user.id'), nullable=False) + user = db.relationship('User', backref=db.backref('scores', lazy=True)) + quiz_id = db.Column(db.Integer, db.ForeignKey('quiz.id'), nullable=False) + quiz = db.relationship('Quiz', back_populates='scores') + + total_scored = db.Column(db.Integer, nullable=False) + time_stamp_of_event = db.Column(db.DateTime, nullable=False) + user_answers = db.Column(JSONB, nullable=False) + + +with app.app_context(): + db.create_all() + admin = User.query.filter_by(is_admin=True).first() + if not admin: + password_hash = generate_password_hash('admin') + admin = User(username='admin', passhash=password_hash, name='admin', is_admin=True) + db.session.add(admin) + db.session.commit() \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/requirements.txt b/Domains/FullStack/MiniProjects/QuizMaster/requirements.txt new file mode 100644 index 00000000..cdc5b53e --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/requirements.txt @@ -0,0 +1,18 @@ +aniso8601==10.0.0 +blinker==1.9.0 +click==8.1.8 +Flask==3.1.0 +Flask-RESTful==0.3.10 +Flask-SQLAlchemy==3.1.1 +gunicorn==23.0.0 +itsdangerous==2.2.0 +Jinja2==3.1.5 +MarkupSafe==3.0.2 +packaging==25.0 +psycopg2-binary==2.9.10 +python-dotenv==1.0.1 +pytz==2025.1 +six==1.17.0 +SQLAlchemy==2.0.37 +typing_extensions==4.12.2 +Werkzeug==3.1.3 diff --git a/Domains/FullStack/MiniProjects/QuizMaster/run_app.py b/Domains/FullStack/MiniProjects/QuizMaster/run_app.py new file mode 100644 index 00000000..76abc931 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/run_app.py @@ -0,0 +1,23 @@ +import os +os.environ['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///db.sqlite3' +import sqlalchemy +from sqlalchemy import TEXT +from sqlalchemy.types import TypeDecorator +import json +from app import app +app.secret_key = 'your_secret_key' +class JSONEncodedDict(TypeDecorator): + impl = TEXT + def process_bind_param(self, value, dialect): + if value is not None: + value = json.dumps(value) + return value + def process_result_value(self, value, dialect): + if value is not None: + value = json.loads(value) + return value +import sqlalchemy.dialects.postgresql +sqlalchemy.dialects.postgresql.JSONB = JSONEncodedDict +from app import app +if __name__ == '__main__': + app.run(debug=True) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/swagger_subject_chapter_api.yaml b/Domains/FullStack/MiniProjects/QuizMaster/swagger_subject_chapter_api.yaml new file mode 100644 index 00000000..a252d942 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/swagger_subject_chapter_api.yaml @@ -0,0 +1,112 @@ + +openapi: 3.0.0 +info: + title: Subject & Chapter API + version: 1.0.0 + description: A simple RESTful API to manage subjects and chapters. + +servers: + - url: http://localhost:5000 + +paths: + /api/subject: + get: + summary: Get all subjects + responses: + '200': + description: A list of subjects + content: + application/json: + schema: + type: object + properties: + subjects: + type: array + items: + $ref: '#/components/schemas/Subject' + post: + summary: Create a new subject + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/SubjectInput' + responses: + '201': + description: Subject created successfully + '400': + description: Subject already exists + + /api/chapter: + get: + summary: Get all chapters + responses: + '200': + description: A list of chapters + content: + application/json: + schema: + type: object + properties: + chapters: + type: array + items: + $ref: '#/components/schemas/Chapter' + post: + summary: Create a new chapter + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/ChapterInput' + responses: + '201': + description: Chapter created successfully + '400': + description: Chapter already exists under this subject + +components: + schemas: + Subject: + type: object + properties: + id: + type: integer + name: + type: string + description: + type: string + + SubjectInput: + type: object + required: + - name + - description + properties: + name: + type: string + description: + type: string + + Chapter: + type: object + properties: + id: + type: integer + name: + type: string + subject_id: + type: integer + + ChapterInput: + type: object + required: + - name + - subject_id + properties: + name: + type: string + subject_id: + type: integer diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/admin.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/admin.html new file mode 100644 index 00000000..207631b8 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/admin.html @@ -0,0 +1,82 @@ +{% extends 'layout.html' %} + +{% block title %} Admin Dashboard {% endblock %} + +{% block content %} +
+

+ Admin Dashboard +

+ + + +

+ Subjects +

+ + {% if subjects %} +
+ {% for subject in subjects %} +
+
+
+
{{ subject.subject_name }}
+
+
+ {% if subject.chapters %} + + + + + + + + + + {% for chapter in subject.chapters %} + + + + + + {% endfor %} + +
ChapterQuestionsAction
{{ chapter.chapter_name }}{{ chapter.get_questions_count() }} + + + + + + +
+ {% else %} +

No chapters available.

+ {% endif %} + + +
+ +
+
+ {% endfor %} +
+ {% else %} +

No subjects available.

+ {% endif %} +
+{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/add.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/add.html new file mode 100644 index 00000000..fb883413 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/add.html @@ -0,0 +1,39 @@ +{% extends 'layout.html' %} + +{% block title %} + Add Chapter +{% endblock %} + +{% block content %} +
+
+
+

New Chapter under {{ subject.subject_name }}

+
+
+
+ + +
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/delete.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/delete.html new file mode 100644 index 00000000..999e1322 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/delete.html @@ -0,0 +1,41 @@ +{% extends 'layout.html' %} + +{% block title %} + Delete Chapter +{% endblock %} + +{% block content %} +
+
+
+

Delete Chapter

+
+
+
+
+ + +
+ +
+ + +
+ +
+ Are you sure you want to delete this chapter? +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/edit.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/edit.html new file mode 100644 index 00000000..ce2b1cb0 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/chapter/edit.html @@ -0,0 +1,37 @@ +{% extends 'layout.html' %} + +{% block title %} + Edit Chapter +{% endblock %} + +{% block content %} +
+
+
+

Edit Chapter

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/index.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/index.html new file mode 100644 index 00000000..74872d45 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/index.html @@ -0,0 +1,134 @@ +{% extends 'layout.html' %} + +{% block title %} User Dashboard {% endblock %} + +{% block content %} +
+

Welcome, {{ user.name }}!

+ + +
+
+

+ Quizzes Available Now +

+
+
+ {% if available_quizzes %} + + + + + + + + + + + + {% for quiz in available_quizzes %} + + + + + + + + {% endfor %} + +
Name Questions Date Duration Actions
{{ quiz.quiz_name }}{{ quiz_question_counts[quiz.id] }}{{ quiz.date_of_quiz.strftime('%Y-%m-%d') }}{{ quiz.time_duration }} + + View + + + Start + +
+ {% else %} +

No quizzes available at the moment.

+ {% endif %} +
+
+ + +
+
+

+ Upcoming Quizzes +

+
+
+ {% if upcoming_quizzes %} + + + + + + + + + + + + {% for quiz in upcoming_quizzes %} + + + + + + + + {% endfor %} + +
Name Questions Date Duration Details
{{ quiz.quiz_name }}{{ quiz_question_counts[quiz.id] }}{{ quiz.date_of_quiz.strftime('%Y-%m-%d') }}{{ quiz.time_duration }} + + View + +
+ {% else %} +

No upcoming quizzes scheduled.

+ {% endif %} +
+
+ + +
+
+

+ Attempted Quizzes +

+
+
+ {% if attempted_quizzes %} + + + + + + + + + + + {% for quiz in attempted_quizzes %} + + + + + + + {% endfor %} + +
Name Questions Date Details
{{ quiz.quiz_name }}{{ quiz_question_counts[quiz.id] }}{{ quiz.date_of_quiz.strftime('%Y-%m-%d') }} + + View + +
+ {% else %} +

You haven't attempted any quizzes yet.

+ {% endif %} +
+
+
+ +{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/layout.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/layout.html new file mode 100644 index 00000000..365af3ff --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/layout.html @@ -0,0 +1,65 @@ + + + + + + + {% block title %} Quiz Master {% endblock %} + + + + + + {% block style %} + + {% endblock %} + + + {% include 'navbar.html' with context %} + +
+ {% include 'messages.html' with context %} +
+ +
+ {% block content %} + {% endblock %} +
+ + {% block script %}{% endblock %} + + + + \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/login.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/login.html new file mode 100644 index 00000000..ccc9d362 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/login.html @@ -0,0 +1,41 @@ +{% extends 'layout.html' %} + +{% block title %} Login {% endblock %} + +{% block content %} +
+
+

+ Login +

+ +
+
+ + +
+ +
+ + +
+ + +
+ +
+

Don't have an account? + Register +

+
+
+
+{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/messages.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/messages.html new file mode 100644 index 00000000..316fdf12 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/messages.html @@ -0,0 +1,17 @@ +{% with messages = get_flashed_messages() %} + {% if messages %} +
    + {% for message in messages %} + {% if 'success' in message|lower %} + + {% else %} + + {% endif %} + {% endfor %} +
+ {% endif %} + {% endwith %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/navbar.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/navbar.html new file mode 100644 index 00000000..7445ed3c --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/navbar.html @@ -0,0 +1,38 @@ + \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/profile.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/profile.html new file mode 100644 index 00000000..86bb2eb4 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/profile.html @@ -0,0 +1,60 @@ +{% extends 'layout.html' %} + +{% block title %} Profile {% endblock %} + +{% block content %} +
+
+

+ Profile +

+ +

Hello, {{ user.name }}

+ + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/question/add.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/question/add.html new file mode 100644 index 00000000..f87d2228 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/question/add.html @@ -0,0 +1,72 @@ +{% extends 'layout.html' %} + +{% block title %} + Add Question +{% endblock %} + +{% block content %} +
+
+
+

Add New Question

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/question/delete.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/question/delete.html new file mode 100644 index 00000000..0736fce4 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/question/delete.html @@ -0,0 +1,72 @@ +{% extends 'layout.html' %} + +{% block title %} + Delete Question +{% endblock %} + +{% block content %} +
+
+
+

Delete Question

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/question/edit.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/question/edit.html new file mode 100644 index 00000000..f0f68056 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/question/edit.html @@ -0,0 +1,72 @@ +{% extends 'layout.html' %} + +{% block title %} + Edit Question +{% endblock %} + +{% block content %} +
+
+
+

Edit Question

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ +
+ + + + +
+
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz.html new file mode 100644 index 00000000..f3b54556 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz.html @@ -0,0 +1,82 @@ +{% extends 'layout.html' %} + +{% block title %} Quiz {% endblock %} + +{% block content %} +
+

+ Quiz Management +

+ + + + {% if quizzes %} +
+ {% for quiz in quizzes %} +
+
+ +
+ {% if quiz.questions %} + + + + + + + + + + {% for question in quiz.questions %} + + + + + + {% endfor %} + +
Q#TitleActions
{{ question.question_number }}{{ question.question_title }} + + + + + + +
+ {% else %} +

No questions available.

+ {% endif %} + + +
+ +
+
+ {% endfor %} +
+ {% else %} +

No quizzes available.

+ {% endif %} +
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/add.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/add.html new file mode 100644 index 00000000..fc0e7562 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/add.html @@ -0,0 +1,94 @@ +{% extends 'layout.html' %} + +{% block title %} + Add Quiz +{% endblock %} + +{% block content %} +
+
+
+

Add Quiz

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/delete.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/delete.html new file mode 100644 index 00000000..ddce1153 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/delete.html @@ -0,0 +1,67 @@ +{% extends 'layout.html' %} + +{% block title %} + Delete Quiz +{% endblock %} + +{% block content %} +
+
+
+

Delete Quiz

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/details.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/details.html new file mode 100644 index 00000000..5e8bfa62 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/details.html @@ -0,0 +1,64 @@ +{% extends 'layout.html' %} + +{% block title %} + Quiz Details +{% endblock %} + +{% block content %} +
+
+
+

Quiz Details

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+
+
+
+{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/edit.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/edit.html new file mode 100644 index 00000000..11f6e5b6 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz/edit.html @@ -0,0 +1,131 @@ +{% extends 'layout.html' %} + +{% block title %} + Edit Quiz +{% endblock %} + +{% block content %} +
+
+
+

Edit Quiz

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+ + +{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz_user/start.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz_user/start.html new file mode 100644 index 00000000..d4f19001 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz_user/start.html @@ -0,0 +1,180 @@ +{% extends 'layout.html' %} + +{% block title %} + Start {{ quiz.quiz_name }} +{% endblock %} + +{% block content %} +
+

{{ quiz.quiz_name }}

+ +
+
+ Time Left: +
+
+ Question: 1 / +
+
+ +
+
+ {% for question in questions %} +
+

Q{{ loop.index }}: {{ question.question_statement }}

+ +
+ + + + + + + +
+
+ {% endfor %} +
+ +
+ + +
+ +
+ +
+
+
+ +{% endblock %} + +{% block script %} + + + + +{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz_user/view.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz_user/view.html new file mode 100644 index 00000000..1a645448 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/quiz_user/view.html @@ -0,0 +1,45 @@ +{% extends 'layout.html' %} + +{% block title %} + {{ quiz.quiz_name }} - Quiz Details +{% endblock %} + +{% block content %} +
+
+
+

+ {{ quiz.quiz_name }} +

+

Quiz Details

+ +
+
+

Remarks: {{ quiz.remarks or "No remarks provided" }}

+

Subject: {{ subject.subject_name }}

+

Chapter: {{ chapter.chapter_name }}

+
+
+

Date: {{ quiz.date_of_quiz.strftime('%Y-%m-%d') }}

+

Time Duration: {{ quiz.time_duration }}

+

Number of Questions: {{ quiz.questions|length }}

+
+
+ +
+ {% if quiz.date_of_quiz <= current_date %} + + Start Quiz + + {% else %} +

This quiz is not available yet.

+ {% endif %} + + + Back to Quizzes + +
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/register.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/register.html new file mode 100644 index 00000000..3bfa10e7 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/register.html @@ -0,0 +1,57 @@ +{% extends 'layout.html' %} + +{% block title %} Register {% endblock %} + +{% block content %} +
+
+

+ Register +

+ +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ + +
+ +
+

Already have an account? + Login +

+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/scores/quiz_details.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/scores/quiz_details.html new file mode 100644 index 00000000..e1f2212f --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/scores/quiz_details.html @@ -0,0 +1,65 @@ +{% extends 'layout.html' %} + +{% block title %} + Quiz Details - {{ quiz.quiz_name }} +{% endblock %} + +{% block content %} +
+

{{ quiz.quiz_name }} - Details

+ +
+
+
Quiz Summary
+
+
+

Date Attempted: {{ score.time_stamp_of_event.strftime('%Y-%m-%d %H:%M') }}

+

Score: + {{ score.total_scored }} / {{ total_questions }} +

+
+
+ +
+
+
Your Answers vs Correct Answers
+
+
+
+ + + + + + + + + + + {% for answer in user_answers %} + + + + + + + {% endfor %} + +
Question Your Answer Correct Answer Result
{{ answer.question_text }}{{ answer.user_answer }}{{ answer.correct_answer }} + {% if answer.is_correct %} + Correct + {% else %} + Incorrect + {% endif %} +
+
+
+
+ + +
+{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/scores/score.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/scores/score.html new file mode 100644 index 00000000..93e4f5e6 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/scores/score.html @@ -0,0 +1,47 @@ +{% extends 'layout.html' %} + +{% block title %} My Scores {% endblock %} + +{% block content %} +
+

My Quiz Scores

+ + {% if user_scores %} +
+ + + + + + + + + + + {% for score in user_scores|sort(attribute="time_stamp_of_event", reverse=True) %} + + + + + + + {% endfor %} + +
Quiz Name Date Attempted Score Actions
{{ quizzes[score.quiz_id]["name"] }}{{ score.time_stamp_of_event.strftime('%Y-%m-%d %H:%M') }} + + {{ score.total_scored }} / {{ quizzes[score.quiz_id]["total_score"] }} + + + + View Details + +
+
+ {% else %} +
+

No quiz attempts yet!

+

Take a quiz to see your scores here.

+
+ {% endif %} +
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/search/search_results.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/search/search_results.html new file mode 100644 index 00000000..199725e4 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/search/search_results.html @@ -0,0 +1,36 @@ +{% extends "layout.html" %} + +{% block title %} + Search Results +{% endblock %} + +{% block content %} +
+

Search Results for "{{ query }}"

+ + {% if results %} +
    + {% for result in results %} +
  • +
    + {{ result.type }}: + {{ result.name }} +
    + + View + +
  • + {% endfor %} +
+ {% else %} +
+ No results found. +
+ {% endif %} +
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/search/subject_details.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/search/subject_details.html new file mode 100644 index 00000000..ba90ed8c --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/search/subject_details.html @@ -0,0 +1,51 @@ +{% extends 'layout.html' %} + +{% block content %} +
+
+

{{ subject.subject_name }}

+

Description: {{ subject.subject_description }}

+
+ +
+ +

Chapters

+ + {% if subject.chapters %} +
+ {% for chapter in subject.chapters %} +
+

+ +

+
+
+

{{ chapter.chapter_description }}

+ + {% if chapter.quizzes %} +
Available Quizzes:
+ + {% else %} +

No quizzes available.

+ {% endif %} +
+
+
+ {% endfor %} +
+ {% else %} +
No chapters available for this subject.
+ {% endif %} +
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/search/user_details.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/search/user_details.html new file mode 100644 index 00000000..6bc5be6a --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/search/user_details.html @@ -0,0 +1,89 @@ +{% extends "layout.html" %} + +{% block title %} User Details {% endblock %} + +{% block content %} +
+
+

+ User Details +

+
+

+ {{ user.name }} +

+

Username: {{ user.username }}

+

+ + + {% if user.is_admin %} Admin {% else %} User {% endif %} + +

+
+
+ +
+ +

+ Quiz Statistics +

+
+
+
+
+
Total Quizzes Attempted
+

{{ total_quizzes }}

+
+
+
+
+
+
+
Average Score
+

{{ average_score }}%

+
+
+
+
+
+
+
Highest Score
+

{{ highest_score }}

+
+
+
+
+ +

+ Quiz Attempts +

+ {% if scores %} +
+ + + + + + + + + + + + {% for score in scores %} + + + + + + + + {% endfor %} + +
Quiz Name Subject Score Total Marks Percentage
{{ score.quiz_name }}{{ score.subject_name }}{{ score.score }}{{ score.total_marks }}{{ score.percentage }}%
+
+ {% else %} +
No quizzes attempted by this user.
+ {% endif %} +
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/add.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/add.html new file mode 100644 index 00000000..8ca4e47f --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/add.html @@ -0,0 +1,64 @@ +{% extends 'layout.html' %} + +{% block title %} + Add Subject +{% endblock %} + +{% block content %} +
+
+
+

Add New Subject

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} + +{% block style %} + +{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/delete.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/delete.html new file mode 100644 index 00000000..cdde7364 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/delete.html @@ -0,0 +1,39 @@ +{% extends 'layout.html' %} + +{% block title %} + Delete Subject +{% endblock %} + +{% block content %} +
+
+
+

Delete Subject

+
+
+
+
+ + +
+ +
+ + +
+ +

Are you sure you want to delete this subject?

+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/edit.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/edit.html new file mode 100644 index 00000000..648421b1 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/subject/edit.html @@ -0,0 +1,37 @@ +{% extends 'layout.html' %} + +{% block title %} + Edit Subject +{% endblock %} + +{% block content %} +
+
+
+

Edit Subject

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + + Cancel + +
+
+
+
+
+{% endblock %} diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/summary/admin_summary.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/summary/admin_summary.html new file mode 100644 index 00000000..d362dbc3 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/summary/admin_summary.html @@ -0,0 +1,95 @@ +{% extends 'layout.html' %} + +{% block title %} Admin Summary {% endblock %} + +{% block content %} +
+

+ Admin Dashboard - Summary +

+ +
+ + + + + + + + + + + {% for subject in subject_stats %} + + + + + + + {% endfor %} + +
SubjectTop ScorerTop PercentageAverage Percentage
{{ subject.subject_name }}{{ subject.top_scorer or 'N/A' }}{{ subject.top_percentage | round(2) }}%{{ subject.avg_percentage | round(2) }}%
+
+ +
+

Average Percentage by Subject

+
+ +
+
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/templates/summary/user_summary.html b/Domains/FullStack/MiniProjects/QuizMaster/templates/summary/user_summary.html new file mode 100644 index 00000000..b93d4313 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/templates/summary/user_summary.html @@ -0,0 +1,92 @@ +{% extends 'layout.html' %} + +{% block title %} User Summary {% endblock %} + +{% block content %} +
+

User Summary

+ +
+

Average Percentage by Subject

+
+ +
+
+ +
+

Quiz Attempts Per Month

+
+ + + + + + + + + {% for entry in quiz_attempts %} + + + + + {% endfor %} + +
MonthAttempts
{{ entry.month }}{{ entry.attempts }}
+
+
+
+ + + +{% endblock %} \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/Activate.ps1 b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/Activate.ps1 new file mode 100644 index 00000000..b49d77ba --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/Activate.ps1 @@ -0,0 +1,247 @@ +<# +.Synopsis +Activate a Python virtual environment for the current PowerShell session. + +.Description +Pushes the python executable for a virtual environment to the front of the +$Env:PATH environment variable and sets the prompt to signify that you are +in a Python virtual environment. Makes use of the command line switches as +well as the `pyvenv.cfg` file values present in the virtual environment. + +.Parameter VenvDir +Path to the directory that contains the virtual environment to activate. The +default value for this is the parent of the directory that the Activate.ps1 +script is located within. + +.Parameter Prompt +The prompt prefix to display when this virtual environment is activated. By +default, this prompt is the name of the virtual environment folder (VenvDir) +surrounded by parentheses and followed by a single space (ie. '(.venv) '). + +.Example +Activate.ps1 +Activates the Python virtual environment that contains the Activate.ps1 script. + +.Example +Activate.ps1 -Verbose +Activates the Python virtual environment that contains the Activate.ps1 script, +and shows extra information about the activation as it executes. + +.Example +Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv +Activates the Python virtual environment located in the specified location. + +.Example +Activate.ps1 -Prompt "MyPython" +Activates the Python virtual environment that contains the Activate.ps1 script, +and prefixes the current prompt with the specified string (surrounded in +parentheses) while the virtual environment is active. + +.Notes +On Windows, it may be required to enable this Activate.ps1 script by setting the +execution policy for the user. You can do this by issuing the following PowerShell +command: + +PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser + +For more information on Execution Policies: +https://go.microsoft.com/fwlink/?LinkID=135170 + +#> +Param( + [Parameter(Mandatory = $false)] + [String] + $VenvDir, + [Parameter(Mandatory = $false)] + [String] + $Prompt +) + +<# Function declarations --------------------------------------------------- #> + +<# +.Synopsis +Remove all shell session elements added by the Activate script, including the +addition of the virtual environment's Python executable from the beginning of +the PATH variable. + +.Parameter NonDestructive +If present, do not remove this function from the global namespace for the +session. + +#> +function global:deactivate ([switch]$NonDestructive) { + # Revert to original values + + # The prior prompt: + if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) { + Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt + Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT + } + + # The prior PYTHONHOME: + if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) { + Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME + Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME + } + + # The prior PATH: + if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) { + Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH + Remove-Item -Path Env:_OLD_VIRTUAL_PATH + } + + # Just remove the VIRTUAL_ENV altogether: + if (Test-Path -Path Env:VIRTUAL_ENV) { + Remove-Item -Path env:VIRTUAL_ENV + } + + # Just remove VIRTUAL_ENV_PROMPT altogether. + if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) { + Remove-Item -Path env:VIRTUAL_ENV_PROMPT + } + + # Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether: + if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) { + Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force + } + + # Leave deactivate function in the global namespace if requested: + if (-not $NonDestructive) { + Remove-Item -Path function:deactivate + } +} + +<# +.Description +Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the +given folder, and returns them in a map. + +For each line in the pyvenv.cfg file, if that line can be parsed into exactly +two strings separated by `=` (with any amount of whitespace surrounding the =) +then it is considered a `key = value` line. The left hand string is the key, +the right hand is the value. + +If the value starts with a `'` or a `"` then the first and last character is +stripped from the value before being captured. + +.Parameter ConfigDir +Path to the directory that contains the `pyvenv.cfg` file. +#> +function Get-PyVenvConfig( + [String] + $ConfigDir +) { + Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg" + + # Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue). + $pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue + + # An empty map will be returned if no config file is found. + $pyvenvConfig = @{ } + + if ($pyvenvConfigPath) { + + Write-Verbose "File exists, parse `key = value` lines" + $pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath + + $pyvenvConfigContent | ForEach-Object { + $keyval = $PSItem -split "\s*=\s*", 2 + if ($keyval[0] -and $keyval[1]) { + $val = $keyval[1] + + # Remove extraneous quotations around a string value. + if ("'""".Contains($val.Substring(0, 1))) { + $val = $val.Substring(1, $val.Length - 2) + } + + $pyvenvConfig[$keyval[0]] = $val + Write-Verbose "Adding Key: '$($keyval[0])'='$val'" + } + } + } + return $pyvenvConfig +} + + +<# Begin Activate script --------------------------------------------------- #> + +# Determine the containing directory of this script +$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition +$VenvExecDir = Get-Item -Path $VenvExecPath + +Write-Verbose "Activation script is located in path: '$VenvExecPath'" +Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)" +Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)" + +# Set values required in priority: CmdLine, ConfigFile, Default +# First, get the location of the virtual environment, it might not be +# VenvExecDir if specified on the command line. +if ($VenvDir) { + Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values" +} +else { + Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir." + $VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/") + Write-Verbose "VenvDir=$VenvDir" +} + +# Next, read the `pyvenv.cfg` file to determine any required value such +# as `prompt`. +$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir + +# Next, set the prompt from the command line, or the config file, or +# just use the name of the virtual environment folder. +if ($Prompt) { + Write-Verbose "Prompt specified as argument, using '$Prompt'" +} +else { + Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value" + if ($pyvenvCfg -and $pyvenvCfg['prompt']) { + Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'" + $Prompt = $pyvenvCfg['prompt']; + } + else { + Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)" + Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'" + $Prompt = Split-Path -Path $venvDir -Leaf + } +} + +Write-Verbose "Prompt = '$Prompt'" +Write-Verbose "VenvDir='$VenvDir'" + +# Deactivate any currently active virtual environment, but leave the +# deactivate function in place. +deactivate -nondestructive + +# Now set the environment variable VIRTUAL_ENV, used by many tools to determine +# that there is an activated venv. +$env:VIRTUAL_ENV = $VenvDir + +if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) { + + Write-Verbose "Setting prompt to '$Prompt'" + + # Set the prompt to include the env name + # Make sure _OLD_VIRTUAL_PROMPT is global + function global:_OLD_VIRTUAL_PROMPT { "" } + Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT + New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt + + function global:prompt { + Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) " + _OLD_VIRTUAL_PROMPT + } + $env:VIRTUAL_ENV_PROMPT = $Prompt +} + +# Clear PYTHONHOME +if (Test-Path -Path Env:PYTHONHOME) { + Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME + Remove-Item -Path Env:PYTHONHOME +} + +# Add the venv to the PATH +Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH +$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH" diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate new file mode 100644 index 00000000..ec51a454 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate @@ -0,0 +1,76 @@ +# This file must be used with "source bin/activate" *from bash* +# You cannot run it directly + +deactivate () { + # reset old environment variables + if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then + PATH="${_OLD_VIRTUAL_PATH:-}" + export PATH + unset _OLD_VIRTUAL_PATH + fi + if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then + PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}" + export PYTHONHOME + unset _OLD_VIRTUAL_PYTHONHOME + fi + + # This should detect bash and zsh, which have a hash command that must + # be called to get it to forget past commands. Without forgetting + # past commands the $PATH changes we made may not be respected + if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null + fi + + if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then + PS1="${_OLD_VIRTUAL_PS1:-}" + export PS1 + unset _OLD_VIRTUAL_PS1 + fi + + unset VIRTUAL_ENV + unset VIRTUAL_ENV_PROMPT + if [ ! "${1:-}" = "nondestructive" ] ; then + # Self destruct! + unset -f deactivate + fi +} + +# unset irrelevant variables +deactivate nondestructive + +# on Windows, a path can contain colons and backslashes and has to be converted: +if [ "$OSTYPE" = "cygwin" ] || [ "$OSTYPE" = "msys" ] ; then + # transform D:\path\to\venv to /d/path/to/venv on MSYS + # and to /cygdrive/d/path/to/venv on Cygwin + export VIRTUAL_ENV=$(cygpath "/Users/ekanshjindal/IITM/MAD1/quizmaster/venv") +else + # use the path as-is + export VIRTUAL_ENV="/Users/ekanshjindal/IITM/MAD1/quizmaster/venv" +fi + +_OLD_VIRTUAL_PATH="$PATH" +PATH="$VIRTUAL_ENV/bin:$PATH" +export PATH + +# unset PYTHONHOME if set +# this will fail if PYTHONHOME is set to the empty string (which is bad anyway) +# could use `if (set -u; : $PYTHONHOME) ;` in bash +if [ -n "${PYTHONHOME:-}" ] ; then + _OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}" + unset PYTHONHOME +fi + +if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then + _OLD_VIRTUAL_PS1="${PS1:-}" + PS1="(venv) ${PS1:-}" + export PS1 + VIRTUAL_ENV_PROMPT="(venv) " + export VIRTUAL_ENV_PROMPT +fi + +# This should detect bash and zsh, which have a hash command that must +# be called to get it to forget past commands. Without forgetting +# past commands the $PATH changes we made may not be respected +if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then + hash -r 2> /dev/null +fi diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate.csh b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate.csh new file mode 100644 index 00000000..7a9a80c2 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate.csh @@ -0,0 +1,27 @@ +# This file must be used with "source bin/activate.csh" *from csh*. +# You cannot run it directly. + +# Created by Davide Di Blasi . +# Ported to Python 3.3 venv by Andrew Svetlov + +alias deactivate 'test $?_OLD_VIRTUAL_PATH != 0 && setenv PATH "$_OLD_VIRTUAL_PATH" && unset _OLD_VIRTUAL_PATH; rehash; test $?_OLD_VIRTUAL_PROMPT != 0 && set prompt="$_OLD_VIRTUAL_PROMPT" && unset _OLD_VIRTUAL_PROMPT; unsetenv VIRTUAL_ENV; unsetenv VIRTUAL_ENV_PROMPT; test "\!:*" != "nondestructive" && unalias deactivate' + +# Unset irrelevant variables. +deactivate nondestructive + +setenv VIRTUAL_ENV "/Users/ekanshjindal/IITM/MAD1/quizmaster/venv" + +set _OLD_VIRTUAL_PATH="$PATH" +setenv PATH "$VIRTUAL_ENV/bin:$PATH" + + +set _OLD_VIRTUAL_PROMPT="$prompt" + +if (! "$?VIRTUAL_ENV_DISABLE_PROMPT") then + set prompt = "(venv) $prompt" + setenv VIRTUAL_ENV_PROMPT "(venv) " +endif + +alias pydoc python -m pydoc + +rehash diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate.fish b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate.fish new file mode 100644 index 00000000..b11f634c --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/activate.fish @@ -0,0 +1,69 @@ +# This file must be used with "source /bin/activate.fish" *from fish* +# (https://fishshell.com/). You cannot run it directly. + +function deactivate -d "Exit virtual environment and return to normal shell environment" + # reset old environment variables + if test -n "$_OLD_VIRTUAL_PATH" + set -gx PATH $_OLD_VIRTUAL_PATH + set -e _OLD_VIRTUAL_PATH + end + if test -n "$_OLD_VIRTUAL_PYTHONHOME" + set -gx PYTHONHOME $_OLD_VIRTUAL_PYTHONHOME + set -e _OLD_VIRTUAL_PYTHONHOME + end + + if test -n "$_OLD_FISH_PROMPT_OVERRIDE" + set -e _OLD_FISH_PROMPT_OVERRIDE + # prevents error when using nested fish instances (Issue #93858) + if functions -q _old_fish_prompt + functions -e fish_prompt + functions -c _old_fish_prompt fish_prompt + functions -e _old_fish_prompt + end + end + + set -e VIRTUAL_ENV + set -e VIRTUAL_ENV_PROMPT + if test "$argv[1]" != "nondestructive" + # Self-destruct! + functions -e deactivate + end +end + +# Unset irrelevant variables. +deactivate nondestructive + +set -gx VIRTUAL_ENV "/Users/ekanshjindal/IITM/MAD1/quizmaster/venv" + +set -gx _OLD_VIRTUAL_PATH $PATH +set -gx PATH "$VIRTUAL_ENV/bin" $PATH + +# Unset PYTHONHOME if set. +if set -q PYTHONHOME + set -gx _OLD_VIRTUAL_PYTHONHOME $PYTHONHOME + set -e PYTHONHOME +end + +if test -z "$VIRTUAL_ENV_DISABLE_PROMPT" + # fish uses a function instead of an env var to generate the prompt. + + # Save the current fish_prompt function as the function _old_fish_prompt. + functions -c fish_prompt _old_fish_prompt + + # With the original prompt function renamed, we can override with our own. + function fish_prompt + # Save the return status of the last command. + set -l old_status $status + + # Output the venv prompt; color taken from the blue of the Python logo. + printf "%s%s%s" (set_color 4B8BBE) "(venv) " (set_color normal) + + # Restore the return status of the previous command. + echo "exit $old_status" | . + # Output the original/"old" prompt. + _old_fish_prompt + end + + set -gx _OLD_FISH_PROMPT_OVERRIDE "$VIRTUAL_ENV" + set -gx VIRTUAL_ENV_PROMPT "(venv) " +end diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/dotenv b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/dotenv new file mode 100755 index 00000000..f561dd30 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/dotenv @@ -0,0 +1,8 @@ +#!/Users/ekanshjindal/IITM/MAD1/quizmaster/venv/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from dotenv.__main__ import cli +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(cli()) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/flask b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/flask new file mode 100755 index 00000000..632e3f4d --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/flask @@ -0,0 +1,8 @@ +#!/Users/ekanshjindal/IITM/MAD1/quizmaster/venv/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from flask.cli import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/gunicorn b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/gunicorn new file mode 100755 index 00000000..13e569c7 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/gunicorn @@ -0,0 +1,8 @@ +#!/Users/ekanshjindal/IITM/MAD1/quizmaster/venv/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from gunicorn.app.wsgiapp import run +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(run()) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip new file mode 100755 index 00000000..eda6faf5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip @@ -0,0 +1,8 @@ +#!/Users/ekanshjindal/IITM/MAD1/quizmaster/venv/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip3 b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip3 new file mode 100755 index 00000000..eda6faf5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip3 @@ -0,0 +1,8 @@ +#!/Users/ekanshjindal/IITM/MAD1/quizmaster/venv/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip3.12 b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip3.12 new file mode 100755 index 00000000..eda6faf5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/pip3.12 @@ -0,0 +1,8 @@ +#!/Users/ekanshjindal/IITM/MAD1/quizmaster/venv/bin/python3.12 +# -*- coding: utf-8 -*- +import re +import sys +from pip._internal.cli.main import main +if __name__ == '__main__': + sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) + sys.exit(main()) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python new file mode 120000 index 00000000..11b9d885 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python @@ -0,0 +1 @@ +python3.12 \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python3 b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python3 new file mode 120000 index 00000000..11b9d885 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python3 @@ -0,0 +1 @@ +python3.12 \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python3.12 b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python3.12 new file mode 120000 index 00000000..a7a5fcca --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/bin/python3.12 @@ -0,0 +1 @@ +/Library/Frameworks/Python.framework/Versions/3.12/bin/python3.12 \ No newline at end of file diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/AUTHORS.md b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/AUTHORS.md new file mode 100644 index 00000000..bbcfb5c5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/AUTHORS.md @@ -0,0 +1,132 @@ +Authors +======= + +A huge thanks to all of our contributors: + +- Adam Chainz +- Alec Nikolas Reiter +- Alex Gaynor +- Alex M +- Alex Morken +- Andrew Dunham +- Andriy Yurchuk +- Anil Kulkarni +- Antonio Dourado +- Antonio Herraiz +- Ares Ou +- Artur Rodrigues +- Axel Haustant +- Belousow Makc +- Benjamin Dopplinger +- Bennett, Bryan +- Bohan Zhang +- Bryan Bennett +- Bulat Bochkariov +- Cameron Brandon White +- Catherine Devlin +- Dan Quirk +- Daniele Esposti +- Dario Bertini +- David Arnold +- David Baumgold +- David Boucha +- David Crawford +- Dimitris Theodorou +- Doug Black +- Evan Dale Aromin +- Eyal Levin +- Francesco Della Vedova +- Frank Stratton +- Garret Raziel +- Gary Belvin +- Gilles Dartiguelongue +- Giorgio Salluzzo +- Guillaume BINET +- Heston Liebowitz +- Hu WQ +- Jacob Magnusson +- James Booth +- James Ogura +- James Turk +- Jeff Widman +- Joakim Ekberg +- Johannes +- Jordan Yelloz +- Josh Friend +- Joshua C. Randall +- Joshua Randall +- José Fernández Ramos +- Juan Rossi +- JuneHyeon Bae +- Kamil Gałuszka +- Kevin Burke +- Kevin Deldycke +- Kevin Funk +- Kyle Conroy +- Lance Ingle +- Lars Holm Nielsen +- Luiz Armesto +- Malthe Borch +- Marek Hlobil +- Matt Wright +- Max Mautner +- Max Peterson +- Maxim +- Michael Hwang +- Michael Newman +- Miguel Grinberg +- Mihai Tomescu +- Neil Halelamien +- Nicolas Harraudeau +- Pavel Tyslyatsky +- Petrus J.v.Rensburg +- Philippe Ndiaye +- Piotr Husiatyński +- Prasanna Swaminathan +- Robert Warner +- Rod Cloutier +- Ryan Horn +- Rémi Alvergnat +- Sam Kimbrel +- Samarth Shah +- Sami Jaktholm +- Sander Sink +- Sasha Baranov +- Saul Diez-Guerra +- Sergey Romanov +- Shreyans Sheth +- Steven Leggett +- Sven-Hendrik Haase +- Usman Ehtesham Gul +- Victor Neo +- Vlad Frolov +- Vladimir Pal +- WooParadog +- Yaniv Aknin +- akash +- bret barker +- hachichaud +- jbouzekri +- jobou +- johnrichter +- justanr +- k-funk +- kelvinhammond +- kenjones +- kieran gorman +- kumy +- lyschoening +- mailto1587 +- mniebla +- mozillazg +- muchosalsa +- nachinius +- nixdata +- papaeye +- pingz +- saml +- siavashg +- silasray +- soasme +- ueg1990 +- y-p diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/INSTALLER b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/LICENSE b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/LICENSE new file mode 100644 index 00000000..3337f908 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/LICENSE @@ -0,0 +1,25 @@ +Copyright (c) 2013, Twilio, Inc. +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +- Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. +- Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. +- Neither the name of the Twilio, Inc. nor the names of its contributors may be + used to endorse or promote products derived from this software without + specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/METADATA b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/METADATA new file mode 100644 index 00000000..4ad3ddb4 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/METADATA @@ -0,0 +1,29 @@ +Metadata-Version: 2.1 +Name: Flask-RESTful +Version: 0.3.10 +Summary: Simple framework for creating REST APIs +Home-page: https://www.github.com/flask-restful/flask-restful/ +Author: Twilio API Team +Author-email: help@twilio.com +License: BSD +Project-URL: Source, https://github.com/flask-restful/flask-restful +Platform: any +Classifier: Framework :: Flask +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.4 +Classifier: Programming Language :: Python :: 3.5 +Classifier: Programming Language :: Python :: 3.6 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: License :: OSI Approved :: BSD License +License-File: LICENSE +License-File: AUTHORS.md +Requires-Dist: aniso8601 (>=0.82) +Requires-Dist: Flask (>=0.8) +Requires-Dist: six (>=1.3.0) +Requires-Dist: pytz +Provides-Extra: docs +Requires-Dist: sphinx ; extra == 'docs' + diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/RECORD b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/RECORD new file mode 100644 index 00000000..f54d1128 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/RECORD @@ -0,0 +1,28 @@ +Flask_RESTful-0.3.10.dist-info/AUTHORS.md,sha256=HBq00z_VgMI2xfwfUobrU16_qamdouMkpNxbR0BzaVg,1992 +Flask_RESTful-0.3.10.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +Flask_RESTful-0.3.10.dist-info/LICENSE,sha256=PFjoO0Jk5okmshAgMix5-RZTC0sFT_EJWpC_CtQcCyM,1485 +Flask_RESTful-0.3.10.dist-info/METADATA,sha256=eTeg3NLzPPlJxKSMhedGPPQvRaQm-9lMafpxwIddLT8,1018 +Flask_RESTful-0.3.10.dist-info/RECORD,, +Flask_RESTful-0.3.10.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +Flask_RESTful-0.3.10.dist-info/WHEEL,sha256=a-zpFRIJzOq5QfuhBzbhiA1eHTzNCJn8OdRvhdNX0Rk,110 +Flask_RESTful-0.3.10.dist-info/top_level.txt,sha256=lNpWPlejgBAtMhCUwz_FTyJH12ul1mBZ-Uv3ZK1HiGg,14 +flask_restful/__init__.py,sha256=KDyCbekXcfGMyV6E7neY6ZJ8b8GdM6eLtJbtRmn_nL8,28624 +flask_restful/__pycache__/__init__.cpython-312.pyc,, +flask_restful/__pycache__/__version__.cpython-312.pyc,, +flask_restful/__pycache__/fields.cpython-312.pyc,, +flask_restful/__pycache__/inputs.cpython-312.pyc,, +flask_restful/__pycache__/reqparse.cpython-312.pyc,, +flask_restful/__version__.py,sha256=JbZfv76t9J7HHmoA2wdjKemYHpQE0jhBfMJIil6HEsg,46 +flask_restful/fields.py,sha256=43GbFejZ3kiOb20A1QuzLXjevfsxMZSbmpOpGtW56vo,13018 +flask_restful/inputs.py,sha256=561w8fjLqBq4I_7yXPHJM567ijWhpuf8d8uZnKzTehA,9118 +flask_restful/representations/__init__.py,sha256=AbpHGcgLb-kRsJGnwFEktk7uzpZOCcBY74-YBdrKVGs,1 +flask_restful/representations/__pycache__/__init__.cpython-312.pyc,, +flask_restful/representations/__pycache__/json.cpython-312.pyc,, +flask_restful/representations/json.py,sha256=swKwnbt7v2ioHfHkqhqbzIu_yrcP0ComlSl49IGFJOo,873 +flask_restful/reqparse.py,sha256=-xZmkyrvDFfGvFFokTtXe4J-2PWnNX4EfKolhkT995E,14681 +flask_restful/utils/__init__.py,sha256=jgedvOLGeTk4Sqox4WHE_vAFLP0T_PrLHO4PXaqFqxw,723 +flask_restful/utils/__pycache__/__init__.cpython-312.pyc,, +flask_restful/utils/__pycache__/cors.cpython-312.pyc,, +flask_restful/utils/__pycache__/crypto.cpython-312.pyc,, +flask_restful/utils/cors.py,sha256=cZiqaHhIn0w66spRoSIdC-jIn4X_b6OlVms5eGF4Ess,2084 +flask_restful/utils/crypto.py,sha256=q3PBvAYMJYybbqqQlKNF_Pqeyo9h3x5jFJuVqtEA5bA,996 diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/REQUESTED b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/WHEEL b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/WHEEL new file mode 100644 index 00000000..f771c29b --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: bdist_wheel (0.40.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/top_level.txt b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/top_level.txt new file mode 100644 index 00000000..f7b85270 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/Flask_RESTful-0.3.10.dist-info/top_level.txt @@ -0,0 +1 @@ +flask_restful diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt new file mode 100644 index 00000000..9d227a0c --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/LICENSE.txt @@ -0,0 +1,28 @@ +Copyright 2010 Pallets + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + +1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/METADATA b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/METADATA new file mode 100644 index 00000000..82261f2a --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/METADATA @@ -0,0 +1,92 @@ +Metadata-Version: 2.1 +Name: MarkupSafe +Version: 3.0.2 +Summary: Safely add untrusted strings to HTML/XML markup. +Maintainer-email: Pallets +License: Copyright 2010 Pallets + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are + met: + + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + 3. Neither the name of the copyright holder nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A + PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED + TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Project-URL: Donate, https://palletsprojects.com/donate +Project-URL: Documentation, https://markupsafe.palletsprojects.com/ +Project-URL: Changes, https://markupsafe.palletsprojects.com/changes/ +Project-URL: Source, https://github.com/pallets/markupsafe/ +Project-URL: Chat, https://discord.gg/pallets +Classifier: Development Status :: 5 - Production/Stable +Classifier: Environment :: Web Environment +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content +Classifier: Topic :: Text Processing :: Markup :: HTML +Classifier: Typing :: Typed +Requires-Python: >=3.9 +Description-Content-Type: text/markdown +License-File: LICENSE.txt + +# MarkupSafe + +MarkupSafe implements a text object that escapes characters so it is +safe to use in HTML and XML. Characters that have special meanings are +replaced so that they display as the actual characters. This mitigates +injection attacks, meaning untrusted user input can safely be displayed +on a page. + + +## Examples + +```pycon +>>> from markupsafe import Markup, escape + +>>> # escape replaces special characters and wraps in Markup +>>> escape("") +Markup('<script>alert(document.cookie);</script>') + +>>> # wrap in Markup to mark text "safe" and prevent escaping +>>> Markup("Hello") +Markup('hello') + +>>> escape(Markup("Hello")) +Markup('hello') + +>>> # Markup is a str subclass +>>> # methods and operators escape their arguments +>>> template = Markup("Hello {name}") +>>> template.format(name='"World"') +Markup('Hello "World"') +``` + +## Donate + +The Pallets organization develops and supports MarkupSafe and other +popular packages. In order to grow the community of contributors and +users, and allow the maintainers to devote more time to the projects, +[please donate today][]. + +[please donate today]: https://palletsprojects.com/donate diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/RECORD b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/RECORD new file mode 100644 index 00000000..8e0da81b --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/RECORD @@ -0,0 +1,15 @@ +MarkupSafe-3.0.2.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +MarkupSafe-3.0.2.dist-info/LICENSE.txt,sha256=SJqOEQhQntmKN7uYPhHg9-HTHwvY-Zp5yESOf_N9B-o,1475 +MarkupSafe-3.0.2.dist-info/METADATA,sha256=aAwbZhSmXdfFuMM-rEHpeiHRkBOGESyVLJIuwzHP-nw,3975 +MarkupSafe-3.0.2.dist-info/RECORD,, +MarkupSafe-3.0.2.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +MarkupSafe-3.0.2.dist-info/WHEEL,sha256=T94HOVPNbYE6jyG6QmiIglWJ01nwJvHIWFgubY8hhjc,109 +MarkupSafe-3.0.2.dist-info/top_level.txt,sha256=qy0Plje5IJuvsCBjejJyhDCjEAdcDLK_2agVcex8Z6U,11 +markupsafe/__init__.py,sha256=sr-U6_27DfaSrj5jnHYxWN-pvhM27sjlDplMDPZKm7k,13214 +markupsafe/__pycache__/__init__.cpython-312.pyc,, +markupsafe/__pycache__/_native.cpython-312.pyc,, +markupsafe/_native.py,sha256=hSLs8Jmz5aqayuengJJ3kdT5PwNpBWpKrmQSdipndC8,210 +markupsafe/_speedups.c,sha256=O7XulmTo-epI6n2FtMVOrJXl8EAaIwD2iNYmBI5SEoQ,4149 +markupsafe/_speedups.cpython-312-darwin.so,sha256=LAX7ul6PQsfO_zZxF631eSzN6U7PoGVTR_Wwd2pGav8,50624 +markupsafe/_speedups.pyi,sha256=ENd1bYe7gbBUf2ywyYWOGUpnXOHNJ-cgTNqetlW8h5k,41 +markupsafe/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/REQUESTED b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL new file mode 100644 index 00000000..edd13a08 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.2.0) +Root-Is-Purelib: false +Tag: cp312-cp312-macosx_11_0_arm64 + diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt new file mode 100644 index 00000000..75bf7292 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/MarkupSafe-3.0.2.dist-info/top_level.txt @@ -0,0 +1 @@ +markupsafe diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/INSTALLER b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/LICENSE b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/LICENSE new file mode 100644 index 00000000..dfe1a4d8 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/LICENSE @@ -0,0 +1,19 @@ +Copyright 2005-2025 SQLAlchemy authors and contributors . + +Permission is hereby granted, free of charge, to any person obtaining a copy of +this software and associated documentation files (the "Software"), to deal in +the Software without restriction, including without limitation the rights to +use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies +of the Software, and to permit persons to whom the Software is furnished to do +so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/METADATA b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/METADATA new file mode 100644 index 00000000..4a21885b --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/METADATA @@ -0,0 +1,243 @@ +Metadata-Version: 2.2 +Name: SQLAlchemy +Version: 2.0.37 +Summary: Database Abstraction Library +Home-page: https://www.sqlalchemy.org +Author: Mike Bayer +Author-email: mike_mp@zzzcomputing.com +License: MIT +Project-URL: Documentation, https://docs.sqlalchemy.org +Project-URL: Issue Tracker, https://github.com/sqlalchemy/sqlalchemy/ +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: MIT License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.7 +Classifier: Programming Language :: Python :: 3.8 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Programming Language :: Python :: Implementation :: CPython +Classifier: Programming Language :: Python :: Implementation :: PyPy +Classifier: Topic :: Database :: Front-Ends +Requires-Python: >=3.7 +Description-Content-Type: text/x-rst +License-File: LICENSE +Requires-Dist: importlib-metadata; python_version < "3.8" +Requires-Dist: greenlet!=0.4.17; python_version < "3.14" and (platform_machine == "aarch64" or (platform_machine == "ppc64le" or (platform_machine == "x86_64" or (platform_machine == "amd64" or (platform_machine == "AMD64" or (platform_machine == "win32" or platform_machine == "WIN32")))))) +Requires-Dist: typing-extensions>=4.6.0 +Provides-Extra: asyncio +Requires-Dist: greenlet!=0.4.17; extra == "asyncio" +Provides-Extra: mypy +Requires-Dist: mypy>=0.910; extra == "mypy" +Provides-Extra: mssql +Requires-Dist: pyodbc; extra == "mssql" +Provides-Extra: mssql-pymssql +Requires-Dist: pymssql; extra == "mssql-pymssql" +Provides-Extra: mssql-pyodbc +Requires-Dist: pyodbc; extra == "mssql-pyodbc" +Provides-Extra: mysql +Requires-Dist: mysqlclient>=1.4.0; extra == "mysql" +Provides-Extra: mysql-connector +Requires-Dist: mysql-connector-python; extra == "mysql-connector" +Provides-Extra: mariadb-connector +Requires-Dist: mariadb!=1.1.10,!=1.1.2,!=1.1.5,>=1.0.1; extra == "mariadb-connector" +Provides-Extra: oracle +Requires-Dist: cx_oracle>=8; extra == "oracle" +Provides-Extra: oracle-oracledb +Requires-Dist: oracledb>=1.0.1; extra == "oracle-oracledb" +Provides-Extra: postgresql +Requires-Dist: psycopg2>=2.7; extra == "postgresql" +Provides-Extra: postgresql-pg8000 +Requires-Dist: pg8000>=1.29.1; extra == "postgresql-pg8000" +Provides-Extra: postgresql-asyncpg +Requires-Dist: greenlet!=0.4.17; extra == "postgresql-asyncpg" +Requires-Dist: asyncpg; extra == "postgresql-asyncpg" +Provides-Extra: postgresql-psycopg2binary +Requires-Dist: psycopg2-binary; extra == "postgresql-psycopg2binary" +Provides-Extra: postgresql-psycopg2cffi +Requires-Dist: psycopg2cffi; extra == "postgresql-psycopg2cffi" +Provides-Extra: postgresql-psycopg +Requires-Dist: psycopg>=3.0.7; extra == "postgresql-psycopg" +Provides-Extra: postgresql-psycopgbinary +Requires-Dist: psycopg[binary]>=3.0.7; extra == "postgresql-psycopgbinary" +Provides-Extra: pymysql +Requires-Dist: pymysql; extra == "pymysql" +Provides-Extra: aiomysql +Requires-Dist: greenlet!=0.4.17; extra == "aiomysql" +Requires-Dist: aiomysql>=0.2.0; extra == "aiomysql" +Provides-Extra: aioodbc +Requires-Dist: greenlet!=0.4.17; extra == "aioodbc" +Requires-Dist: aioodbc; extra == "aioodbc" +Provides-Extra: asyncmy +Requires-Dist: greenlet!=0.4.17; extra == "asyncmy" +Requires-Dist: asyncmy!=0.2.4,!=0.2.6,>=0.2.3; extra == "asyncmy" +Provides-Extra: aiosqlite +Requires-Dist: greenlet!=0.4.17; extra == "aiosqlite" +Requires-Dist: aiosqlite; extra == "aiosqlite" +Requires-Dist: typing_extensions!=3.10.0.1; extra == "aiosqlite" +Provides-Extra: sqlcipher +Requires-Dist: sqlcipher3_binary; extra == "sqlcipher" + +SQLAlchemy +========== + +|PyPI| |Python| |Downloads| + +.. |PyPI| image:: https://img.shields.io/pypi/v/sqlalchemy + :target: https://pypi.org/project/sqlalchemy + :alt: PyPI + +.. |Python| image:: https://img.shields.io/pypi/pyversions/sqlalchemy + :target: https://pypi.org/project/sqlalchemy + :alt: PyPI - Python Version + +.. |Downloads| image:: https://static.pepy.tech/badge/sqlalchemy/month + :target: https://pepy.tech/project/sqlalchemy + :alt: PyPI - Downloads + + +The Python SQL Toolkit and Object Relational Mapper + +Introduction +------------- + +SQLAlchemy is the Python SQL toolkit and Object Relational Mapper +that gives application developers the full power and +flexibility of SQL. SQLAlchemy provides a full suite +of well known enterprise-level persistence patterns, +designed for efficient and high-performing database +access, adapted into a simple and Pythonic domain +language. + +Major SQLAlchemy features include: + +* An industrial strength ORM, built + from the core on the identity map, unit of work, + and data mapper patterns. These patterns + allow transparent persistence of objects + using a declarative configuration system. + Domain models + can be constructed and manipulated naturally, + and changes are synchronized with the + current transaction automatically. +* A relationally-oriented query system, exposing + the full range of SQL's capabilities + explicitly, including joins, subqueries, + correlation, and most everything else, + in terms of the object model. + Writing queries with the ORM uses the same + techniques of relational composition you use + when writing SQL. While you can drop into + literal SQL at any time, it's virtually never + needed. +* A comprehensive and flexible system + of eager loading for related collections and objects. + Collections are cached within a session, + and can be loaded on individual access, all + at once using joins, or by query per collection + across the full result set. +* A Core SQL construction system and DBAPI + interaction layer. The SQLAlchemy Core is + separate from the ORM and is a full database + abstraction layer in its own right, and includes + an extensible Python-based SQL expression + language, schema metadata, connection pooling, + type coercion, and custom types. +* All primary and foreign key constraints are + assumed to be composite and natural. Surrogate + integer primary keys are of course still the + norm, but SQLAlchemy never assumes or hardcodes + to this model. +* Database introspection and generation. Database + schemas can be "reflected" in one step into + Python structures representing database metadata; + those same structures can then generate + CREATE statements right back out - all within + the Core, independent of the ORM. + +SQLAlchemy's philosophy: + +* SQL databases behave less and less like object + collections the more size and performance start to + matter; object collections behave less and less like + tables and rows the more abstraction starts to matter. + SQLAlchemy aims to accommodate both of these + principles. +* An ORM doesn't need to hide the "R". A relational + database provides rich, set-based functionality + that should be fully exposed. SQLAlchemy's + ORM provides an open-ended set of patterns + that allow a developer to construct a custom + mediation layer between a domain model and + a relational schema, turning the so-called + "object relational impedance" issue into + a distant memory. +* The developer, in all cases, makes all decisions + regarding the design, structure, and naming conventions + of both the object model as well as the relational + schema. SQLAlchemy only provides the means + to automate the execution of these decisions. +* With SQLAlchemy, there's no such thing as + "the ORM generated a bad query" - you + retain full control over the structure of + queries, including how joins are organized, + how subqueries and correlation is used, what + columns are requested. Everything SQLAlchemy + does is ultimately the result of a developer-initiated + decision. +* Don't use an ORM if the problem doesn't need one. + SQLAlchemy consists of a Core and separate ORM + component. The Core offers a full SQL expression + language that allows Pythonic construction + of SQL constructs that render directly to SQL + strings for a target database, returning + result sets that are essentially enhanced DBAPI + cursors. +* Transactions should be the norm. With SQLAlchemy's + ORM, nothing goes to permanent storage until + commit() is called. SQLAlchemy encourages applications + to create a consistent means of delineating + the start and end of a series of operations. +* Never render a literal value in a SQL statement. + Bound parameters are used to the greatest degree + possible, allowing query optimizers to cache + query plans effectively and making SQL injection + attacks a non-issue. + +Documentation +------------- + +Latest documentation is at: + +https://www.sqlalchemy.org/docs/ + +Installation / Requirements +--------------------------- + +Full documentation for installation is at +`Installation `_. + +Getting Help / Development / Bug reporting +------------------------------------------ + +Please refer to the `SQLAlchemy Community Guide `_. + +Code of Conduct +--------------- + +Above all, SQLAlchemy places great emphasis on polite, thoughtful, and +constructive communication between users and developers. +Please see our current Code of Conduct at +`Code of Conduct `_. + +License +------- + +SQLAlchemy is distributed under the `MIT license +`_. + diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/RECORD b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/RECORD new file mode 100644 index 00000000..b103ae0e --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/RECORD @@ -0,0 +1,530 @@ +SQLAlchemy-2.0.37.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +SQLAlchemy-2.0.37.dist-info/LICENSE,sha256=mCFyC1jUpWW2EyEAeorUOraZGjlZ5mzV203Z6uacffw,1100 +SQLAlchemy-2.0.37.dist-info/METADATA,sha256=U0OcluY6KNHDIczc03PmWkLp1DGtzongNDkQ6THEvDo,9641 +SQLAlchemy-2.0.37.dist-info/RECORD,, +SQLAlchemy-2.0.37.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +SQLAlchemy-2.0.37.dist-info/WHEEL,sha256=VujM3ypTCyUW6hcTDdK2ej0ARVMxlU1Djlh_zWnDgqk,109 +SQLAlchemy-2.0.37.dist-info/top_level.txt,sha256=rp-ZgB7D8G11ivXON5VGPjupT1voYmWqkciDt5Uaw_Q,11 +sqlalchemy/__init__.py,sha256=m8AoRzqL1l_3uFAeJ_vwtlAfXkboxLKJ3oL1RqFnXbM,13033 +sqlalchemy/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/__pycache__/events.cpython-312.pyc,, +sqlalchemy/__pycache__/exc.cpython-312.pyc,, +sqlalchemy/__pycache__/inspection.cpython-312.pyc,, +sqlalchemy/__pycache__/log.cpython-312.pyc,, +sqlalchemy/__pycache__/schema.cpython-312.pyc,, +sqlalchemy/__pycache__/types.cpython-312.pyc,, +sqlalchemy/connectors/__init__.py,sha256=YeSHsOB0YhdM6jZUvHFQFwKqNXO02MlklmGW0yCywjI,476 +sqlalchemy/connectors/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/connectors/__pycache__/aioodbc.cpython-312.pyc,, +sqlalchemy/connectors/__pycache__/asyncio.cpython-312.pyc,, +sqlalchemy/connectors/__pycache__/pyodbc.cpython-312.pyc,, +sqlalchemy/connectors/aioodbc.py,sha256=KT9xi2xQ4AJgDiGPTV5h_5qi9dummmenKAvWelwza3w,5288 +sqlalchemy/connectors/asyncio.py,sha256=00claZADdFUh2iQmlpqoLhLTBxK0i79Mwd9WZqUtleM,6138 +sqlalchemy/connectors/pyodbc.py,sha256=GsW9bD0H30OMTbGDx9SdaTT_ujgpxP7TM4rfhIzD4mo,8501 +sqlalchemy/cyextension/__init__.py,sha256=4npVIjitKfUs0NQ6f3UdQBDq4ipJ0_ZNB2mpKqtc5ik,244 +sqlalchemy/cyextension/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/cyextension/collections.cpython-312-darwin.so,sha256=AUXZEG2TY_w3DMw8RA2WSWZn2x_6IklKaeFcRwIiQA4,247360 +sqlalchemy/cyextension/collections.pyx,sha256=L7DZ3DGKpgw2MT2ZZRRxCnrcyE5pU1NAFowWgAzQPEc,12571 +sqlalchemy/cyextension/immutabledict.cpython-312-darwin.so,sha256=oqPbXAJAZtkweKQPRwVKtTI8rZd29x5Abkir-w5oNGM,122336 +sqlalchemy/cyextension/immutabledict.pxd,sha256=3x3-rXG5eRQ7bBnktZ-OJ9-6ft8zToPmTDOd92iXpB0,291 +sqlalchemy/cyextension/immutabledict.pyx,sha256=KfDTYbTfebstE8xuqAtuXsHNAK0_b5q_ymUiinUe_xs,3535 +sqlalchemy/cyextension/processors.cpython-312-darwin.so,sha256=wDjerlhJKxI2jQ3F_cM8XFcCinXyo1iPtEylBvO2uVQ,102928 +sqlalchemy/cyextension/processors.pyx,sha256=R1rHsGLEaGeBq5VeCydjClzYlivERIJ9B-XLOJlf2MQ,1792 +sqlalchemy/cyextension/resultproxy.cpython-312-darwin.so,sha256=VVnne-Rcw-CFq1POMU9YRbavz4cPM9nB2NEohXIAFdA,104864 +sqlalchemy/cyextension/resultproxy.pyx,sha256=eWLdyBXiBy_CLQrF5ScfWJm7X0NeelscSXedtj1zv9Q,2725 +sqlalchemy/cyextension/util.cpython-312-darwin.so,sha256=2iZ-_NtEVw9E656_SGOS4MUDiXdKqEMXeIgfT-9mtcg,122296 +sqlalchemy/cyextension/util.pyx,sha256=B85orxa9LddLuQEaDoVSq1XmAXIbLKxrxpvuB8ogV_o,2530 +sqlalchemy/dialects/__init__.py,sha256=4jxiSgI_fVCNXcz42gQYKEp0k07RAHyQN4ZpjaNsFUI,1770 +sqlalchemy/dialects/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/dialects/__pycache__/_typing.cpython-312.pyc,, +sqlalchemy/dialects/_typing.py,sha256=8YwrkOa8IvmBojwwegbL5mL_0UAuzdqYiKHKANpvHMw,971 +sqlalchemy/dialects/mssql/__init__.py,sha256=6t_aNpgbMLdPE9gpHYTf9o6QfVavncztRLbr21l2NaY,1880 +sqlalchemy/dialects/mssql/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/aioodbc.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/base.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/information_schema.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/json.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/provision.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/pymssql.cpython-312.pyc,, +sqlalchemy/dialects/mssql/__pycache__/pyodbc.cpython-312.pyc,, +sqlalchemy/dialects/mssql/aioodbc.py,sha256=4CmhwIkZrabpG-r7_ogRVajD-nhRZSFJ0Swz2d0jIHM,2021 +sqlalchemy/dialects/mssql/base.py,sha256=2UCotpN3WBPgMddhXVP6Epc-srvNrYHCnK4kcEbjW6w,132713 +sqlalchemy/dialects/mssql/information_schema.py,sha256=v5MZz1FN72THEwF_u3Eh_2vnWdFE13RYydOioMMcuvU,8084 +sqlalchemy/dialects/mssql/json.py,sha256=F53pibuOVRzgDtjoclOI7LnkKXNVsaVfJyBH1XAhyDo,4756 +sqlalchemy/dialects/mssql/provision.py,sha256=P1tqxZ4f6Oeqn2gNi7dXl82LRLCg1-OB4eWiZc6CHek,5593 +sqlalchemy/dialects/mssql/pymssql.py,sha256=C7yAs3Pw81W1KTVNc6_0sHQuYlJ5iH82vKByY4TkB1g,4097 +sqlalchemy/dialects/mssql/pyodbc.py,sha256=CnO7KDWxbxb7AoZhp_PMDBvVSMuzwq1h4Cav2IWFWDo,27173 +sqlalchemy/dialects/mysql/__init__.py,sha256=ropOMUWrAcL-Q7h-9jQ_tb3ISAFIsNRQ8YVXvn0URl0,2206 +sqlalchemy/dialects/mysql/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/aiomysql.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/asyncmy.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/base.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/cymysql.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/dml.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/enumerated.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/expression.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/json.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/mariadb.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/mariadbconnector.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/mysqlconnector.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/mysqldb.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/provision.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/pymysql.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/pyodbc.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/reflection.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/reserved_words.cpython-312.pyc,, +sqlalchemy/dialects/mysql/__pycache__/types.cpython-312.pyc,, +sqlalchemy/dialects/mysql/aiomysql.py,sha256=yrujoFtAG0QvtVlgbGBUMg3kXeXlIH62tvyYTCMUfnE,10013 +sqlalchemy/dialects/mysql/asyncmy.py,sha256=rmVSf86VYxgAUROIKfVtvS-grG9aPBiLY_Gu0KJMjuo,10081 +sqlalchemy/dialects/mysql/base.py,sha256=LkGJ6G1U2xygOawOtQYBfTipGh8MuiE1kNxaD7S9UIY,123432 +sqlalchemy/dialects/mysql/cymysql.py,sha256=KwxSsF4a6uUd6yblhSns8uj4hgmhv4hFInTZNdmRixA,2300 +sqlalchemy/dialects/mysql/dml.py,sha256=VjnTobe_SBNF2RN6tvqa5LOn-9x4teVUyzUedZkOmdc,7768 +sqlalchemy/dialects/mysql/enumerated.py,sha256=qI5gnBYhxk9dhPeUfGiijp0qT2Puazdp27-ba_38uWQ,8447 +sqlalchemy/dialects/mysql/expression.py,sha256=3PEKPwYIZ8mVXkjUgHaj_efPBYuBNWZSnfUcJuoZddA,4121 +sqlalchemy/dialects/mysql/json.py,sha256=W31DojiRypifXKVh3PJSWP7IHqFoeKwzLl-0CJH6QRI,2269 +sqlalchemy/dialects/mysql/mariadb.py,sha256=g4v4WQuXHn556Nn6k-RgvPrmfCql1R46fIEk6UEx0U8,1450 +sqlalchemy/dialects/mysql/mariadbconnector.py,sha256=t4m6kfYBoURjNXRxlEsRajjvArNDc4lmaFGxHQh7VTo,8623 +sqlalchemy/dialects/mysql/mysqlconnector.py,sha256=gdNOGdRqvnCbLZpKjpubu_0tGRQ5Tn_2TZvbp3v9rX0,5729 +sqlalchemy/dialects/mysql/mysqldb.py,sha256=5ME7B0WI9G8tw5482YBejDg38uVMXR2oUasNDOCsAqQ,9526 +sqlalchemy/dialects/mysql/provision.py,sha256=5LCeInPvyEbGuzxSs9rnnLYkMsFpW3IJ8lC-sjTfKnk,3575 +sqlalchemy/dialects/mysql/pymysql.py,sha256=osp0em1s3Cip5Vpcj-PeaH7btHEInorO-qs351muw3Q,4082 +sqlalchemy/dialects/mysql/pyodbc.py,sha256=ZiFNJQq2qiOTzTZLmNJQ938EnS1ItVsNDa3fvNEDqnI,4298 +sqlalchemy/dialects/mysql/reflection.py,sha256=eGV9taua0nZS_HsHyAy6zjcHEHFPXmFdux-bUmtOeWs,22834 +sqlalchemy/dialects/mysql/reserved_words.py,sha256=C9npWSuhsxoVCqETxCQ1zE_UEgy4gfiHw9zI5dPkjWI,9258 +sqlalchemy/dialects/mysql/types.py,sha256=w68OASMw04xkyAc0_GtXkuEhhVqlR6LTwaOch4KaAFQ,24343 +sqlalchemy/dialects/oracle/__init__.py,sha256=rp9qPRNQAk1Yq_Zhe7SsUH8EvFgNOAh8XOF17Lkxpyo,1493 +sqlalchemy/dialects/oracle/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/dialects/oracle/__pycache__/base.cpython-312.pyc,, +sqlalchemy/dialects/oracle/__pycache__/cx_oracle.cpython-312.pyc,, +sqlalchemy/dialects/oracle/__pycache__/dictionary.cpython-312.pyc,, +sqlalchemy/dialects/oracle/__pycache__/oracledb.cpython-312.pyc,, +sqlalchemy/dialects/oracle/__pycache__/provision.cpython-312.pyc,, +sqlalchemy/dialects/oracle/__pycache__/types.cpython-312.pyc,, +sqlalchemy/dialects/oracle/base.py,sha256=_JF4OwXmXjAsXj8wXq2m8M2vtMjoxdlOwg1hfcgn3bc,123096 +sqlalchemy/dialects/oracle/cx_oracle.py,sha256=ohENTgLxGUfobRH3K8KdeZgBRPG1rX3vY-ph9blj-2g,56612 +sqlalchemy/dialects/oracle/dictionary.py,sha256=J7tGVE0KyUPZKpPLOary3HdDq1DWd29arF5udLgv8_o,19519 +sqlalchemy/dialects/oracle/oracledb.py,sha256=veqto1AUIbSxRmpUQin0ysMV8Y6sWAkzXt7W8IIl118,33771 +sqlalchemy/dialects/oracle/provision.py,sha256=ga1gNQZlXZKk7DYuYegllUejJxZXRKDGa7dbi_S_poc,8313 +sqlalchemy/dialects/oracle/types.py,sha256=axN6Yidx9tGRIUAbDpBrhMWXE-C8jSllFpTghpGOOzU,9058 +sqlalchemy/dialects/postgresql/__init__.py,sha256=kD8W-SV5e2CesvWg2MQAtncXuZFwGPfR_UODvmRXE08,3892 +sqlalchemy/dialects/postgresql/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/_psycopg_common.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/array.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/asyncpg.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/base.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/dml.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/ext.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/hstore.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/json.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/named_types.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/operators.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/pg8000.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/pg_catalog.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/provision.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/psycopg.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/psycopg2.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/psycopg2cffi.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/ranges.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/__pycache__/types.cpython-312.pyc,, +sqlalchemy/dialects/postgresql/_psycopg_common.py,sha256=szME-lCjVwqnW9-USA6e8ke8N_bN3IbqnIm_oZruvqc,5696 +sqlalchemy/dialects/postgresql/array.py,sha256=28kndSQwgvNWlO4z6MUh5WYAtNSgkgBa6qSEQCIflks,13856 +sqlalchemy/dialects/postgresql/asyncpg.py,sha256=ysIDXcGT3OG2lu0YdiIn-_pzfL0uDe-tmHs70fOWVVE,41283 +sqlalchemy/dialects/postgresql/base.py,sha256=otAswEHqeRhbN9_AGMxnwDo6r872ECkiJ5FMetXfS0k,179452 +sqlalchemy/dialects/postgresql/dml.py,sha256=2SmyMeYveAgm7OnT_CJvwad2nh8BP37yT6gFs8dBYN8,12126 +sqlalchemy/dialects/postgresql/ext.py,sha256=MtN4IU5sRYvoY-E8PTltJ1CuIGb-aCwY2pHMPJcTboA,16318 +sqlalchemy/dialects/postgresql/hstore.py,sha256=wR4gmvfQWPssHwYTXEsPJTb4LkBS6x4e4XXE6smtDH4,11934 +sqlalchemy/dialects/postgresql/json.py,sha256=9sHFGTRFyNbLsANrVYookw9NOJwIPTsEBRNIOUOzOGw,11612 +sqlalchemy/dialects/postgresql/named_types.py,sha256=TEWaBCjuHM2WJoQNrQErQ6f_bUkWypGJfW71wzVJXWc,17572 +sqlalchemy/dialects/postgresql/operators.py,sha256=ay3ckNsWtqDjxDseTdKMGGqYVzST6lmfhbbYHG_bxCw,2808 +sqlalchemy/dialects/postgresql/pg8000.py,sha256=RAykzZuO3Anr6AsyK2JYr7CPb2pru6WtkrX2phCyCGU,18638 +sqlalchemy/dialects/postgresql/pg_catalog.py,sha256=lgJMn7aDuJI2XeHddLkge5NFy6oB2-aDSn8A47QpwAU,9254 +sqlalchemy/dialects/postgresql/provision.py,sha256=7pg9-nOnaK5XBzqByXNPuvi3rxtnRa3dJxdSPVq4eeA,5770 +sqlalchemy/dialects/postgresql/psycopg.py,sha256=k7zXsJj35aOXCrhsbMxwTQX5JWegrqirFJ1Hgbq-GjQ,23326 +sqlalchemy/dialects/postgresql/psycopg2.py,sha256=1KXw9RzsQEAXJazCBywdP5CwLu-HsCSDAD_Khc_rPTM,32032 +sqlalchemy/dialects/postgresql/psycopg2cffi.py,sha256=nKilJfvO9mJwk5NRw5iZDekKY5vi379tvdUJ2vn5eyQ,1756 +sqlalchemy/dialects/postgresql/ranges.py,sha256=fnaj4YgCQGO-G_S4k5ea8bYMH7SzggKJdUX5qfaNp4Y,32978 +sqlalchemy/dialects/postgresql/types.py,sha256=sjb-m-h49lbLBFh0P30G8BWgf_aKNiNyVwWEktugwRw,7286 +sqlalchemy/dialects/sqlite/__init__.py,sha256=6Xcz3nPsl8lqCcZ4-VzPRmkMrkKgAp2buKsClZelU7c,1182 +sqlalchemy/dialects/sqlite/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/aiosqlite.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/base.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/dml.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/json.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/provision.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/pysqlcipher.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/__pycache__/pysqlite.cpython-312.pyc,, +sqlalchemy/dialects/sqlite/aiosqlite.py,sha256=FWS-Nn2jnpITQKGd4xOZCYEW-l1C_erQ3IdDJC855t8,12348 +sqlalchemy/dialects/sqlite/base.py,sha256=PvwPzukomHAkufUzSqgfJcbKC2ZJAkJbVnW2BQB2T58,98271 +sqlalchemy/dialects/sqlite/dml.py,sha256=4N8qh06RuMphLoQgWw7wv5nXIrka57jIFvK2x9xTZqg,9138 +sqlalchemy/dialects/sqlite/json.py,sha256=A62xPyLRZxl2hvgTMM92jd_7jlw9UE_4Y6Udqt-8g04,2777 +sqlalchemy/dialects/sqlite/provision.py,sha256=iLJyeQSy8pfr9lwEu4_d4O_CI4OavAtkNeRi3qqys1U,5632 +sqlalchemy/dialects/sqlite/pysqlcipher.py,sha256=di8rYryfL0KAn3pRGepmunHyIRGy-4Hhr-2q_ehPzss,5371 +sqlalchemy/dialects/sqlite/pysqlite.py,sha256=rg7F1S2UOhUu6Y1xNVaqF8VbA-FsRY_Y_XpGTpkKpGs,28087 +sqlalchemy/dialects/type_migration_guidelines.txt,sha256=-uHNdmYFGB7bzUNT6i8M5nb4j6j9YUKAtW4lcBZqsMg,8239 +sqlalchemy/engine/__init__.py,sha256=EF4haWCPu95WtWx1GzcHRJ_bBmtJMznno3I2TQ-ZIHE,2818 +sqlalchemy/engine/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/_py_processors.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/_py_row.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/_py_util.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/base.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/characteristics.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/create.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/cursor.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/default.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/events.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/interfaces.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/mock.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/processors.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/reflection.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/result.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/row.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/strategies.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/url.cpython-312.pyc,, +sqlalchemy/engine/__pycache__/util.cpython-312.pyc,, +sqlalchemy/engine/_py_processors.py,sha256=7QxgkVOd5h1Qd22qFh-pPZdM7RBRzNjj8lWAMWrilcI,3744 +sqlalchemy/engine/_py_row.py,sha256=yNdrZe36yw6mO7x0OEbG0dGojH7CQkNReIwn9LMUPUs,3787 +sqlalchemy/engine/_py_util.py,sha256=LdpbNRQIrJo3EkmiwNkM5bxGUf4uWuL5uS_u-zHadWc,2484 +sqlalchemy/engine/base.py,sha256=9kCWrDp3ECOlQ7BHK_efYAILo3-emcPSk4F8AFRgN7E,122901 +sqlalchemy/engine/characteristics.py,sha256=PepmGApo1sL01dS1qtSbmHplu9ZCdtuSegiGI7L7NZY,4765 +sqlalchemy/engine/create.py,sha256=4gFkqV7fgJbI1906DC4zDgFFX1-xJQ96GIHIrQuc-w4,33217 +sqlalchemy/engine/cursor.py,sha256=6KIZqlwWMUMv02w_el4uNYFMYcfc7eWbkAxW27UyDLE,76305 +sqlalchemy/engine/default.py,sha256=SHM6boxcDNk7MW_Eyd0zCb557Eqf8KTdX1iTUbS0DLw,84705 +sqlalchemy/engine/events.py,sha256=4_e6Ip32ar2Eb27R4ipamiKC-7Tpg4lVz3txabhT5Rc,37400 +sqlalchemy/engine/interfaces.py,sha256=fGmcrBt8yT78ty0R3e3XUvsPh7XYDU_b1JW3QhK_MwY,113029 +sqlalchemy/engine/mock.py,sha256=_aXG1xzj_TO5UWdz8IthPj1ZJ8IlhsKw6D9mmFN_frQ,4181 +sqlalchemy/engine/processors.py,sha256=XK32bULBkuVVRa703u4-SrTCDi_a18Dxq1M09QFBEPw,2379 +sqlalchemy/engine/reflection.py,sha256=_v9zCy3h28hN4KKIUTc5_7KJv7argSgi8A011b_iCdc,75383 +sqlalchemy/engine/result.py,sha256=rgny4qFLmpj80GSdFK35Dpgc3Qk2tc3eJPpahGWVR-M,77622 +sqlalchemy/engine/row.py,sha256=BPtAwsceiRxB9ANpDNM24uQ1M_Zs0xFkSXoKR_I8xyY,12031 +sqlalchemy/engine/strategies.py,sha256=-0rieXY-iXgV83OrJZr-wozFFQn3amKKHchQ6kL-r7A,442 +sqlalchemy/engine/url.py,sha256=gaEeSEJCD0nVEb8J02rIMASrd5L2wYdq5ZXJaj7szVI,31069 +sqlalchemy/engine/util.py,sha256=4OmXwFlmnq6_vBlfUBHnz5LrI_8bT3TwgynX4wcJfnw,5682 +sqlalchemy/event/__init__.py,sha256=ZjVxFGbt9neH5AC4GFiUN5IG2O4j6Z9v2LdmyagJi9w,997 +sqlalchemy/event/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/event/__pycache__/api.cpython-312.pyc,, +sqlalchemy/event/__pycache__/attr.cpython-312.pyc,, +sqlalchemy/event/__pycache__/base.cpython-312.pyc,, +sqlalchemy/event/__pycache__/legacy.cpython-312.pyc,, +sqlalchemy/event/__pycache__/registry.cpython-312.pyc,, +sqlalchemy/event/api.py,sha256=NetgcQfbURaZzoxus7_801YDG_LJ7PYqaC3T1lws114,8111 +sqlalchemy/event/attr.py,sha256=YhPXVBPj63Cfyn0nS6h8Ljq0SEbD3mtAZn9HYlzGbtw,20751 +sqlalchemy/event/base.py,sha256=OevVb82IrUoVgFRrjH4b5GquS5pjFHOgzWAxPwwTKMY,15127 +sqlalchemy/event/legacy.py,sha256=lGafKAOF6PY8Bz0AqhN9Q6n-lpXqFLwdv-0T6-UBpow,8227 +sqlalchemy/event/registry.py,sha256=MNEMyR8HZhzQFgxk4Jk_Em6nXTihmGXiSIwPdUnalPM,11144 +sqlalchemy/events.py,sha256=VBRvtckn9JS3tfUfi6UstqUrvQ15J2xamcDByFysIrI,525 +sqlalchemy/exc.py,sha256=AjFBCrOl_V4vQdGegn72Y951RSRMPL6T5qjxnFTGFbM,23978 +sqlalchemy/ext/__init__.py,sha256=BkTNuOg454MpCY9QA3FLK8td7KQhD1W74fOEXxnWibE,322 +sqlalchemy/ext/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/associationproxy.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/automap.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/baked.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/compiler.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/horizontal_shard.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/hybrid.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/indexable.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/instrumentation.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/mutable.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/orderinglist.cpython-312.pyc,, +sqlalchemy/ext/__pycache__/serializer.cpython-312.pyc,, +sqlalchemy/ext/associationproxy.py,sha256=VhOFB1vB8hmDYQP90_VdpPI9IFzP3NENkG_eDKziVoI,66062 +sqlalchemy/ext/asyncio/__init__.py,sha256=kTIfpwsHWhqZ-VMOBZFBq66kt1XeF0hNuwOToEDe4_Y,1317 +sqlalchemy/ext/asyncio/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/ext/asyncio/__pycache__/base.cpython-312.pyc,, +sqlalchemy/ext/asyncio/__pycache__/engine.cpython-312.pyc,, +sqlalchemy/ext/asyncio/__pycache__/exc.cpython-312.pyc,, +sqlalchemy/ext/asyncio/__pycache__/result.cpython-312.pyc,, +sqlalchemy/ext/asyncio/__pycache__/scoping.cpython-312.pyc,, +sqlalchemy/ext/asyncio/__pycache__/session.cpython-312.pyc,, +sqlalchemy/ext/asyncio/base.py,sha256=2YQ-nKaHbAm--7q6vbxbznzdwT8oPwetwAarKyu2O8E,8930 +sqlalchemy/ext/asyncio/engine.py,sha256=fe_RZrO-5DiiEgMZ3g-Lti-fdaR7z_Q8gDfPUf-30EY,48198 +sqlalchemy/ext/asyncio/exc.py,sha256=npijuILDXH2p4Q5RzhHzutKwZ5CjtqTcP-U0h9TZUmk,639 +sqlalchemy/ext/asyncio/result.py,sha256=zhhXe13vMT7OfdfGXapgtn4crtiqqctRLb3ka4mmGXY,30477 +sqlalchemy/ext/asyncio/scoping.py,sha256=4f7MX3zUd-4rA8A5wd7j0_GlqCSUxdOPfYd7BBIxkJI,52587 +sqlalchemy/ext/asyncio/session.py,sha256=2wxu06UtJGyf-be2edMFkcK4eLMh8xuGmsAlGRj0YPM,63166 +sqlalchemy/ext/automap.py,sha256=n88mktqvExwjqfsDu3yLIA4wbOIWUpQ1S35Uw3X6ffQ,61675 +sqlalchemy/ext/baked.py,sha256=w3SeRoqnPkIhPL2nRAxfVhyir2ypsiW4kmtmUGKs8qo,17753 +sqlalchemy/ext/compiler.py,sha256=f7o4qhUUldpsx4F1sQoUvdVaT2BhiemqNBCF4r_uQUo,20889 +sqlalchemy/ext/declarative/__init__.py,sha256=SuVflXOGDxx2sB2QSTqNEvqS0fyhOkh3-sy2lRsSOLA,1818 +sqlalchemy/ext/declarative/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/ext/declarative/__pycache__/extensions.cpython-312.pyc,, +sqlalchemy/ext/declarative/extensions.py,sha256=yHUPcztU-5E1JrNyELDFWKchAnaYK6Y9-dLcqyc1nUI,19531 +sqlalchemy/ext/horizontal_shard.py,sha256=vouIehpQAuwT0HXyWyynTL3m_gcBuLcB-X8lDB0uQ8U,16691 +sqlalchemy/ext/hybrid.py,sha256=DkvNGtiQYzlEBvs1rYEDXhM8vJEXXh_6DMigsHH9w4k,52531 +sqlalchemy/ext/indexable.py,sha256=_dTOgCS96jURcQd9L-hnUMIJDe9KUMyd9gfH57vs078,11065 +sqlalchemy/ext/instrumentation.py,sha256=iCp89rvfK7buW0jJyzKTBDKyMsd06oTRJDItOk4OVSw,15707 +sqlalchemy/ext/mutable.py,sha256=7Zyh2kQq2gm3J_JwsddinIXk7qUuKWbPzRZOmTultEk,37560 +sqlalchemy/ext/mypy/__init__.py,sha256=yVNtoBDNeTl1sqRoA_fSY3o1g6M8NxqUVvAHPRLmFTw,241 +sqlalchemy/ext/mypy/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/ext/mypy/__pycache__/apply.cpython-312.pyc,, +sqlalchemy/ext/mypy/__pycache__/decl_class.cpython-312.pyc,, +sqlalchemy/ext/mypy/__pycache__/infer.cpython-312.pyc,, +sqlalchemy/ext/mypy/__pycache__/names.cpython-312.pyc,, +sqlalchemy/ext/mypy/__pycache__/plugin.cpython-312.pyc,, +sqlalchemy/ext/mypy/__pycache__/util.cpython-312.pyc,, +sqlalchemy/ext/mypy/apply.py,sha256=v_Svc1WiBz9yBXqBVBKoCuPGN286TfVmuuCVZPlbyzo,10591 +sqlalchemy/ext/mypy/decl_class.py,sha256=Nuca4ofHkASAkdqEQlULYB7iLm_KID7Mp384seDhVGg,17384 +sqlalchemy/ext/mypy/infer.py,sha256=29vgn22Hi8E8oIZL6UJCBl6oipiPSAQjxccCEkVb410,19367 +sqlalchemy/ext/mypy/names.py,sha256=hn889DD1nlF0f3drsKi5KSGTG-JefJ2UJrrIQ4L8QWA,10479 +sqlalchemy/ext/mypy/plugin.py,sha256=9YHBp0Bwo92DbDZIUWwIr0hwXPcE4XvHs0-xshvSwUw,9750 +sqlalchemy/ext/mypy/util.py,sha256=CuW2fJ-g9YtkjcypzmrPRaFc-rAvQTzW5A2-w5VTANg,9960 +sqlalchemy/ext/orderinglist.py,sha256=MROa19cm4RZkWXuUuqc1029r7g4HrAJRc17fTHeThvI,14431 +sqlalchemy/ext/serializer.py,sha256=_z95wZMTn3G3sCGN52gwzD4CuKjrhGMr5Eu8g9MxQNg,6169 +sqlalchemy/future/__init__.py,sha256=R1h8VBwMiIUdP3QHv_tFNby557425FJOAGhUoXGvCmc,512 +sqlalchemy/future/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/future/__pycache__/engine.cpython-312.pyc,, +sqlalchemy/future/engine.py,sha256=2nJFBQAXAE8pqe1cs-D3JjC6wUX2ya2h2e_tniuaBq0,495 +sqlalchemy/inspection.py,sha256=qKEKG37N1OjxpQeVzob1q9VwWjBbjI1x0movJG7fYJ4,5063 +sqlalchemy/log.py,sha256=e_ztNUfZM08FmTWeXN9-doD5YKW44nXxgKCUxxNs6Ow,8607 +sqlalchemy/orm/__init__.py,sha256=BICvTXpLaTNe2AiUaxnZHWzjL5miT9fd_IU-ip3OFNk,8463 +sqlalchemy/orm/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/_orm_constructors.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/_typing.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/attributes.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/base.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/bulk_persistence.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/clsregistry.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/collections.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/context.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/decl_api.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/decl_base.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/dependency.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/descriptor_props.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/dynamic.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/evaluator.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/events.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/exc.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/identity.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/instrumentation.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/interfaces.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/loading.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/mapped_collection.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/mapper.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/path_registry.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/persistence.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/properties.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/query.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/relationships.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/scoping.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/session.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/state.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/state_changes.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/strategies.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/strategy_options.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/sync.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/unitofwork.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/util.cpython-312.pyc,, +sqlalchemy/orm/__pycache__/writeonly.cpython-312.pyc,, +sqlalchemy/orm/_orm_constructors.py,sha256=NiAagQ1060QYS9n5y_gzPvHQQz44EN1dVtamGVtde6E,103626 +sqlalchemy/orm/_typing.py,sha256=vaYRl4_K3n-sjc9u0Rb4eWWpBOoOi92--OHqaGogRvA,4973 +sqlalchemy/orm/attributes.py,sha256=e_U0A4TGWAzL3yXVvk9YVhIRjKM4RTsIE2PNRLn8LbU,92534 +sqlalchemy/orm/base.py,sha256=oCgscNoRrqHwYvc1Iz8ZFhoVXhalu45g9z0m_7_ldaE,27502 +sqlalchemy/orm/bulk_persistence.py,sha256=Ciea9MhJ6ZbAi-uGy5-Kj6lodO9bfRqPq8GSf2qFshE,72663 +sqlalchemy/orm/clsregistry.py,sha256=syn6bB-Ylx-juh5GDCmNrPZ58C-z6sdwRkbZFeKysQU,17974 +sqlalchemy/orm/collections.py,sha256=XxZC8d9UX9E2R-WlNH198OPWRPmpLuYt0Y26LrdbuHc,52252 +sqlalchemy/orm/context.py,sha256=eyh7xTo3SyxIHl8_NBUqJ_GpJ0kZtmnTt32Z67cfqgs,112973 +sqlalchemy/orm/decl_api.py,sha256=SJ25fQjjKyWZDQbq5S69eiybpOzns0LkRziP10iW5-E,64969 +sqlalchemy/orm/decl_base.py,sha256=ZlZmyNVOsCPA_pThMeXuWmAhlJwlvTxdGXhnARsKxhk,83288 +sqlalchemy/orm/dependency.py,sha256=4NMhoogevOiX1Wm5B1_yY2u9MHYlIjJNNoEVRE0yLwA,47631 +sqlalchemy/orm/descriptor_props.py,sha256=LgfdiO_U5uznq5ImenfbWGV5T47bH4b_ztbzB4B7FsU,37231 +sqlalchemy/orm/dynamic.py,sha256=Z4GpcVL8rM8gi0bytQOZXw-_kKi-sExbRWGjU30dK3g,9816 +sqlalchemy/orm/evaluator.py,sha256=PKrUW1zEOvmv1XEgc_hBdYqNcyk4zjWr_rJhCEQBFIc,12353 +sqlalchemy/orm/events.py,sha256=OZtTCpI-DVaE6CY16e42GUVpci1U1GjdNO76xU-Tj5Y,127781 +sqlalchemy/orm/exc.py,sha256=zJgAIofYsWKjktqO5MFxze95GlJASziEOJJx-P5_wOU,7413 +sqlalchemy/orm/identity.py,sha256=5NFtF9ZPZWAOmtOqCPyVX2-_pQq9A5XeN2ns3Wirpv8,9249 +sqlalchemy/orm/instrumentation.py,sha256=WhElvvOWOn3Fuc-Asc5HmcKDX6EzFtBleLJKPZEc5A0,24321 +sqlalchemy/orm/interfaces.py,sha256=W6ADDLOixmm4tnSnUP_I9HFLj9MCO2bODk_WTNjkZGA,48797 +sqlalchemy/orm/loading.py,sha256=6Rd1hWtBPm7SfCUpjPQrcoUg_DSCcfhO8Qhz7SScjRE,58277 +sqlalchemy/orm/mapped_collection.py,sha256=FAqaTlOUCYqdws2KR_fW0T8mMWIrLuAxJGU5f4W1aGs,19682 +sqlalchemy/orm/mapper.py,sha256=-gkJKHeAJmIFT153WFIIySduyyLGbT5plCgSfnsa0I0,171668 +sqlalchemy/orm/path_registry.py,sha256=-aAEhGkDf_2ZUXmHQICQNOa4Z5xhTlhlYLag7eoVpxE,25920 +sqlalchemy/orm/persistence.py,sha256=Uz45Cwxi7FnNiSk2crbh3TzV7b9kb85vmcvOwy5NVmw,61701 +sqlalchemy/orm/properties.py,sha256=vbx_YiSjj3tI94-G-_ghbyWYcIIJQQeGG1P-0RC8Jv4,29065 +sqlalchemy/orm/query.py,sha256=GI_go9ErXYK1BteCmIh5E9iv-jfMJkRBVIlw0XmnYyk,118540 +sqlalchemy/orm/relationships.py,sha256=C40n_-oliMgJJ0FHfwsi1-dm963CrYeKJ5HEYjLdg_o,128899 +sqlalchemy/orm/scoping.py,sha256=-SNRAewfMJ4x4Um8X-yv0k1Thz8E1_kCBmbmG1l1auo,78617 +sqlalchemy/orm/session.py,sha256=1fzksIcb9DtKcwqkS1KkZngkrEYGUHmoNW_o6l8IXQ4,196114 +sqlalchemy/orm/state.py,sha256=1vtlz674sGFmwZ8Ih9TdrslA-0nhU2G52WgV-FoG2j0,37670 +sqlalchemy/orm/state_changes.py,sha256=XJLYYhTZu7nA6uD7xupbLZ9XSzqLYwrDJgW0ZAWvVGE,6815 +sqlalchemy/orm/strategies.py,sha256=qziXv4z2bJeF2qFSj6wogc9BLlxuOnT8nOcEvocVf88,119866 +sqlalchemy/orm/strategy_options.py,sha256=wMYd4E_nRb5ei8Fr3jWeSewNY2k1-AfqtYRGOLiHOFA,85043 +sqlalchemy/orm/sync.py,sha256=RdoxnhvgNjn3Lhtoq4QjvXpj8qfOz__wyibh0FMON0A,5779 +sqlalchemy/orm/unitofwork.py,sha256=hkSIcVonoSt0WWHk019bCDEw0g2o2fg4m4yqoTGyAoo,27033 +sqlalchemy/orm/util.py,sha256=rtClCjtg0eSSC8k-L30W0v6BauJaJuh9Nf-MSqofWuQ,80831 +sqlalchemy/orm/writeonly.py,sha256=R-MVxYDw0ZQ795H21yBtgGSZXWUzSovcb_SO1mv5hoI,22305 +sqlalchemy/pool/__init__.py,sha256=niqzCv2uOZT07DOiV2inlmjrW3lZyqDXGCjnOl1IqJ4,1804 +sqlalchemy/pool/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/pool/__pycache__/base.cpython-312.pyc,, +sqlalchemy/pool/__pycache__/events.cpython-312.pyc,, +sqlalchemy/pool/__pycache__/impl.cpython-312.pyc,, +sqlalchemy/pool/base.py,sha256=mT-PHTlVUGcYRVsMB9LQwNgndjhOTOorWX5-hNRi2FM,52236 +sqlalchemy/pool/events.py,sha256=wdFfvat0fSrVF84Zzsz5E3HnVY0bhL7MPsGME-b2qa8,13149 +sqlalchemy/pool/impl.py,sha256=MLSh83SGNNtZZgZvA-5tvTIT8Dz7U95Bgt8HO_oR1Ps,18944 +sqlalchemy/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +sqlalchemy/schema.py,sha256=yt4dcuMAKMleUHVidsAVAsm-JPpASFZXP2xM3pmzYHY,3194 +sqlalchemy/sql/__init__.py,sha256=Y-bZ25Zf-bxqsF2zUkpRGTjFuozNNVQHxUJV3Qmaq2M,5820 +sqlalchemy/sql/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/_dml_constructors.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/_elements_constructors.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/_orm_types.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/_py_util.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/_selectable_constructors.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/_typing.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/annotation.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/base.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/cache_key.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/coercions.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/compiler.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/crud.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/ddl.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/default_comparator.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/dml.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/elements.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/events.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/expression.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/functions.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/lambdas.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/naming.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/operators.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/roles.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/schema.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/selectable.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/sqltypes.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/traversals.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/type_api.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/util.cpython-312.pyc,, +sqlalchemy/sql/__pycache__/visitors.cpython-312.pyc,, +sqlalchemy/sql/_dml_constructors.py,sha256=JF_XucNTfAk6Vz9fYiPWOgpIGtUkDj6VPILysLcrVhk,3795 +sqlalchemy/sql/_elements_constructors.py,sha256=eoQhkoRH0qox171ZSODyxxhj_HZEhO64rSowaN-I-v4,62630 +sqlalchemy/sql/_orm_types.py,sha256=0zeMit-V4rYZe-bB9X3xugnjFnPXH0gmeqkJou9Fows,625 +sqlalchemy/sql/_py_util.py,sha256=4KFXNvBq3hhfrr-A1J1uBml3b3CGguIf1dat9gsEHqE,2173 +sqlalchemy/sql/_selectable_constructors.py,sha256=fwVBsDHHWhngodBG205nvhM-Tb3uR1srbCnN3mPgrjA,18785 +sqlalchemy/sql/_typing.py,sha256=zYKlxXnUW_KIkGuBmBnzj-vFG1QON8_F9JN1dl9KSiM,12771 +sqlalchemy/sql/annotation.py,sha256=qHUEwbdmMD3Ybr0ez-Dyiw9l9UB_RUMHWAUIeO_r3gE,18245 +sqlalchemy/sql/base.py,sha256=kfmVNRimU5z6X6OKqMLMs1bDCFQ47BeyF_MZc23nkjY,73848 +sqlalchemy/sql/cache_key.py,sha256=ET2OIQ6jZK2FSxsdnCvhLCrNJ2Fp3zipQ-gvINgAjhQ,33668 +sqlalchemy/sql/coercions.py,sha256=lRciS5agnpVvx_vHYxJV-aN6QOVb_O4yCnMZ0s07GUE,40750 +sqlalchemy/sql/compiler.py,sha256=eT_zrKvApimVfycvcTdubQK8-QAzGHm5xWKdhOgnWUY,274965 +sqlalchemy/sql/crud.py,sha256=vFegNw5557ayS4kv761zh0bx0yikEKh1ovMrhErHelg,56514 +sqlalchemy/sql/ddl.py,sha256=rfb7gDvLmn_ktgH2xiXLRTczqnMOED1eakXuGuRPklg,45641 +sqlalchemy/sql/default_comparator.py,sha256=uXLr8B-X6KbybwTjLjZ2hN-WZAvqoMhZ-DDHJX7rAUw,16707 +sqlalchemy/sql/dml.py,sha256=oTW8PB-55qf6crAkbxh2JD-TvkT3MO1zqkKDrt5-2c8,65611 +sqlalchemy/sql/elements.py,sha256=RYq5N-IEPnhcDKtokeaCDIGZiUex8oDgwRLCDqjkk_g,176482 +sqlalchemy/sql/events.py,sha256=iWjc_nm1vClDBLg4ZhDnY75CkBdnlDPSPe0MGBSmbiM,18312 +sqlalchemy/sql/expression.py,sha256=rw5tAm8vbd5Vm4MofTZ0ZcXsphz4z9xO_exy-gem6TM,7586 +sqlalchemy/sql/functions.py,sha256=tbBxIeAqLV3kc1YDxyt68mxw0fFy6e93ctRUZSuuf3I,63858 +sqlalchemy/sql/lambdas.py,sha256=h9sPCETBgAanLtVHQsRPHeY-hTEjM5nscq3m4bDstwM,49196 +sqlalchemy/sql/naming.py,sha256=BU0ZdSzXXKHTPhoaKMWJ3gPMoeZSJJe9-3YDYflmjJw,6858 +sqlalchemy/sql/operators.py,sha256=h5bgu31gukGdsYsN_0-1C7IGAdSCFpBxuRjOUnu1Two,76792 +sqlalchemy/sql/roles.py,sha256=drAeWbevjgFAKNcMrH_EuJ-9sSvcq4aeXwAqMXXZGYw,7662 +sqlalchemy/sql/schema.py,sha256=WKKwxkC9oNRHN-B4s35NkWcr5dvavccKf-_1t35Do8A,229896 +sqlalchemy/sql/selectable.py,sha256=5Za7eh4USrgVwJgQGVX1bb2w1qXcy-hGzGpWNPbhf68,237610 +sqlalchemy/sql/sqltypes.py,sha256=yXHvZXfZJmaRvMoX4_jXqazAev33pk0Ltwl5c-D5Ha4,128609 +sqlalchemy/sql/traversals.py,sha256=7GALHt5mFceUv2SMUikIdAb9SUcSbACqhwoei5rPkxc,33664 +sqlalchemy/sql/type_api.py,sha256=wdi3nmOBRdhG6L1z21V_PwQGB8CIRouMdNKoIzJA4Zo,84440 +sqlalchemy/sql/util.py,sha256=G-2ZI6rZ7XxVu5YXaVvLrApeAk5VwSG4C--lqtglgGE,48086 +sqlalchemy/sql/visitors.py,sha256=URpw-GxxUkwjEDbD2xXJGyFJavG5lN6ISoY34JlYRS8,36319 +sqlalchemy/testing/__init__.py,sha256=GgUEqxUNCxg-92_GgBDnljUHsdCxaGPMG1TWy5tjwgk,3160 +sqlalchemy/testing/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/assertions.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/assertsql.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/asyncio.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/config.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/engines.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/entities.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/exclusions.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/pickleable.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/profiling.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/provision.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/requirements.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/schema.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/util.cpython-312.pyc,, +sqlalchemy/testing/__pycache__/warnings.cpython-312.pyc,, +sqlalchemy/testing/assertions.py,sha256=RFTkxGq-kDvn3JSUuT_6bU1y0vtoI6pE6ryZgV2YEx4,31439 +sqlalchemy/testing/assertsql.py,sha256=cmhtZrgPBjrqIfzFz3VBWxVNvxWoRllvmoWcUCoqsio,16817 +sqlalchemy/testing/asyncio.py,sha256=QsMzDWARFRrpLoWhuYqzYQPTUZ80fymlKrqOoDkmCmQ,3830 +sqlalchemy/testing/config.py,sha256=HySdB5_FgCW1iHAJVxYo-4wq5gUAEi0N8E93IC6M86Q,12058 +sqlalchemy/testing/engines.py,sha256=c1gFXfpo5S1dvNjGIL03mbW2eVYtUD_9M_ZEfQO2ArM,13414 +sqlalchemy/testing/entities.py,sha256=KdgTVPSALhi9KkAXj2giOYl62ld-1yZziIDBSV8E3vw,3354 +sqlalchemy/testing/exclusions.py,sha256=jzVrBXqyQlyMgvfChMjJOd0ZtReKgkJ4Ik-0mkWe6KM,12460 +sqlalchemy/testing/fixtures/__init__.py,sha256=e5YtfSlkKDRuyIZhEKBCycMX5BOO4MZ-0d97l1JDhJE,1198 +sqlalchemy/testing/fixtures/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/testing/fixtures/__pycache__/base.cpython-312.pyc,, +sqlalchemy/testing/fixtures/__pycache__/mypy.cpython-312.pyc,, +sqlalchemy/testing/fixtures/__pycache__/orm.cpython-312.pyc,, +sqlalchemy/testing/fixtures/__pycache__/sql.cpython-312.pyc,, +sqlalchemy/testing/fixtures/base.py,sha256=y5iEEdUZIft06fvAOXwKU73ciIFTO5AVgDDGzYD9nOY,12256 +sqlalchemy/testing/fixtures/mypy.py,sha256=9fuJ90F9LBki26dVEVOEtRVXG2koaK803k4nukTnA8o,11973 +sqlalchemy/testing/fixtures/orm.py,sha256=3JJoYdI2tj5-LL7AN8bVa79NV3Guo4d9p6IgheHkWGc,6095 +sqlalchemy/testing/fixtures/sql.py,sha256=ht-OD6fMZ0inxucRzRZG4kEMNicqY8oJdlKbZzHhAJc,15900 +sqlalchemy/testing/pickleable.py,sha256=G3L0xL9OtbX7wThfreRjWd0GW7q0kUKcTUuCN5ETGno,2833 +sqlalchemy/testing/plugin/__init__.py,sha256=vRfF7M763cGm9tLQDWK6TyBNHc80J1nX2fmGGxN14wY,247 +sqlalchemy/testing/plugin/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/testing/plugin/__pycache__/bootstrap.cpython-312.pyc,, +sqlalchemy/testing/plugin/__pycache__/plugin_base.cpython-312.pyc,, +sqlalchemy/testing/plugin/__pycache__/pytestplugin.cpython-312.pyc,, +sqlalchemy/testing/plugin/bootstrap.py,sha256=VYnVSMb-u30hGY6xGn6iG-LqiF0CubT90AJPFY_6UiY,1685 +sqlalchemy/testing/plugin/plugin_base.py,sha256=TBWdg2XgXB6QgUUFdKLv1O9-SXMitjHLm2rNNIzXZhQ,21578 +sqlalchemy/testing/plugin/pytestplugin.py,sha256=0rRCp7RlnhJBg3gJEq0t0kJ-BCTQ34bqBE_lEQk5U3U,27656 +sqlalchemy/testing/profiling.py,sha256=SWhWiZImJvDsNn0rQyNki70xdNxZL53ZI98ihxiykbQ,10148 +sqlalchemy/testing/provision.py,sha256=6r2FTnm-t7u8MMbWo7eMhAH3qkL0w0WlmE29MUSEIu4,14702 +sqlalchemy/testing/requirements.py,sha256=MVuTKtZjeTZaYlrAU8XFIB1bhJA_AedqL_q7NwVEGiw,52956 +sqlalchemy/testing/schema.py,sha256=IImFumAdpzOyoKAs0WnaGakq8D3sSU4snD9W4LVOV3s,6513 +sqlalchemy/testing/suite/__init__.py,sha256=S8TLwTiif8xX67qlZUo5I9fl9UjZAFGSzvlptp2WoWc,722 +sqlalchemy/testing/suite/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_cte.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_ddl.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_deprecations.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_dialect.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_insert.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_reflection.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_results.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_rowcount.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_select.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_sequence.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_types.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_unicode_ddl.cpython-312.pyc,, +sqlalchemy/testing/suite/__pycache__/test_update_delete.cpython-312.pyc,, +sqlalchemy/testing/suite/test_cte.py,sha256=d3OWDBNhnAwlyAz_QhFk-vKSWaAI3mADVnqdtTWOuwI,6451 +sqlalchemy/testing/suite/test_ddl.py,sha256=MItp-votCzvahlRqHRagte2Omyq9XUOFdFsgzCb6_-g,12031 +sqlalchemy/testing/suite/test_deprecations.py,sha256=7C6IbxRmq7wg_DLq56f1V5RCS9iVrAv3epJZQTB-dOo,5337 +sqlalchemy/testing/suite/test_dialect.py,sha256=eGJFZCwKmLrIl66ZlkLLZf5Fq6bzWI174gQsJt2bY2c,22923 +sqlalchemy/testing/suite/test_insert.py,sha256=pR0VWMQ9JJPbnANE6634PzR0VFmWMF8im6OTahc4vsQ,18824 +sqlalchemy/testing/suite/test_reflection.py,sha256=EJvTjRDimw9k90zlI5VCkmCzf7Tv5VF9y4O3D8SZMFU,109648 +sqlalchemy/testing/suite/test_results.py,sha256=9FFBNLeXcNRIC9FHfEjFKwfV6w2Bb58ulml_M8Zdokg,16914 +sqlalchemy/testing/suite/test_rowcount.py,sha256=UVyHHQsU0TxkzV_dqCOKR1aROvIq7frKYMVjwUqLWfE,7900 +sqlalchemy/testing/suite/test_select.py,sha256=S81w-Dox6W29Tjmi6LIBJ4HuB5E8dDAzmePDm0PKTYo,61732 +sqlalchemy/testing/suite/test_sequence.py,sha256=DMqyJkL1o4GClrNjzoy7GDn_jPNPTZNvk9t5e-MVXeo,9923 +sqlalchemy/testing/suite/test_types.py,sha256=gPA6t-90Icnpj2ZzITwbqka1DB-rNOoh6_xS9dC-4HU,67805 +sqlalchemy/testing/suite/test_unicode_ddl.py,sha256=0zVc2e3zbCQag_xL4b0i7F062HblHwV46JHLMweYtcE,6141 +sqlalchemy/testing/suite/test_update_delete.py,sha256=_OxH0wggHUqPImalGEPI48RiRx6mO985Om1PtRYOCzA,3994 +sqlalchemy/testing/util.py,sha256=KsUInolFBXUPIXVZKAdb_8rQrW8yW8OCtiA3GXuYRvA,14571 +sqlalchemy/testing/warnings.py,sha256=sj4vfTtjodcfoX6FPH_Zykb4fomjmgqIYj81QPpSwH8,1546 +sqlalchemy/types.py,sha256=m3I9h6xoyT7cjeUx5XCzmaE-GHT2sJVwECiuSJl75Ss,3168 +sqlalchemy/util/__init__.py,sha256=tYWkZV6PYVfEW32zt48FCLH12VyV_kaNUa3KBAOYpSM,8312 +sqlalchemy/util/__pycache__/__init__.cpython-312.pyc,, +sqlalchemy/util/__pycache__/_collections.cpython-312.pyc,, +sqlalchemy/util/__pycache__/_concurrency_py3k.cpython-312.pyc,, +sqlalchemy/util/__pycache__/_has_cy.cpython-312.pyc,, +sqlalchemy/util/__pycache__/_py_collections.cpython-312.pyc,, +sqlalchemy/util/__pycache__/compat.cpython-312.pyc,, +sqlalchemy/util/__pycache__/concurrency.cpython-312.pyc,, +sqlalchemy/util/__pycache__/deprecations.cpython-312.pyc,, +sqlalchemy/util/__pycache__/langhelpers.cpython-312.pyc,, +sqlalchemy/util/__pycache__/preloaded.cpython-312.pyc,, +sqlalchemy/util/__pycache__/queue.cpython-312.pyc,, +sqlalchemy/util/__pycache__/tool_support.cpython-312.pyc,, +sqlalchemy/util/__pycache__/topological.cpython-312.pyc,, +sqlalchemy/util/__pycache__/typing.cpython-312.pyc,, +sqlalchemy/util/_collections.py,sha256=RbP4UixqNtRBUrl_QqYDiadVmELSVxxXm2drhvQaIKo,20078 +sqlalchemy/util/_concurrency_py3k.py,sha256=UtPDkb67OOVWYvBqYaQgENg0k_jOA2mQOE04XmrbYq0,9170 +sqlalchemy/util/_has_cy.py,sha256=3oh7s5iQtW9qcI8zYunCfGAKG6fzo2DIpzP5p1BnE8Q,1247 +sqlalchemy/util/_py_collections.py,sha256=irOg3nkzxmtdYfIS46un2cp0JqSiACI7WGQBg-BaEXU,16714 +sqlalchemy/util/compat.py,sha256=TdDfvL21VnBEdSUnjcx-F8XhmVFg9Mvyr67a4omWZAM,8760 +sqlalchemy/util/concurrency.py,sha256=eQVS3YDH3GwB3Uw5pbzmqEBSYTK90EbnE5mQ05fHERg,3304 +sqlalchemy/util/deprecations.py,sha256=L7D4GqeIozpjO8iVybf7jL9dDlgfTbAaQH4TQAX74qE,12012 +sqlalchemy/util/langhelpers.py,sha256=G67avnsStFbslILlbCHmsyAMnShS7RYftFr9a8uFDL8,65140 +sqlalchemy/util/preloaded.py,sha256=RMarsuhtMW8ZuvqLSuR0kwbp45VRlzKpJMLUe7p__qY,5904 +sqlalchemy/util/queue.py,sha256=w1ufhuiC7lzyiZDhciRtRz1uyxU72jRI7SWhhL-p600,10185 +sqlalchemy/util/tool_support.py,sha256=e7lWu6o1QlKq4e6c9PyDsuyFyiWe79vO72UQ_YX2pUA,6135 +sqlalchemy/util/topological.py,sha256=HcJgdCeU0XFIskgIBnTaHXfRXaulaEJRYRwKv4yPNek,3458 +sqlalchemy/util/typing.py,sha256=C4jF7QTNo0w0bjvcIqSSTOvoy8FttuZtyTzjiyoIzQQ,20920 diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/REQUESTED b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/WHEEL b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/WHEEL new file mode 100644 index 00000000..d37c50b5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/WHEEL @@ -0,0 +1,5 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.8.0) +Root-Is-Purelib: false +Tag: cp312-cp312-macosx_11_0_arm64 + diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/top_level.txt b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/top_level.txt new file mode 100644 index 00000000..39fb2bef --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/SQLAlchemy-2.0.37.dist-info/top_level.txt @@ -0,0 +1 @@ +sqlalchemy diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc new file mode 100644 index 00000000..16e18713 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/__pycache__/six.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc new file mode 100644 index 00000000..743948e3 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/__pycache__/typing_extensions.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/INSTALLER b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/INSTALLER new file mode 100644 index 00000000..a1b589e3 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/INSTALLER @@ -0,0 +1 @@ +pip diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/LICENSE b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/LICENSE new file mode 100644 index 00000000..a065b6e5 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2025, Brandon Nielsen +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors + may be used to endorse or promote products derived from this software + without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/METADATA b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/METADATA new file mode 100644 index 00000000..ebcae541 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/METADATA @@ -0,0 +1,519 @@ +Metadata-Version: 2.2 +Name: aniso8601 +Version: 10.0.0 +Summary: A library for parsing ISO 8601 strings. +Home-page: https://bitbucket.org/nielsenb/aniso8601 +Author: Brandon Nielsen +Author-email: nielsenb@jetfuse.net +Project-URL: Documentation, https://aniso8601.readthedocs.io/ +Project-URL: Source, https://bitbucket.org/nielsenb/aniso8601 +Project-URL: Tracker, https://bitbucket.org/nielsenb/aniso8601/issues +Keywords: iso8601 parser +Classifier: Development Status :: 5 - Production/Stable +Classifier: Intended Audience :: Developers +Classifier: License :: OSI Approved :: BSD License +Classifier: Operating System :: OS Independent +Classifier: Programming Language :: Python +Classifier: Programming Language :: Python :: 2 +Classifier: Programming Language :: Python :: 2.7 +Classifier: Programming Language :: Python :: 3 +Classifier: Programming Language :: Python :: 3.9 +Classifier: Programming Language :: Python :: 3.10 +Classifier: Programming Language :: Python :: 3.11 +Classifier: Programming Language :: Python :: 3.12 +Classifier: Programming Language :: Python :: 3.13 +Classifier: Topic :: Software Development :: Libraries :: Python Modules +Description-Content-Type: text/x-rst +License-File: LICENSE +Provides-Extra: dev +Requires-Dist: black; extra == "dev" +Requires-Dist: coverage; extra == "dev" +Requires-Dist: isort; extra == "dev" +Requires-Dist: pre-commit; extra == "dev" +Requires-Dist: pyenchant; extra == "dev" +Requires-Dist: pylint; extra == "dev" +Dynamic: author +Dynamic: author-email +Dynamic: classifier +Dynamic: description +Dynamic: description-content-type +Dynamic: home-page +Dynamic: keywords +Dynamic: project-url +Dynamic: provides-extra +Dynamic: summary + +aniso8601 +========= + +Another ISO 8601 parser for Python +---------------------------------- + +Features +======== +* Pure Python implementation +* Logical behavior + + - Parse a time, get a `datetime.time `_ + - Parse a date, get a `datetime.date `_ + - Parse a datetime, get a `datetime.datetime `_ + - Parse a duration, get a `datetime.timedelta `_ + - Parse an interval, get a tuple of dates or datetimes + - Parse a repeating interval, get a date or datetime `generator `_ + +* UTC offset represented as fixed-offset tzinfo +* Parser separate from representation, allowing parsing to different datetime representations (see `Builders`_) +* No regular expressions + +Installation +============ + +The recommended installation method is to use pip:: + + $ pip install aniso8601 + +Alternatively, you can download the source (git repository hosted at `Bitbucket `_) and install directly:: + + $ python setup.py install + +Use +=== + +Parsing datetimes +----------------- + +*Consider* `datetime.datetime.fromisoformat `_ *for basic ISO 8601 datetime parsing* + +To parse a typical ISO 8601 datetime string:: + + >>> import aniso8601 + >>> aniso8601.parse_datetime('1977-06-10T12:00:00Z') + datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC) + +Alternative delimiters can be specified, for example, a space:: + + >>> aniso8601.parse_datetime('1977-06-10 12:00:00Z', delimiter=' ') + datetime.datetime(1977, 6, 10, 12, 0, tzinfo=+0:00:00 UTC) + +UTC offsets are supported:: + + >>> aniso8601.parse_datetime('1979-06-05T08:00:00-08:00') + datetime.datetime(1979, 6, 5, 8, 0, tzinfo=-8:00:00 UTC) + +If a UTC offset is not specified, the returned datetime will be naive:: + + >>> aniso8601.parse_datetime('1983-01-22T08:00:00') + datetime.datetime(1983, 1, 22, 8, 0) + +Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`:: + + >>> aniso8601.parse_datetime('2018-03-06T23:59:60') + Traceback (most recent call last): + File "", line 1, in + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/time.py", line 196, in parse_datetime + return builder.build_datetime(datepart, timepart) + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 237, in build_datetime + cls._build_object(time)) + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/__init__.py", line 336, in _build_object + return cls.build_time(hh=parsetuple.hh, mm=parsetuple.mm, + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 191, in build_time + hh, mm, ss, tz = cls.range_check_time(hh, mm, ss, tz) + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/__init__.py", line 266, in range_check_time + raise LeapSecondError('Leap seconds are not supported.') + aniso8601.exceptions.LeapSecondError: Leap seconds are not supported. + +To get the resolution of an ISO 8601 datetime string:: + + >>> aniso8601.get_datetime_resolution('1977-06-10T12:00:00Z') == aniso8601.resolution.TimeResolution.Seconds + True + >>> aniso8601.get_datetime_resolution('1977-06-10T12:00') == aniso8601.resolution.TimeResolution.Minutes + True + >>> aniso8601.get_datetime_resolution('1977-06-10T12') == aniso8601.resolution.TimeResolution.Hours + True + +Note that datetime resolutions map to :code:`TimeResolution` as a valid datetime must have at least one time member so the resolution mapping is equivalent. + +Parsing dates +------------- + +*Consider* `datetime.date.fromisoformat `_ *for basic ISO 8601 date parsing* + +To parse a date represented in an ISO 8601 string:: + + >>> import aniso8601 + >>> aniso8601.parse_date('1984-04-23') + datetime.date(1984, 4, 23) + +Basic format is supported as well:: + + >>> aniso8601.parse_date('19840423') + datetime.date(1984, 4, 23) + +To parse a date using the ISO 8601 week date format:: + + >>> aniso8601.parse_date('1986-W38-1') + datetime.date(1986, 9, 15) + +To parse an ISO 8601 ordinal date:: + + >>> aniso8601.parse_date('1988-132') + datetime.date(1988, 5, 11) + +To get the resolution of an ISO 8601 date string:: + + >>> aniso8601.get_date_resolution('1981-04-05') == aniso8601.resolution.DateResolution.Day + True + >>> aniso8601.get_date_resolution('1981-04') == aniso8601.resolution.DateResolution.Month + True + >>> aniso8601.get_date_resolution('1981') == aniso8601.resolution.DateResolution.Year + True + +Parsing times +------------- + +*Consider* `datetime.time.fromisoformat `_ *for basic ISO 8601 time parsing* + +To parse a time formatted as an ISO 8601 string:: + + >>> import aniso8601 + >>> aniso8601.parse_time('11:31:14') + datetime.time(11, 31, 14) + +As with all of the above, basic format is supported:: + + >>> aniso8601.parse_time('113114') + datetime.time(11, 31, 14) + +A UTC offset can be specified for times:: + + >>> aniso8601.parse_time('17:18:19-02:30') + datetime.time(17, 18, 19, tzinfo=-2:30:00 UTC) + >>> aniso8601.parse_time('171819Z') + datetime.time(17, 18, 19, tzinfo=+0:00:00 UTC) + +Reduced accuracy is supported:: + + >>> aniso8601.parse_time('21:42') + datetime.time(21, 42) + >>> aniso8601.parse_time('22') + datetime.time(22, 0) + +A decimal fraction is always allowed on the lowest order element of an ISO 8601 formatted time:: + + >>> aniso8601.parse_time('22:33.5') + datetime.time(22, 33, 30) + >>> aniso8601.parse_time('23.75') + datetime.time(23, 45) + +The decimal fraction can be specified with a comma instead of a full-stop:: + + >>> aniso8601.parse_time('22:33,5') + datetime.time(22, 33, 30) + >>> aniso8601.parse_time('23,75') + datetime.time(23, 45) + +Leap seconds are currently not supported and attempting to parse one raises a :code:`LeapSecondError`:: + + >>> aniso8601.parse_time('23:59:60') + Traceback (most recent call last): + File "", line 1, in + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/time.py", line 174, in parse_time + return builder.build_time(hh=hourstr, mm=minutestr, ss=secondstr, tz=tz) + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 191, in build_time + hh, mm, ss, tz = cls.range_check_time(hh, mm, ss, tz) + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/__init__.py", line 266, in range_check_time + raise LeapSecondError('Leap seconds are not supported.') + aniso8601.exceptions.LeapSecondError: Leap seconds are not supported. + +To get the resolution of an ISO 8601 time string:: + + >>> aniso8601.get_time_resolution('11:31:14') == aniso8601.resolution.TimeResolution.Seconds + True + >>> aniso8601.get_time_resolution('11:31') == aniso8601.resolution.TimeResolution.Minutes + True + >>> aniso8601.get_time_resolution('11') == aniso8601.resolution.TimeResolution.Hours + True + +Parsing durations +----------------- + +To parse a duration formatted as an ISO 8601 string:: + + >>> import aniso8601 + >>> aniso8601.parse_duration('P1Y2M3DT4H54M6S') + datetime.timedelta(428, 17646) + +Reduced accuracy is supported:: + + >>> aniso8601.parse_duration('P1Y') + datetime.timedelta(365) + +A decimal fraction is allowed on the lowest order element:: + + >>> aniso8601.parse_duration('P1YT3.5M') + datetime.timedelta(365, 210) + +The decimal fraction can be specified with a comma instead of a full-stop:: + + >>> aniso8601.parse_duration('P1YT3,5M') + datetime.timedelta(365, 210) + +Parsing a duration from a combined date and time is supported as well:: + + >>> aniso8601.parse_duration('P0001-01-02T01:30:05') + datetime.timedelta(397, 5405) + +To get the resolution of an ISO 8601 duration string:: + + >>> aniso8601.get_duration_resolution('P1Y2M3DT4H54M6S') == aniso8601.resolution.DurationResolution.Seconds + True + >>> aniso8601.get_duration_resolution('P1Y2M3DT4H54M') == aniso8601.resolution.DurationResolution.Minutes + True + >>> aniso8601.get_duration_resolution('P1Y2M3DT4H') == aniso8601.resolution.DurationResolution.Hours + True + >>> aniso8601.get_duration_resolution('P1Y2M3D') == aniso8601.resolution.DurationResolution.Days + True + >>> aniso8601.get_duration_resolution('P1Y2M') == aniso8601.resolution.DurationResolution.Months + True + >>> aniso8601.get_duration_resolution('P1Y') == aniso8601.resolution.DurationResolution.Years + True + +The default :code:`PythonTimeBuilder` assumes years are 365 days, and months are 30 days. Where calendar level accuracy is required, a `RelativeTimeBuilder `_ can be used, see also `Builders`_. + +Parsing intervals +----------------- + +To parse an interval specified by a start and end:: + + >>> import aniso8601 + >>> aniso8601.parse_interval('2007-03-01T13:00:00/2008-05-11T15:30:00') + (datetime.datetime(2007, 3, 1, 13, 0), datetime.datetime(2008, 5, 11, 15, 30)) + +Intervals specified by a start time and a duration are supported:: + + >>> aniso8601.parse_interval('2007-03-01T13:00:00Z/P1Y2M10DT2H30M') + (datetime.datetime(2007, 3, 1, 13, 0, tzinfo=+0:00:00 UTC), datetime.datetime(2008, 5, 9, 15, 30, tzinfo=+0:00:00 UTC)) + +A duration can also be specified by a duration and end time:: + + >>> aniso8601.parse_interval('P1M/1981-04-05') + (datetime.date(1981, 4, 5), datetime.date(1981, 3, 6)) + +Notice that the result of the above parse is not in order from earliest to latest. If sorted intervals are required, simply use the :code:`sorted` keyword as shown below:: + + >>> sorted(aniso8601.parse_interval('P1M/1981-04-05')) + [datetime.date(1981, 3, 6), datetime.date(1981, 4, 5)] + +The end of an interval is returned as a datetime when required to maintain the resolution specified by a duration, even if the duration start is given as a date:: + + >>> aniso8601.parse_interval('2014-11-12/PT4H54M6.5S') + (datetime.date(2014, 11, 12), datetime.datetime(2014, 11, 12, 4, 54, 6, 500000)) + >>> aniso8601.parse_interval('2007-03-01/P1.5D') + (datetime.date(2007, 3, 1), datetime.datetime(2007, 3, 2, 12, 0)) + +Concise representations are supported:: + + >>> aniso8601.parse_interval('2020-01-01/02') + (datetime.date(2020, 1, 1), datetime.date(2020, 1, 2)) + >>> aniso8601.parse_interval('2007-12-14T13:30/15:30') + (datetime.datetime(2007, 12, 14, 13, 30), datetime.datetime(2007, 12, 14, 15, 30)) + >>> aniso8601.parse_interval('2008-02-15/03-14') + (datetime.date(2008, 2, 15), datetime.date(2008, 3, 14)) + >>> aniso8601.parse_interval('2007-11-13T09:00/15T17:00') + (datetime.datetime(2007, 11, 13, 9, 0), datetime.datetime(2007, 11, 15, 17, 0)) + +Repeating intervals are supported as well, and return a `generator `_:: + + >>> aniso8601.parse_repeating_interval('R3/1981-04-05/P1D') + + >>> list(aniso8601.parse_repeating_interval('R3/1981-04-05/P1D')) + [datetime.date(1981, 4, 5), datetime.date(1981, 4, 6), datetime.date(1981, 4, 7)] + +Repeating intervals are allowed to go in the reverse direction:: + + >>> list(aniso8601.parse_repeating_interval('R2/PT1H2M/1980-03-05T01:01:00')) + [datetime.datetime(1980, 3, 5, 1, 1), datetime.datetime(1980, 3, 4, 23, 59)] + +Unbounded intervals are also allowed (Python 2):: + + >>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00') + >>> result.next() + datetime.datetime(1980, 3, 5, 1, 1) + >>> result.next() + datetime.datetime(1980, 3, 4, 23, 59) + +or for Python 3:: + + >>> result = aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00') + >>> next(result) + datetime.datetime(1980, 3, 5, 1, 1) + >>> next(result) + datetime.datetime(1980, 3, 4, 23, 59) + +Note that you should never try to convert a generator produced by an unbounded interval to a list:: + + >>> list(aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00')) + Traceback (most recent call last): + File "", line 1, in + File "/home/nielsenb/Jetfuse/aniso8601/aniso8601/aniso8601/builders/python.py", line 560, in _date_generator_unbounded + currentdate += timedelta + OverflowError: date value out of range + +To get the resolution of an ISO 8601 interval string:: + + >>> aniso8601.get_interval_resolution('2007-03-01T13:00:00/2008-05-11T15:30:00') == aniso8601.resolution.IntervalResolution.Seconds + True + >>> aniso8601.get_interval_resolution('2007-03-01T13:00/2008-05-11T15:30') == aniso8601.resolution.IntervalResolution.Minutes + True + >>> aniso8601.get_interval_resolution('2007-03-01T13/2008-05-11T15') == aniso8601.resolution.IntervalResolution.Hours + True + >>> aniso8601.get_interval_resolution('2007-03-01/2008-05-11') == aniso8601.resolution.IntervalResolution.Day + True + >>> aniso8601.get_interval_resolution('2007-03/P1Y') == aniso8601.resolution.IntervalResolution.Month + True + >>> aniso8601.get_interval_resolution('2007/P1Y') == aniso8601.resolution.IntervalResolution.Year + True + +And for repeating ISO 8601 interval strings:: + + >>> aniso8601.get_repeating_interval_resolution('R3/1981-04-05/P1D') == aniso8601.resolution.IntervalResolution.Day + True + >>> aniso8601.get_repeating_interval_resolution('R/PT1H2M/1980-03-05T01:01:00') == aniso8601.resolution.IntervalResolution.Seconds + True + +Builders +======== + +Builders can be used to change the output format of a parse operation. All parse functions have a :code:`builder` keyword argument which accepts a builder class. + +Two builders are included. The :code:`PythonTimeBuilder` (the default) in the :code:`aniso8601.builders.python` module, and the :code:`TupleBuilder` which returns the parse result as a corresponding named tuple and is located in the :code:`aniso8601.builders` module. + +Information on writing a builder can be found in `BUILDERS `_. + +The following builders are available as separate projects: + +* `RelativeTimeBuilder `_ supports parsing to `datetutil relativedelta types `_ for calendar level accuracy +* `AttoTimeBuilder `_ supports parsing directly to `attotime attodatetime and attotimedelta types `_ which support sub-nanosecond precision +* `NumPyTimeBuilder `_ supports parsing directly to `NumPy datetime64 and timedelta64 types `_ + +TupleBuilder +------------ + +The :code:`TupleBuilder` returns parse results as `named tuples `_. It is located in the :code:`aniso8601.builders` module. + +Datetimes +^^^^^^^^^ + +Parsing a datetime returns a :code:`DatetimeTuple` containing :code:`Date` and :code:`Time` tuples . The date tuple contains the following parse components: :code:`YYYY`, :code:`MM`, :code:`DD`, :code:`Www`, :code:`D`, :code:`DDD`. The time tuple contains the following parse components :code:`hh`, :code:`mm`, :code:`ss`, :code:`tz`, where :code:`tz` itself is a tuple with the following components :code:`negative`, :code:`Z`, :code:`hh`, :code:`mm`, :code:`name` with :code:`negative` and :code:`Z` being booleans:: + + >>> import aniso8601 + >>> from aniso8601.builders import TupleBuilder + >>> aniso8601.parse_datetime('1977-06-10T12:00:00', builder=TupleBuilder) + Datetime(date=Date(YYYY='1977', MM='06', DD='10', Www=None, D=None, DDD=None), time=Time(hh='12', mm='00', ss='00', tz=None)) + >>> aniso8601.parse_datetime('1979-06-05T08:00:00-08:00', builder=TupleBuilder) + Datetime(date=Date(YYYY='1979', MM='06', DD='05', Www=None, D=None, DDD=None), time=Time(hh='08', mm='00', ss='00', tz=Timezone(negative=True, Z=None, hh='08', mm='00', name='-08:00'))) + +Dates +^^^^^ + +Parsing a date returns a :code:`DateTuple` containing the following parse components: :code:`YYYY`, :code:`MM`, :code:`DD`, :code:`Www`, :code:`D`, :code:`DDD`:: + + >>> import aniso8601 + >>> from aniso8601.builders import TupleBuilder + >>> aniso8601.parse_date('1984-04-23', builder=TupleBuilder) + Date(YYYY='1984', MM='04', DD='23', Www=None, D=None, DDD=None) + >>> aniso8601.parse_date('1986-W38-1', builder=TupleBuilder) + Date(YYYY='1986', MM=None, DD=None, Www='38', D='1', DDD=None) + >>> aniso8601.parse_date('1988-132', builder=TupleBuilder) + Date(YYYY='1988', MM=None, DD=None, Www=None, D=None, DDD='132') + +Times +^^^^^ + +Parsing a time returns a :code:`TimeTuple` containing following parse components: :code:`hh`, :code:`mm`, :code:`ss`, :code:`tz`, where :code:`tz` is a :code:`TimezoneTuple` with the following components :code:`negative`, :code:`Z`, :code:`hh`, :code:`mm`, :code:`name`, with :code:`negative` and :code:`Z` being booleans:: + + >>> import aniso8601 + >>> from aniso8601.builders import TupleBuilder + >>> aniso8601.parse_time('11:31:14', builder=TupleBuilder) + Time(hh='11', mm='31', ss='14', tz=None) + >>> aniso8601.parse_time('171819Z', builder=TupleBuilder) + Time(hh='17', mm='18', ss='19', tz=Timezone(negative=False, Z=True, hh=None, mm=None, name='Z')) + >>> aniso8601.parse_time('17:18:19-02:30', builder=TupleBuilder) + Time(hh='17', mm='18', ss='19', tz=Timezone(negative=True, Z=None, hh='02', mm='30', name='-02:30')) + +Durations +^^^^^^^^^ + +Parsing a duration returns a :code:`DurationTuple` containing the following parse components: :code:`PnY`, :code:`PnM`, :code:`PnW`, :code:`PnD`, :code:`TnH`, :code:`TnM`, :code:`TnS`:: + + >>> import aniso8601 + >>> from aniso8601.builders import TupleBuilder + >>> aniso8601.parse_duration('P1Y2M3DT4H54M6S', builder=TupleBuilder) + Duration(PnY='1', PnM='2', PnW=None, PnD='3', TnH='4', TnM='54', TnS='6') + >>> aniso8601.parse_duration('P7W', builder=TupleBuilder) + Duration(PnY=None, PnM=None, PnW='7', PnD=None, TnH=None, TnM=None, TnS=None) + +Intervals +^^^^^^^^^ + +Parsing an interval returns an :code:`IntervalTuple` containing the following parse components: :code:`start`, :code:`end`, :code:`duration`, :code:`start` and :code:`end` may both be datetime or date tuples, :code:`duration` is a duration tuple:: + + >>> import aniso8601 + >>> from aniso8601.builders import TupleBuilder + >>> aniso8601.parse_interval('2007-03-01T13:00:00/2008-05-11T15:30:00', builder=TupleBuilder) + Interval(start=Datetime(date=Date(YYYY='2007', MM='03', DD='01', Www=None, D=None, DDD=None), time=Time(hh='13', mm='00', ss='00', tz=None)), end=Datetime(date=Date(YYYY='2008', MM='05', DD='11', Www=None, D=None, DDD=None), time=Time(hh='15', mm='30', ss='00', tz=None)), duration=None) + >>> aniso8601.parse_interval('2007-03-01T13:00:00Z/P1Y2M10DT2H30M', builder=TupleBuilder) + Interval(start=Datetime(date=Date(YYYY='2007', MM='03', DD='01', Www=None, D=None, DDD=None), time=Time(hh='13', mm='00', ss='00', tz=Timezone(negative=False, Z=True, hh=None, mm=None, name='Z'))), end=None, duration=Duration(PnY='1', PnM='2', PnW=None, PnD='10', TnH='2', TnM='30', TnS=None)) + >>> aniso8601.parse_interval('P1M/1981-04-05', builder=TupleBuilder) + Interval(start=None, end=Date(YYYY='1981', MM='04', DD='05', Www=None, D=None, DDD=None), duration=Duration(PnY=None, PnM='1', PnW=None, PnD=None, TnH=None, TnM=None, TnS=None)) + +A repeating interval returns a :code:`RepeatingIntervalTuple` containing the following parse components: :code:`R`, :code:`Rnn`, :code:`interval`, where :code:`R` is a boolean, :code:`True` for an unbounded interval, :code:`False` otherwise.:: + + >>> aniso8601.parse_repeating_interval('R3/1981-04-05/P1D', builder=TupleBuilder) + RepeatingInterval(R=False, Rnn='3', interval=Interval(start=Date(YYYY='1981', MM='04', DD='05', Www=None, D=None, DDD=None), end=None, duration=Duration(PnY=None, PnM=None, PnW=None, PnD='1', TnH=None, TnM=None, TnS=None))) + >>> aniso8601.parse_repeating_interval('R/PT1H2M/1980-03-05T01:01:00', builder=TupleBuilder) + RepeatingInterval(R=True, Rnn=None, interval=Interval(start=None, end=Datetime(date=Date(YYYY='1980', MM='03', DD='05', Www=None, D=None, DDD=None), time=Time(hh='01', mm='01', ss='00', tz=None)), duration=Duration(PnY=None, PnM=None, PnW=None, PnD=None, TnH='1', TnM='2', TnS=None))) + +Development +=========== + +Setup +----- + +It is recommended to develop using a `virtualenv `_. + +Inside a virtualenv, development dependencies can be installed automatically:: + + $ pip install -e .[dev] + +`pre-commit `_ is used for managing pre-commit hooks:: + + $ pre-commit install + +To run the pre-commit hooks manually:: + + $ pre-commit run --all-files + +Tests +----- + +Tests can be run using the `unittest testing framework `_:: + + $ python -m unittest discover aniso8601 + +Contributing +============ + +aniso8601 is an open source project hosted on `Bitbucket `_. + +Any and all bugs are welcome on our `issue tracker `_. +Of particular interest are valid ISO 8601 strings that don't parse, or invalid ones that do. At a minimum, +bug reports should include an example of the misbehaving string, as well as the expected result. Of course +patches containing unit tests (or fixed bugs) are welcome! + +References +========== + +* `ISO 8601:2004(E) `_ (Caution, PDF link) +* `Wikipedia article on ISO 8601 `_ +* `Discussion on alternative ISO 8601 parsers for Python `_ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/RECORD b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/RECORD new file mode 100644 index 00000000..f025c43e --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/RECORD @@ -0,0 +1,61 @@ +aniso8601-10.0.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4 +aniso8601-10.0.0.dist-info/LICENSE,sha256=w8yguadP0pZovZm13PAnTVO-kE3md4kW3IUnCPQHsPA,1501 +aniso8601-10.0.0.dist-info/METADATA,sha256=xdACJdmxmW0r6sVmuokYme51EH23UhNhbwOc1246CIg,23577 +aniso8601-10.0.0.dist-info/RECORD,, +aniso8601-10.0.0.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0 +aniso8601-10.0.0.dist-info/WHEEL,sha256=9Hm2OB-j1QcCUq9Jguht7ayGIIZBRTdOXD1qg9cCgPM,109 +aniso8601-10.0.0.dist-info/top_level.txt,sha256=MVQomyeED8nGIH7PUQdMzxgLppIB48oYHtcmL17ETB0,10 +aniso8601/__init__.py,sha256=LwoL2Wj0kdYdHS3YqvGPH6SJ1HQWpsN9jKsAMHlAdwE,705 +aniso8601/__pycache__/__init__.cpython-312.pyc,, +aniso8601/__pycache__/compat.cpython-312.pyc,, +aniso8601/__pycache__/date.cpython-312.pyc,, +aniso8601/__pycache__/decimalfraction.cpython-312.pyc,, +aniso8601/__pycache__/duration.cpython-312.pyc,, +aniso8601/__pycache__/exceptions.cpython-312.pyc,, +aniso8601/__pycache__/interval.cpython-312.pyc,, +aniso8601/__pycache__/resolution.cpython-312.pyc,, +aniso8601/__pycache__/time.cpython-312.pyc,, +aniso8601/__pycache__/timezone.cpython-312.pyc,, +aniso8601/__pycache__/utcoffset.cpython-312.pyc,, +aniso8601/builders/__init__.py,sha256=jXJ75D-QRhB8huW2GyShjh_I4Z83ua4Qgzn1DQiX-1M,17975 +aniso8601/builders/__pycache__/__init__.cpython-312.pyc,, +aniso8601/builders/__pycache__/python.cpython-312.pyc,, +aniso8601/builders/python.py,sha256=U0Tvqt4vPVcH9epfkbz_o4550yr_4_Q8SvoUdj9JAvs,22072 +aniso8601/builders/tests/__init__.py,sha256=qjC0jrTWf2UUlZtXE3AKcMFSLC2kQPOvAI36t5gc8q0,209 +aniso8601/builders/tests/__pycache__/__init__.cpython-312.pyc,, +aniso8601/builders/tests/__pycache__/test_init.cpython-312.pyc,, +aniso8601/builders/tests/__pycache__/test_python.cpython-312.pyc,, +aniso8601/builders/tests/test_init.py,sha256=pyES5pMJUWy16KK4MLsfzRmPRcQvj6_vxcM8yzeZxOc,29997 +aniso8601/builders/tests/test_python.py,sha256=dhkaGiE0ToMPBhUmhOAeM32aC2SDu6Rj6d4YniV2M7A,62032 +aniso8601/compat.py,sha256=CvNkC-tCr3hzv1i9VOzgiPaS2EUxCfxtfo8SkJ2Juyc,571 +aniso8601/date.py,sha256=D3Kffr6Ln_z0fNTP5KAQa0R0T00VdmZgIS_O-pVmxnI,4496 +aniso8601/decimalfraction.py,sha256=NBUies6Gp1NrVkSU_9MwJNu28OhUIeWXNpkTRcClGYA,333 +aniso8601/duration.py,sha256=XEMm8t3Vipw3YsCgXf17XA10dHmUCQuJo4-PX4z8U7I,9550 +aniso8601/exceptions.py,sha256=uCQIrrIpCJV-n3WDUuhB3wMdgmHuu1UqOXrE9ct3xPY,1313 +aniso8601/interval.py,sha256=ruz49D2BoyoyYggM2oLrz0HSB8CIPxdl4LZCvwq3kCg,10752 +aniso8601/resolution.py,sha256=RJGfir0k6IiR3L1ZCrPvjhsQliVT_ZOcaeKqJqH6JHM,684 +aniso8601/tests/__init__.py,sha256=qjC0jrTWf2UUlZtXE3AKcMFSLC2kQPOvAI36t5gc8q0,209 +aniso8601/tests/__pycache__/__init__.cpython-312.pyc,, +aniso8601/tests/__pycache__/compat.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_compat.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_date.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_decimalfraction.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_duration.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_init.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_interval.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_time.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_timezone.cpython-312.pyc,, +aniso8601/tests/__pycache__/test_utcoffset.cpython-312.pyc,, +aniso8601/tests/compat.py,sha256=J4Ocl6zpo7WmA_pItOc2KUH8xZU156L9fTbCBBFX9xY,346 +aniso8601/tests/test_compat.py,sha256=55qZ_Uu7XRQo8hXNDUjy-IB3WaTqATnVtMlAJP4TGr4,763 +aniso8601/tests/test_date.py,sha256=bJy2ve-iFmWlBCWDidfK2lB-7OBMjeATfK3SBFSet4U,9266 +aniso8601/tests/test_decimalfraction.py,sha256=bvPPSuWnS2XfD10wBUjg5nFFbI1kfBshR2Wkmfggu6I,578 +aniso8601/tests/test_duration.py,sha256=QRLpd_bdgEzHjcLkLMFMWnGF4bN_YxPDdtwACtBekc4,44952 +aniso8601/tests/test_init.py,sha256=1GF0Yms8adNM3Ax13w2ncSz6toHhK3pRkPYoNaRcPVk,1689 +aniso8601/tests/test_interval.py,sha256=GjeU8HrIT-klpselgsgMcF1KfuV7sDKLlyVU5S4XMCQ,60457 +aniso8601/tests/test_time.py,sha256=SS0jXkEl5bYFSe1qFPpjZ0GNXycgBMH16Uc885ujR7I,19147 +aniso8601/tests/test_timezone.py,sha256=EJk2cTsHddhe2Pqtzl250gKF8XoXynEAngNctk23b48,4649 +aniso8601/tests/test_utcoffset.py,sha256=wQ7ivBqax2KP340tlC0DBxM7DTK9SNy_Zq_13FqeaKM,1926 +aniso8601/time.py,sha256=CZRisJz6u7fBfMthZvNcUuVFSL_GCYi9WEjXsbrnDF8,5687 +aniso8601/timezone.py,sha256=xpukG_AuvyMNGs57y4bf40eHRcpe3b1fKC8x-H8Epo8,2124 +aniso8601/utcoffset.py,sha256=dm7-eFl6WQFPpDamcTVl46aEjmGObpWJdSZJ-QzblfU,2421 diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/REQUESTED b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/REQUESTED new file mode 100644 index 00000000..e69de29b diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/WHEEL b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/WHEEL new file mode 100644 index 00000000..eaea6f3b --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/WHEEL @@ -0,0 +1,6 @@ +Wheel-Version: 1.0 +Generator: setuptools (75.8.0) +Root-Is-Purelib: true +Tag: py2-none-any +Tag: py3-none-any + diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/top_level.txt b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/top_level.txt new file mode 100644 index 00000000..166ae78c --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601-10.0.0.dist-info/top_level.txt @@ -0,0 +1 @@ +aniso8601 diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__init__.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__init__.py new file mode 100644 index 00000000..c85d218b --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__init__.py @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +from aniso8601.date import get_date_resolution, parse_date +from aniso8601.duration import get_duration_resolution, parse_duration +from aniso8601.interval import ( + get_interval_resolution, + get_repeating_interval_resolution, + parse_interval, + parse_repeating_interval, +) + +# Import the main parsing functions so they are readily available +from aniso8601.time import ( + get_datetime_resolution, + get_time_resolution, + parse_datetime, + parse_time, +) + +__version__ = "10.0.0" diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/__init__.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..931f1ecf Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/__init__.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/compat.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/compat.cpython-312.pyc new file mode 100644 index 00000000..5f2081fc Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/compat.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/date.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/date.cpython-312.pyc new file mode 100644 index 00000000..4735b26a Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/date.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/decimalfraction.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/decimalfraction.cpython-312.pyc new file mode 100644 index 00000000..60691505 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/decimalfraction.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/duration.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/duration.cpython-312.pyc new file mode 100644 index 00000000..66e64bd6 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/duration.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/exceptions.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/exceptions.cpython-312.pyc new file mode 100644 index 00000000..088d8ebc Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/exceptions.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/interval.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/interval.cpython-312.pyc new file mode 100644 index 00000000..e0c27a69 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/interval.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/resolution.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/resolution.cpython-312.pyc new file mode 100644 index 00000000..16b741c3 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/resolution.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/time.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/time.cpython-312.pyc new file mode 100644 index 00000000..cc65c13f Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/time.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/timezone.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/timezone.cpython-312.pyc new file mode 100644 index 00000000..73e40b80 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/timezone.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/utcoffset.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/utcoffset.cpython-312.pyc new file mode 100644 index 00000000..d92564f4 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/__pycache__/utcoffset.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__init__.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__init__.py new file mode 100644 index 00000000..280a00e6 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__init__.py @@ -0,0 +1,614 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import calendar +from collections import namedtuple + +from aniso8601.exceptions import ( + DayOutOfBoundsError, + HoursOutOfBoundsError, + ISOFormatError, + LeapSecondError, + MidnightBoundsError, + MinutesOutOfBoundsError, + MonthOutOfBoundsError, + SecondsOutOfBoundsError, + WeekOutOfBoundsError, + YearOutOfBoundsError, +) + +DateTuple = namedtuple("Date", ["YYYY", "MM", "DD", "Www", "D", "DDD"]) +TimeTuple = namedtuple("Time", ["hh", "mm", "ss", "tz"]) +DatetimeTuple = namedtuple("Datetime", ["date", "time"]) +DurationTuple = namedtuple( + "Duration", ["PnY", "PnM", "PnW", "PnD", "TnH", "TnM", "TnS"] +) +IntervalTuple = namedtuple("Interval", ["start", "end", "duration"]) +RepeatingIntervalTuple = namedtuple("RepeatingInterval", ["R", "Rnn", "interval"]) +TimezoneTuple = namedtuple("Timezone", ["negative", "Z", "hh", "mm", "name"]) + +Limit = namedtuple( + "Limit", + [ + "casterrorstring", + "min", + "max", + "rangeexception", + "rangeerrorstring", + "rangefunc", + ], +) + + +def cast( + value, + castfunction, + caughtexceptions=(ValueError,), + thrownexception=ISOFormatError, + thrownmessage=None, +): + try: + result = castfunction(value) + except caughtexceptions: + raise thrownexception(thrownmessage) + + return result + + +def range_check(valuestr, limit): + # Returns cast value if in range, raises defined exceptions on failure + if valuestr is None: + return None + + if "." in valuestr: + castfunc = float + else: + castfunc = int + + value = cast(valuestr, castfunc, thrownmessage=limit.casterrorstring) + + if limit.min is not None and value < limit.min: + raise limit.rangeexception(limit.rangeerrorstring) + + if limit.max is not None and value > limit.max: + raise limit.rangeexception(limit.rangeerrorstring) + + return value + + +class BaseTimeBuilder(object): + # Limit tuple format cast function, cast error string, + # lower limit, upper limit, limit error string + DATE_YYYY_LIMIT = Limit( + "Invalid year string.", + 0000, + 9999, + YearOutOfBoundsError, + "Year must be between 1..9999.", + range_check, + ) + DATE_MM_LIMIT = Limit( + "Invalid month string.", + 1, + 12, + MonthOutOfBoundsError, + "Month must be between 1..12.", + range_check, + ) + DATE_DD_LIMIT = Limit( + "Invalid day string.", + 1, + 31, + DayOutOfBoundsError, + "Day must be between 1..31.", + range_check, + ) + DATE_WWW_LIMIT = Limit( + "Invalid week string.", + 1, + 53, + WeekOutOfBoundsError, + "Week number must be between 1..53.", + range_check, + ) + DATE_D_LIMIT = Limit( + "Invalid weekday string.", + 1, + 7, + DayOutOfBoundsError, + "Weekday number must be between 1..7.", + range_check, + ) + DATE_DDD_LIMIT = Limit( + "Invalid ordinal day string.", + 1, + 366, + DayOutOfBoundsError, + "Ordinal day must be between 1..366.", + range_check, + ) + TIME_HH_LIMIT = Limit( + "Invalid hour string.", + 0, + 24, + HoursOutOfBoundsError, + "Hour must be between 0..24 with 24 representing midnight.", + range_check, + ) + TIME_MM_LIMIT = Limit( + "Invalid minute string.", + 0, + 59, + MinutesOutOfBoundsError, + "Minute must be between 0..59.", + range_check, + ) + TIME_SS_LIMIT = Limit( + "Invalid second string.", + 0, + 60, + SecondsOutOfBoundsError, + "Second must be between 0..60 with 60 representing a leap second.", + range_check, + ) + TZ_HH_LIMIT = Limit( + "Invalid timezone hour string.", + 0, + 23, + HoursOutOfBoundsError, + "Hour must be between 0..23.", + range_check, + ) + TZ_MM_LIMIT = Limit( + "Invalid timezone minute string.", + 0, + 59, + MinutesOutOfBoundsError, + "Minute must be between 0..59.", + range_check, + ) + DURATION_PNY_LIMIT = Limit( + "Invalid year duration string.", + 0, + None, + ISOFormatError, + "Duration years component must be positive.", + range_check, + ) + DURATION_PNM_LIMIT = Limit( + "Invalid month duration string.", + 0, + None, + ISOFormatError, + "Duration months component must be positive.", + range_check, + ) + DURATION_PNW_LIMIT = Limit( + "Invalid week duration string.", + 0, + None, + ISOFormatError, + "Duration weeks component must be positive.", + range_check, + ) + DURATION_PND_LIMIT = Limit( + "Invalid day duration string.", + 0, + None, + ISOFormatError, + "Duration days component must be positive.", + range_check, + ) + DURATION_TNH_LIMIT = Limit( + "Invalid hour duration string.", + 0, + None, + ISOFormatError, + "Duration hours component must be positive.", + range_check, + ) + DURATION_TNM_LIMIT = Limit( + "Invalid minute duration string.", + 0, + None, + ISOFormatError, + "Duration minutes component must be positive.", + range_check, + ) + DURATION_TNS_LIMIT = Limit( + "Invalid second duration string.", + 0, + None, + ISOFormatError, + "Duration seconds component must be positive.", + range_check, + ) + INTERVAL_RNN_LIMIT = Limit( + "Invalid duration repetition string.", + 0, + None, + ISOFormatError, + "Duration repetition count must be positive.", + range_check, + ) + + DATE_RANGE_DICT = { + "YYYY": DATE_YYYY_LIMIT, + "MM": DATE_MM_LIMIT, + "DD": DATE_DD_LIMIT, + "Www": DATE_WWW_LIMIT, + "D": DATE_D_LIMIT, + "DDD": DATE_DDD_LIMIT, + } + + TIME_RANGE_DICT = {"hh": TIME_HH_LIMIT, "mm": TIME_MM_LIMIT, "ss": TIME_SS_LIMIT} + + DURATION_RANGE_DICT = { + "PnY": DURATION_PNY_LIMIT, + "PnM": DURATION_PNM_LIMIT, + "PnW": DURATION_PNW_LIMIT, + "PnD": DURATION_PND_LIMIT, + "TnH": DURATION_TNH_LIMIT, + "TnM": DURATION_TNM_LIMIT, + "TnS": DURATION_TNS_LIMIT, + } + + REPEATING_INTERVAL_RANGE_DICT = {"Rnn": INTERVAL_RNN_LIMIT} + + TIMEZONE_RANGE_DICT = {"hh": TZ_HH_LIMIT, "mm": TZ_MM_LIMIT} + + LEAP_SECONDS_SUPPORTED = False + + @classmethod + def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): + raise NotImplementedError + + @classmethod + def build_time(cls, hh=None, mm=None, ss=None, tz=None): + raise NotImplementedError + + @classmethod + def build_datetime(cls, date, time): + raise NotImplementedError + + @classmethod + def build_duration( + cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None + ): + raise NotImplementedError + + @classmethod + def build_interval(cls, start=None, end=None, duration=None): + # start, end, and duration are all tuples + raise NotImplementedError + + @classmethod + def build_repeating_interval(cls, R=None, Rnn=None, interval=None): + # interval is a tuple + raise NotImplementedError + + @classmethod + def build_timezone(cls, negative=None, Z=None, hh=None, mm=None, name=""): + raise NotImplementedError + + @classmethod + def range_check_date( + cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None, rangedict=None + ): + if rangedict is None: + rangedict = cls.DATE_RANGE_DICT + + if "YYYY" in rangedict: + YYYY = rangedict["YYYY"].rangefunc(YYYY, rangedict["YYYY"]) + + if "MM" in rangedict: + MM = rangedict["MM"].rangefunc(MM, rangedict["MM"]) + + if "DD" in rangedict: + DD = rangedict["DD"].rangefunc(DD, rangedict["DD"]) + + if "Www" in rangedict: + Www = rangedict["Www"].rangefunc(Www, rangedict["Www"]) + + if "D" in rangedict: + D = rangedict["D"].rangefunc(D, rangedict["D"]) + + if "DDD" in rangedict: + DDD = rangedict["DDD"].rangefunc(DDD, rangedict["DDD"]) + + if DD is not None: + # Check calendar + if DD > calendar.monthrange(YYYY, MM)[1]: + raise DayOutOfBoundsError( + "{0} is out of range for {1}-{2}".format(DD, YYYY, MM) + ) + + if DDD is not None: + if calendar.isleap(YYYY) is False and DDD == 366: + raise DayOutOfBoundsError( + "{0} is only valid for leap year.".format(DDD) + ) + + return (YYYY, MM, DD, Www, D, DDD) + + @classmethod + def range_check_time(cls, hh=None, mm=None, ss=None, tz=None, rangedict=None): + # Used for midnight and leap second handling + midnight = False # Handle hh = '24' specially + + if rangedict is None: + rangedict = cls.TIME_RANGE_DICT + + if "hh" in rangedict: + try: + hh = rangedict["hh"].rangefunc(hh, rangedict["hh"]) + except HoursOutOfBoundsError as e: + if float(hh) > 24 and float(hh) < 25: + raise MidnightBoundsError("Hour 24 may only represent midnight.") + + raise e + + if "mm" in rangedict: + mm = rangedict["mm"].rangefunc(mm, rangedict["mm"]) + + if "ss" in rangedict: + ss = rangedict["ss"].rangefunc(ss, rangedict["ss"]) + + if hh is not None and hh == 24: + midnight = True + + # Handle midnight range + if midnight is True and ( + (mm is not None and mm != 0) or (ss is not None and ss != 0) + ): + raise MidnightBoundsError("Hour 24 may only represent midnight.") + + if cls.LEAP_SECONDS_SUPPORTED is True: + if hh != 23 and mm != 59 and ss == 60: + raise cls.TIME_SS_LIMIT.rangeexception( + cls.TIME_SS_LIMIT.rangeerrorstring + ) + else: + if hh == 23 and mm == 59 and ss == 60: + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + raise LeapSecondError("Leap seconds are not supported.") + + if ss == 60: + raise cls.TIME_SS_LIMIT.rangeexception( + cls.TIME_SS_LIMIT.rangeerrorstring + ) + + return (hh, mm, ss, tz) + + @classmethod + def range_check_duration( + cls, + PnY=None, + PnM=None, + PnW=None, + PnD=None, + TnH=None, + TnM=None, + TnS=None, + rangedict=None, + ): + if rangedict is None: + rangedict = cls.DURATION_RANGE_DICT + + if "PnY" in rangedict: + PnY = rangedict["PnY"].rangefunc(PnY, rangedict["PnY"]) + + if "PnM" in rangedict: + PnM = rangedict["PnM"].rangefunc(PnM, rangedict["PnM"]) + + if "PnW" in rangedict: + PnW = rangedict["PnW"].rangefunc(PnW, rangedict["PnW"]) + + if "PnD" in rangedict: + PnD = rangedict["PnD"].rangefunc(PnD, rangedict["PnD"]) + + if "TnH" in rangedict: + TnH = rangedict["TnH"].rangefunc(TnH, rangedict["TnH"]) + + if "TnM" in rangedict: + TnM = rangedict["TnM"].rangefunc(TnM, rangedict["TnM"]) + + if "TnS" in rangedict: + TnS = rangedict["TnS"].rangefunc(TnS, rangedict["TnS"]) + + return (PnY, PnM, PnW, PnD, TnH, TnM, TnS) + + @classmethod + def range_check_repeating_interval( + cls, R=None, Rnn=None, interval=None, rangedict=None + ): + if rangedict is None: + rangedict = cls.REPEATING_INTERVAL_RANGE_DICT + + if "Rnn" in rangedict: + Rnn = rangedict["Rnn"].rangefunc(Rnn, rangedict["Rnn"]) + + return (R, Rnn, interval) + + @classmethod + def range_check_timezone( + cls, negative=None, Z=None, hh=None, mm=None, name="", rangedict=None + ): + if rangedict is None: + rangedict = cls.TIMEZONE_RANGE_DICT + + if "hh" in rangedict: + hh = rangedict["hh"].rangefunc(hh, rangedict["hh"]) + + if "mm" in rangedict: + mm = rangedict["mm"].rangefunc(mm, rangedict["mm"]) + + return (negative, Z, hh, mm, name) + + @classmethod + def _build_object(cls, parsetuple): + # Given a TupleBuilder tuple, build the correct object + if isinstance(parsetuple, DateTuple): + return cls.build_date( + YYYY=parsetuple.YYYY, + MM=parsetuple.MM, + DD=parsetuple.DD, + Www=parsetuple.Www, + D=parsetuple.D, + DDD=parsetuple.DDD, + ) + + if isinstance(parsetuple, TimeTuple): + return cls.build_time( + hh=parsetuple.hh, mm=parsetuple.mm, ss=parsetuple.ss, tz=parsetuple.tz + ) + + if isinstance(parsetuple, DatetimeTuple): + return cls.build_datetime(parsetuple.date, parsetuple.time) + + if isinstance(parsetuple, DurationTuple): + return cls.build_duration( + PnY=parsetuple.PnY, + PnM=parsetuple.PnM, + PnW=parsetuple.PnW, + PnD=parsetuple.PnD, + TnH=parsetuple.TnH, + TnM=parsetuple.TnM, + TnS=parsetuple.TnS, + ) + + if isinstance(parsetuple, IntervalTuple): + return cls.build_interval( + start=parsetuple.start, end=parsetuple.end, duration=parsetuple.duration + ) + + if isinstance(parsetuple, RepeatingIntervalTuple): + return cls.build_repeating_interval( + R=parsetuple.R, Rnn=parsetuple.Rnn, interval=parsetuple.interval + ) + + return cls.build_timezone( + negative=parsetuple.negative, + Z=parsetuple.Z, + hh=parsetuple.hh, + mm=parsetuple.mm, + name=parsetuple.name, + ) + + @classmethod + def _is_interval_end_concise(cls, endtuple): + if isinstance(endtuple, TimeTuple): + return True + + if isinstance(endtuple, DatetimeTuple): + enddatetuple = endtuple.date + else: + enddatetuple = endtuple + + if enddatetuple.YYYY is None: + return True + + return False + + @classmethod + def _combine_concise_interval_tuples(cls, starttuple, conciseendtuple): + starttimetuple = None + startdatetuple = None + + endtimetuple = None + enddatetuple = None + + if isinstance(starttuple, DateTuple): + startdatetuple = starttuple + else: + # Start is a datetime + starttimetuple = starttuple.time + startdatetuple = starttuple.date + + if isinstance(conciseendtuple, DateTuple): + enddatetuple = conciseendtuple + elif isinstance(conciseendtuple, DatetimeTuple): + enddatetuple = conciseendtuple.date + endtimetuple = conciseendtuple.time + else: + # Time + endtimetuple = conciseendtuple + + if enddatetuple is not None: + if enddatetuple.YYYY is None and enddatetuple.MM is None: + newenddatetuple = DateTuple( + YYYY=startdatetuple.YYYY, + MM=startdatetuple.MM, + DD=enddatetuple.DD, + Www=enddatetuple.Www, + D=enddatetuple.D, + DDD=enddatetuple.DDD, + ) + else: + newenddatetuple = DateTuple( + YYYY=startdatetuple.YYYY, + MM=enddatetuple.MM, + DD=enddatetuple.DD, + Www=enddatetuple.Www, + D=enddatetuple.D, + DDD=enddatetuple.DDD, + ) + + if endtimetuple is None: + return newenddatetuple + + if (starttimetuple is not None and starttimetuple.tz is not None) and ( + endtimetuple is not None and endtimetuple.tz != starttimetuple.tz + ): + # Copy the timezone across + endtimetuple = TimeTuple( + hh=endtimetuple.hh, + mm=endtimetuple.mm, + ss=endtimetuple.ss, + tz=starttimetuple.tz, + ) + + if enddatetuple is not None and endtimetuple is not None: + return TupleBuilder.build_datetime(newenddatetuple, endtimetuple) + + return TupleBuilder.build_datetime(startdatetuple, endtimetuple) + + +class TupleBuilder(BaseTimeBuilder): + # Builder used to return the arguments as a tuple, cleans up some parse methods + @classmethod + def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): + + return DateTuple(YYYY, MM, DD, Www, D, DDD) + + @classmethod + def build_time(cls, hh=None, mm=None, ss=None, tz=None): + return TimeTuple(hh, mm, ss, tz) + + @classmethod + def build_datetime(cls, date, time): + return DatetimeTuple(date, time) + + @classmethod + def build_duration( + cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None + ): + + return DurationTuple(PnY, PnM, PnW, PnD, TnH, TnM, TnS) + + @classmethod + def build_interval(cls, start=None, end=None, duration=None): + return IntervalTuple(start, end, duration) + + @classmethod + def build_repeating_interval(cls, R=None, Rnn=None, interval=None): + return RepeatingIntervalTuple(R, Rnn, interval) + + @classmethod + def build_timezone(cls, negative=None, Z=None, hh=None, mm=None, name=""): + return TimezoneTuple(negative, Z, hh, mm, name) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__pycache__/__init__.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..16e49e49 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__pycache__/__init__.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__pycache__/python.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__pycache__/python.cpython-312.pyc new file mode 100644 index 00000000..3ef4c87e Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/__pycache__/python.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/python.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/python.py new file mode 100644 index 00000000..b60382e1 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/python.py @@ -0,0 +1,700 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import datetime +from collections import namedtuple +from functools import partial + +from aniso8601.builders import ( + BaseTimeBuilder, + DateTuple, + Limit, + TupleBuilder, + cast, + range_check, +) +from aniso8601.exceptions import ( + DayOutOfBoundsError, + HoursOutOfBoundsError, + MinutesOutOfBoundsError, + MonthOutOfBoundsError, + SecondsOutOfBoundsError, + WeekOutOfBoundsError, + YearOutOfBoundsError, +) +from aniso8601.utcoffset import UTCOffset + +DAYS_PER_YEAR = 365 +DAYS_PER_MONTH = 30 +DAYS_PER_WEEK = 7 + +HOURS_PER_DAY = 24 + +MINUTES_PER_HOUR = 60 +MINUTES_PER_DAY = MINUTES_PER_HOUR * HOURS_PER_DAY + +SECONDS_PER_MINUTE = 60 +SECONDS_PER_DAY = MINUTES_PER_DAY * SECONDS_PER_MINUTE + +MICROSECONDS_PER_SECOND = int(1e6) + +MICROSECONDS_PER_MINUTE = 60 * MICROSECONDS_PER_SECOND +MICROSECONDS_PER_HOUR = 60 * MICROSECONDS_PER_MINUTE +MICROSECONDS_PER_DAY = 24 * MICROSECONDS_PER_HOUR +MICROSECONDS_PER_WEEK = 7 * MICROSECONDS_PER_DAY +MICROSECONDS_PER_MONTH = DAYS_PER_MONTH * MICROSECONDS_PER_DAY +MICROSECONDS_PER_YEAR = DAYS_PER_YEAR * MICROSECONDS_PER_DAY + +TIMEDELTA_MAX_DAYS = datetime.timedelta.max.days + +FractionalComponent = namedtuple( + "FractionalComponent", ["principal", "microsecondremainder"] +) + + +def year_range_check(valuestr, limit): + YYYYstr = valuestr + + # Truncated dates, like '19', refer to 1900-1999 inclusive, + # we simply parse to 1900 + if len(valuestr) < 4: + # Shift 0s in from the left to form complete year + YYYYstr = valuestr.ljust(4, "0") + + return range_check(YYYYstr, limit) + + +def fractional_range_check(conversion, valuestr, limit): + if valuestr is None: + return None + + if "." in valuestr: + castfunc = partial(_cast_to_fractional_component, conversion) + else: + castfunc = int + + value = cast(valuestr, castfunc, thrownmessage=limit.casterrorstring) + + if isinstance(value, FractionalComponent): + tocheck = float(valuestr) + else: + tocheck = int(valuestr) + + if limit.min is not None and tocheck < limit.min: + raise limit.rangeexception(limit.rangeerrorstring) + + if limit.max is not None and tocheck > limit.max: + raise limit.rangeexception(limit.rangeerrorstring) + + return value + + +def _cast_to_fractional_component(conversion, floatstr): + # Splits a string with a decimal point into an int, and + # int representing the floating point remainder as a number + # of microseconds, determined by multiplying by conversion + intpart, floatpart = floatstr.split(".") + + intvalue = int(intpart) + preconvertedvalue = int(floatpart) + + convertedvalue = (preconvertedvalue * conversion) // (10 ** len(floatpart)) + + return FractionalComponent(intvalue, convertedvalue) + + +class PythonTimeBuilder(BaseTimeBuilder): + # 0000 (1 BC) is not representable as a Python date + DATE_YYYY_LIMIT = Limit( + "Invalid year string.", + datetime.MINYEAR, + datetime.MAXYEAR, + YearOutOfBoundsError, + "Year must be between {0}..{1}.".format(datetime.MINYEAR, datetime.MAXYEAR), + year_range_check, + ) + TIME_HH_LIMIT = Limit( + "Invalid hour string.", + 0, + 24, + HoursOutOfBoundsError, + "Hour must be between 0..24 with 24 representing midnight.", + partial(fractional_range_check, MICROSECONDS_PER_HOUR), + ) + TIME_MM_LIMIT = Limit( + "Invalid minute string.", + 0, + 59, + MinutesOutOfBoundsError, + "Minute must be between 0..59.", + partial(fractional_range_check, MICROSECONDS_PER_MINUTE), + ) + TIME_SS_LIMIT = Limit( + "Invalid second string.", + 0, + 60, + SecondsOutOfBoundsError, + "Second must be between 0..60 with 60 representing a leap second.", + partial(fractional_range_check, MICROSECONDS_PER_SECOND), + ) + DURATION_PNY_LIMIT = Limit( + "Invalid year duration string.", + None, + None, + YearOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_YEAR), + ) + DURATION_PNM_LIMIT = Limit( + "Invalid month duration string.", + None, + None, + MonthOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_MONTH), + ) + DURATION_PNW_LIMIT = Limit( + "Invalid week duration string.", + None, + None, + WeekOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_WEEK), + ) + DURATION_PND_LIMIT = Limit( + "Invalid day duration string.", + None, + None, + DayOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_DAY), + ) + DURATION_TNH_LIMIT = Limit( + "Invalid hour duration string.", + None, + None, + HoursOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_HOUR), + ) + DURATION_TNM_LIMIT = Limit( + "Invalid minute duration string.", + None, + None, + MinutesOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_MINUTE), + ) + DURATION_TNS_LIMIT = Limit( + "Invalid second duration string.", + None, + None, + SecondsOutOfBoundsError, + None, + partial(fractional_range_check, MICROSECONDS_PER_SECOND), + ) + + DATE_RANGE_DICT = BaseTimeBuilder.DATE_RANGE_DICT + DATE_RANGE_DICT["YYYY"] = DATE_YYYY_LIMIT + + TIME_RANGE_DICT = {"hh": TIME_HH_LIMIT, "mm": TIME_MM_LIMIT, "ss": TIME_SS_LIMIT} + + DURATION_RANGE_DICT = { + "PnY": DURATION_PNY_LIMIT, + "PnM": DURATION_PNM_LIMIT, + "PnW": DURATION_PNW_LIMIT, + "PnD": DURATION_PND_LIMIT, + "TnH": DURATION_TNH_LIMIT, + "TnM": DURATION_TNM_LIMIT, + "TnS": DURATION_TNS_LIMIT, + } + + @classmethod + def build_date(cls, YYYY=None, MM=None, DD=None, Www=None, D=None, DDD=None): + YYYY, MM, DD, Www, D, DDD = cls.range_check_date(YYYY, MM, DD, Www, D, DDD) + + if MM is None: + MM = 1 + + if DD is None: + DD = 1 + + if DDD is not None: + return PythonTimeBuilder._build_ordinal_date(YYYY, DDD) + + if Www is not None: + return PythonTimeBuilder._build_week_date(YYYY, Www, isoday=D) + + return datetime.date(YYYY, MM, DD) + + @classmethod + def build_time(cls, hh=None, mm=None, ss=None, tz=None): + # Builds a time from the given parts, handling fractional arguments + # where necessary + hours = 0 + minutes = 0 + seconds = 0 + microseconds = 0 + + hh, mm, ss, tz = cls.range_check_time(hh, mm, ss, tz) + + if isinstance(hh, FractionalComponent): + hours = hh.principal + microseconds = hh.microsecondremainder + elif hh is not None: + hours = hh + + if isinstance(mm, FractionalComponent): + minutes = mm.principal + microseconds = mm.microsecondremainder + elif mm is not None: + minutes = mm + + if isinstance(ss, FractionalComponent): + seconds = ss.principal + microseconds = ss.microsecondremainder + elif ss is not None: + seconds = ss + + ( + hours, + minutes, + seconds, + microseconds, + ) = PythonTimeBuilder._distribute_microseconds( + microseconds, + (hours, minutes, seconds), + (MICROSECONDS_PER_HOUR, MICROSECONDS_PER_MINUTE, MICROSECONDS_PER_SECOND), + ) + + # Move midnight into range + if hours == 24: + hours = 0 + + # Datetimes don't handle fractional components, so we use a timedelta + if tz is not None: + return ( + datetime.datetime( + 1, 1, 1, hour=hours, minute=minutes, tzinfo=cls._build_object(tz) + ) + + datetime.timedelta(seconds=seconds, microseconds=microseconds) + ).timetz() + + return ( + datetime.datetime(1, 1, 1, hour=hours, minute=minutes) + + datetime.timedelta(seconds=seconds, microseconds=microseconds) + ).time() + + @classmethod + def build_datetime(cls, date, time): + return datetime.datetime.combine( + cls._build_object(date), cls._build_object(time) + ) + + @classmethod + def build_duration( + cls, PnY=None, PnM=None, PnW=None, PnD=None, TnH=None, TnM=None, TnS=None + ): + # PnY and PnM will be distributed to PnD, microsecond remainder to TnS + PnY, PnM, PnW, PnD, TnH, TnM, TnS = cls.range_check_duration( + PnY, PnM, PnW, PnD, TnH, TnM, TnS + ) + + seconds = TnS.principal + microseconds = TnS.microsecondremainder + + return datetime.timedelta( + days=PnD, + seconds=seconds, + microseconds=microseconds, + minutes=TnM, + hours=TnH, + weeks=PnW, + ) + + @classmethod + def build_interval(cls, start=None, end=None, duration=None): + start, end, duration = cls.range_check_interval(start, end, duration) + + if start is not None and end is not None: + # / + startobject = cls._build_object(start) + endobject = cls._build_object(end) + + return (startobject, endobject) + + durationobject = cls._build_object(duration) + + # Determine if datetime promotion is required + datetimerequired = ( + duration.TnH is not None + or duration.TnM is not None + or duration.TnS is not None + or durationobject.seconds != 0 + or durationobject.microseconds != 0 + ) + + if end is not None: + # / + endobject = cls._build_object(end) + + # Range check + if isinstance(end, DateTuple) and datetimerequired is True: + # is a date, and requires datetime resolution + return ( + endobject, + cls.build_datetime(end, TupleBuilder.build_time()) - durationobject, + ) + + return (endobject, endobject - durationobject) + + # / + startobject = cls._build_object(start) + + # Range check + if isinstance(start, DateTuple) and datetimerequired is True: + # is a date, and requires datetime resolution + return ( + startobject, + cls.build_datetime(start, TupleBuilder.build_time()) + durationobject, + ) + + return (startobject, startobject + durationobject) + + @classmethod + def build_repeating_interval(cls, R=None, Rnn=None, interval=None): + startobject = None + endobject = None + + R, Rnn, interval = cls.range_check_repeating_interval(R, Rnn, interval) + + if interval.start is not None: + startobject = cls._build_object(interval.start) + + if interval.end is not None: + endobject = cls._build_object(interval.end) + + if interval.duration is not None: + durationobject = cls._build_object(interval.duration) + else: + durationobject = endobject - startobject + + if R is True: + if startobject is not None: + return cls._date_generator_unbounded(startobject, durationobject) + + return cls._date_generator_unbounded(endobject, -durationobject) + + iterations = int(Rnn) + + if startobject is not None: + return cls._date_generator(startobject, durationobject, iterations) + + return cls._date_generator(endobject, -durationobject, iterations) + + @classmethod + def build_timezone(cls, negative=None, Z=None, hh=None, mm=None, name=""): + negative, Z, hh, mm, name = cls.range_check_timezone(negative, Z, hh, mm, name) + + if Z is True: + # Z -> UTC + return UTCOffset(name="UTC", minutes=0) + + tzhour = int(hh) + + if mm is not None: + tzminute = int(mm) + else: + tzminute = 0 + + if negative is True: + return UTCOffset(name=name, minutes=-(tzhour * 60 + tzminute)) + + return UTCOffset(name=name, minutes=tzhour * 60 + tzminute) + + @classmethod + def range_check_duration( + cls, + PnY=None, + PnM=None, + PnW=None, + PnD=None, + TnH=None, + TnM=None, + TnS=None, + rangedict=None, + ): + years = 0 + months = 0 + days = 0 + weeks = 0 + hours = 0 + minutes = 0 + seconds = 0 + microseconds = 0 + + PnY, PnM, PnW, PnD, TnH, TnM, TnS = BaseTimeBuilder.range_check_duration( + PnY, PnM, PnW, PnD, TnH, TnM, TnS, rangedict=cls.DURATION_RANGE_DICT + ) + + if PnY is not None: + if isinstance(PnY, FractionalComponent): + years = PnY.principal + microseconds = PnY.microsecondremainder + else: + years = PnY + + if years * DAYS_PER_YEAR > TIMEDELTA_MAX_DAYS: + raise YearOutOfBoundsError("Duration exceeds maximum timedelta size.") + + if PnM is not None: + if isinstance(PnM, FractionalComponent): + months = PnM.principal + microseconds = PnM.microsecondremainder + else: + months = PnM + + if months * DAYS_PER_MONTH > TIMEDELTA_MAX_DAYS: + raise MonthOutOfBoundsError("Duration exceeds maximum timedelta size.") + + if PnW is not None: + if isinstance(PnW, FractionalComponent): + weeks = PnW.principal + microseconds = PnW.microsecondremainder + else: + weeks = PnW + + if weeks * DAYS_PER_WEEK > TIMEDELTA_MAX_DAYS: + raise WeekOutOfBoundsError("Duration exceeds maximum timedelta size.") + + if PnD is not None: + if isinstance(PnD, FractionalComponent): + days = PnD.principal + microseconds = PnD.microsecondremainder + else: + days = PnD + + if days > TIMEDELTA_MAX_DAYS: + raise DayOutOfBoundsError("Duration exceeds maximum timedelta size.") + + if TnH is not None: + if isinstance(TnH, FractionalComponent): + hours = TnH.principal + microseconds = TnH.microsecondremainder + else: + hours = TnH + + if hours // HOURS_PER_DAY > TIMEDELTA_MAX_DAYS: + raise HoursOutOfBoundsError("Duration exceeds maximum timedelta size.") + + if TnM is not None: + if isinstance(TnM, FractionalComponent): + minutes = TnM.principal + microseconds = TnM.microsecondremainder + else: + minutes = TnM + + if minutes // MINUTES_PER_DAY > TIMEDELTA_MAX_DAYS: + raise MinutesOutOfBoundsError( + "Duration exceeds maximum timedelta size." + ) + + if TnS is not None: + if isinstance(TnS, FractionalComponent): + seconds = TnS.principal + microseconds = TnS.microsecondremainder + else: + seconds = TnS + + if seconds // SECONDS_PER_DAY > TIMEDELTA_MAX_DAYS: + raise SecondsOutOfBoundsError( + "Duration exceeds maximum timedelta size." + ) + + ( + years, + months, + weeks, + days, + hours, + minutes, + seconds, + microseconds, + ) = PythonTimeBuilder._distribute_microseconds( + microseconds, + (years, months, weeks, days, hours, minutes, seconds), + ( + MICROSECONDS_PER_YEAR, + MICROSECONDS_PER_MONTH, + MICROSECONDS_PER_WEEK, + MICROSECONDS_PER_DAY, + MICROSECONDS_PER_HOUR, + MICROSECONDS_PER_MINUTE, + MICROSECONDS_PER_SECOND, + ), + ) + + # Note that weeks can be handled without conversion to days + totaldays = years * DAYS_PER_YEAR + months * DAYS_PER_MONTH + days + + # Check against timedelta limits + if ( + totaldays + + weeks * DAYS_PER_WEEK + + hours // HOURS_PER_DAY + + minutes // MINUTES_PER_DAY + + seconds // SECONDS_PER_DAY + > TIMEDELTA_MAX_DAYS + ): + raise DayOutOfBoundsError("Duration exceeds maximum timedelta size.") + + return ( + None, + None, + weeks, + totaldays, + hours, + minutes, + FractionalComponent(seconds, microseconds), + ) + + @classmethod + def range_check_interval(cls, start=None, end=None, duration=None): + # Handles concise format, range checks any potential durations + if start is not None and end is not None: + # / + # Handle concise format + if cls._is_interval_end_concise(end) is True: + end = cls._combine_concise_interval_tuples(start, end) + + return (start, end, duration) + + durationobject = cls._build_object(duration) + + if end is not None: + # / + endobject = cls._build_object(end) + + # Range check + if isinstance(end, DateTuple): + enddatetime = cls.build_datetime(end, TupleBuilder.build_time()) + + if enddatetime - datetime.datetime.min < durationobject: + raise YearOutOfBoundsError("Interval end less than minimium date.") + else: + mindatetime = datetime.datetime.min + + if end.time.tz is not None: + mindatetime = mindatetime.replace(tzinfo=endobject.tzinfo) + + if endobject - mindatetime < durationobject: + raise YearOutOfBoundsError("Interval end less than minimium date.") + else: + # / + startobject = cls._build_object(start) + + # Range check + if type(start) is DateTuple: + startdatetime = cls.build_datetime(start, TupleBuilder.build_time()) + + if datetime.datetime.max - startdatetime < durationobject: + raise YearOutOfBoundsError( + "Interval end greater than maximum date." + ) + else: + maxdatetime = datetime.datetime.max + + if start.time.tz is not None: + maxdatetime = maxdatetime.replace(tzinfo=startobject.tzinfo) + + if maxdatetime - startobject < durationobject: + raise YearOutOfBoundsError( + "Interval end greater than maximum date." + ) + + return (start, end, duration) + + @staticmethod + def _build_week_date(isoyear, isoweek, isoday=None): + if isoday is None: + return PythonTimeBuilder._iso_year_start(isoyear) + datetime.timedelta( + weeks=isoweek - 1 + ) + + return PythonTimeBuilder._iso_year_start(isoyear) + datetime.timedelta( + weeks=isoweek - 1, days=isoday - 1 + ) + + @staticmethod + def _build_ordinal_date(isoyear, isoday): + # Day of year to a date + # https://stackoverflow.com/questions/2427555/python-question-year-and-day-of-year-to-date + builtdate = datetime.date(isoyear, 1, 1) + datetime.timedelta(days=isoday - 1) + + return builtdate + + @staticmethod + def _iso_year_start(isoyear): + # Given an ISO year, returns the equivalent of the start of the year + # on the Gregorian calendar (which is used by Python) + # Stolen from: + # http://stackoverflow.com/questions/304256/whats-the-best-way-to-find-the-inverse-of-datetime-isocalendar + + # Determine the location of the 4th of January, the first week of + # the ISO year is the week containing the 4th of January + # http://en.wikipedia.org/wiki/ISO_week_date + fourth_jan = datetime.date(isoyear, 1, 4) + + # Note the conversion from ISO day (1 - 7) and Python day (0 - 6) + delta = datetime.timedelta(days=fourth_jan.isoweekday() - 1) + + # Return the start of the year + return fourth_jan - delta + + @staticmethod + def _date_generator(startdate, timedelta, iterations): + currentdate = startdate + currentiteration = 0 + + while currentiteration < iterations: + yield currentdate + + # Update the values + currentdate += timedelta + currentiteration += 1 + + @staticmethod + def _date_generator_unbounded(startdate, timedelta): + currentdate = startdate + + while True: + yield currentdate + + # Update the value + currentdate += timedelta + + @staticmethod + def _distribute_microseconds(todistribute, recipients, reductions): + # Given a number of microseconds as int, a tuple of ints length n + # to distribute to, and a tuple of ints length n to divide todistribute + # by (from largest to smallest), returns a tuple of length n + 1, with + # todistribute divided across recipients using the reductions, with + # the final remainder returned as the final tuple member + results = [] + + remainder = todistribute + + for index, reduction in enumerate(reductions): + additional, remainder = divmod(remainder, reduction) + + results.append(recipients[index] + additional) + + # Always return the remaining microseconds + results.append(remainder) + + return tuple(results) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__init__.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__init__.py new file mode 100644 index 00000000..5cfededc --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__init__.py @@ -0,0 +1,7 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/__init__.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/__init__.cpython-312.pyc new file mode 100644 index 00000000..e42831ec Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/__init__.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/test_init.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/test_init.cpython-312.pyc new file mode 100644 index 00000000..bd97e417 Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/test_init.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/test_python.cpython-312.pyc b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/test_python.cpython-312.pyc new file mode 100644 index 00000000..5679f4fa Binary files /dev/null and b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/__pycache__/test_python.cpython-312.pyc differ diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/test_init.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/test_init.py new file mode 100644 index 00000000..0a6ed1aa --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/test_init.py @@ -0,0 +1,838 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import unittest + +import aniso8601 +from aniso8601.builders import ( + BaseTimeBuilder, + DatetimeTuple, + DateTuple, + DurationTuple, + IntervalTuple, + RepeatingIntervalTuple, + TimeTuple, + TimezoneTuple, + TupleBuilder, + cast, +) +from aniso8601.exceptions import ( + DayOutOfBoundsError, + HoursOutOfBoundsError, + ISOFormatError, + LeapSecondError, + MidnightBoundsError, + MinutesOutOfBoundsError, + MonthOutOfBoundsError, + SecondsOutOfBoundsError, + WeekOutOfBoundsError, +) +from aniso8601.tests.compat import mock + + +class LeapSecondSupportingTestBuilder(BaseTimeBuilder): + LEAP_SECONDS_SUPPORTED = True + + +class TestBuilderFunctions(unittest.TestCase): + def test_cast(self): + self.assertEqual(cast("1", int), 1) + self.assertEqual(cast("-2", int), -2) + self.assertEqual(cast("3", float), float(3)) + self.assertEqual(cast("-4", float), float(-4)) + self.assertEqual(cast("5.6", float), 5.6) + self.assertEqual(cast("-7.8", float), -7.8) + + def test_cast_exception(self): + with self.assertRaises(ISOFormatError): + cast("asdf", int) + + with self.assertRaises(ISOFormatError): + cast("asdf", float) + + def test_cast_caughtexception(self): + def tester(value): + raise RuntimeError + + with self.assertRaises(ISOFormatError): + cast("asdf", tester, caughtexceptions=(RuntimeError,)) + + def test_cast_thrownexception(self): + with self.assertRaises(RuntimeError): + cast("asdf", int, thrownexception=RuntimeError) + + +class TestBaseTimeBuilder(unittest.TestCase): + def test_build_date(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_date() + + def test_build_time(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_time() + + def test_build_datetime(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_datetime(None, None) + + def test_build_duration(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_duration() + + def test_build_interval(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_interval() + + def test_build_repeating_interval(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_repeating_interval() + + def test_build_timezone(self): + with self.assertRaises(NotImplementedError): + BaseTimeBuilder.build_timezone() + + def test_range_check_date(self): + # Check the calendar for day ranges + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="0007", MM="02", DD="30") + + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="0007", DDD="366") + + with self.assertRaises(MonthOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="4333", MM="30", DD="30") + + # 0 isn't a valid week number + with self.assertRaises(WeekOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="2003", Www="00") + + # Week must not be larger than 53 + with self.assertRaises(WeekOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="2004", Www="54") + + # 0 isn't a valid day number + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="2001", Www="02", D="0") + + # Day must not be larger than 7 + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="2001", Www="02", D="8") + + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="1981", DDD="000") + + # Day must be 365, or 366, not larger + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="1234", DDD="000") + + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="1234", DDD="367") + + # https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow + with self.assertRaises(DayOutOfBoundsError): + BaseTimeBuilder.range_check_date(YYYY="1981", DDD="366") + + # Make sure Nones pass through unmodified + self.assertEqual( + BaseTimeBuilder.range_check_date(rangedict={}), + (None, None, None, None, None, None), + ) + + def test_range_check_time(self): + # Leap seconds not supported + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + # https://bitbucket.org/nielsenb/aniso8601/issues/13/parsing-of-leap-second-gives-wildly + with self.assertRaises(LeapSecondError): + BaseTimeBuilder.range_check_time(hh="23", mm="59", ss="60") + + with self.assertRaises(SecondsOutOfBoundsError): + BaseTimeBuilder.range_check_time(hh="00", mm="00", ss="60") + + with self.assertRaises(SecondsOutOfBoundsError): + BaseTimeBuilder.range_check_time(hh="00", mm="00", ss="61") + + with self.assertRaises(MinutesOutOfBoundsError): + BaseTimeBuilder.range_check_time(hh="00", mm="61") + + with self.assertRaises(MinutesOutOfBoundsError): + BaseTimeBuilder.range_check_time(hh="00", mm="60") + + with self.assertRaises(MinutesOutOfBoundsError): + BaseTimeBuilder.range_check_time(hh="00", mm="60.1") + + with self.assertRaises(HoursOutOfBoundsError): + BaseTimeBuilder.range_check_time(hh="25") + + # Hour 24 can only represent midnight + with self.assertRaises(MidnightBoundsError): + BaseTimeBuilder.range_check_time(hh="24", mm="00", ss="01") + + with self.assertRaises(MidnightBoundsError): + BaseTimeBuilder.range_check_time(hh="24", mm="00.1") + + with self.assertRaises(MidnightBoundsError): + BaseTimeBuilder.range_check_time(hh="24", mm="01") + + with self.assertRaises(MidnightBoundsError): + BaseTimeBuilder.range_check_time(hh="24.1") + + # Leap seconds not supported + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + # https://bitbucket.org/nielsenb/aniso8601/issues/13/parsing-of-leap-second-gives-wildly + with self.assertRaises(LeapSecondError): + BaseTimeBuilder.range_check_time(hh="23", mm="59", ss="60") + + # Make sure Nones pass through unmodified + self.assertEqual( + BaseTimeBuilder.range_check_time(rangedict={}), (None, None, None, None) + ) + + def test_range_check_time_leap_seconds_supported(self): + self.assertEqual( + LeapSecondSupportingTestBuilder.range_check_time(hh="23", mm="59", ss="60"), + (23, 59, 60, None), + ) + + with self.assertRaises(SecondsOutOfBoundsError): + LeapSecondSupportingTestBuilder.range_check_time(hh="01", mm="02", ss="60") + + def test_range_check_duration(self): + self.assertEqual( + BaseTimeBuilder.range_check_duration(), + (None, None, None, None, None, None, None), + ) + + self.assertEqual( + BaseTimeBuilder.range_check_duration(rangedict={}), + (None, None, None, None, None, None, None), + ) + + def test_range_check_repeating_interval(self): + self.assertEqual( + BaseTimeBuilder.range_check_repeating_interval(), (None, None, None) + ) + + self.assertEqual( + BaseTimeBuilder.range_check_repeating_interval(rangedict={}), + (None, None, None), + ) + + def test_range_check_timezone(self): + self.assertEqual( + BaseTimeBuilder.range_check_timezone(), (None, None, None, None, "") + ) + + self.assertEqual( + BaseTimeBuilder.range_check_timezone(rangedict={}), + (None, None, None, None, ""), + ) + + def test_build_object(self): + datetest = ( + DateTuple("1", "2", "3", "4", "5", "6"), + {"YYYY": "1", "MM": "2", "DD": "3", "Www": "4", "D": "5", "DDD": "6"}, + ) + + timetest = ( + TimeTuple("1", "2", "3", TimezoneTuple(False, False, "4", "5", "tz name")), + { + "hh": "1", + "mm": "2", + "ss": "3", + "tz": TimezoneTuple(False, False, "4", "5", "tz name"), + }, + ) + + datetimetest = ( + DatetimeTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple( + "7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") + ), + ), + ( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple( + "7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") + ), + ), + ) + + durationtest = ( + DurationTuple("1", "2", "3", "4", "5", "6", "7"), + { + "PnY": "1", + "PnM": "2", + "PnW": "3", + "PnD": "4", + "TnH": "5", + "TnM": "6", + "TnS": "7", + }, + ) + + intervaltests = ( + ( + IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + DateTuple("7", "8", "9", "10", "11", "12"), + None, + ), + { + "start": DateTuple("1", "2", "3", "4", "5", "6"), + "end": DateTuple("7", "8", "9", "10", "11", "12"), + "duration": None, + }, + ), + ( + IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + None, + DurationTuple("7", "8", "9", "10", "11", "12", "13"), + ), + { + "start": DateTuple("1", "2", "3", "4", "5", "6"), + "end": None, + "duration": DurationTuple("7", "8", "9", "10", "11", "12", "13"), + }, + ), + ( + IntervalTuple( + None, + TimeTuple( + "1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") + ), + DurationTuple("6", "7", "8", "9", "10", "11", "12"), + ), + { + "start": None, + "end": TimeTuple( + "1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") + ), + "duration": DurationTuple("6", "7", "8", "9", "10", "11", "12"), + }, + ), + ) + + repeatingintervaltests = ( + ( + RepeatingIntervalTuple( + True, + None, + IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + DateTuple("7", "8", "9", "10", "11", "12"), + None, + ), + ), + { + "R": True, + "Rnn": None, + "interval": IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + DateTuple("7", "8", "9", "10", "11", "12"), + None, + ), + }, + ), + ( + RepeatingIntervalTuple( + False, + "1", + IntervalTuple( + DatetimeTuple( + DateTuple("2", "3", "4", "5", "6", "7"), + TimeTuple("8", "9", "10", None), + ), + DatetimeTuple( + DateTuple("11", "12", "13", "14", "15", "16"), + TimeTuple("17", "18", "19", None), + ), + None, + ), + ), + { + "R": False, + "Rnn": "1", + "interval": IntervalTuple( + DatetimeTuple( + DateTuple("2", "3", "4", "5", "6", "7"), + TimeTuple("8", "9", "10", None), + ), + DatetimeTuple( + DateTuple("11", "12", "13", "14", "15", "16"), + TimeTuple("17", "18", "19", None), + ), + None, + ), + }, + ), + ) + + timezonetest = ( + TimezoneTuple(False, False, "1", "2", "+01:02"), + {"negative": False, "Z": False, "hh": "1", "mm": "2", "name": "+01:02"}, + ) + + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_date" + ) as mock_build: + mock_build.return_value = datetest[0] + + result = BaseTimeBuilder._build_object(datetest[0]) + + self.assertEqual(result, datetest[0]) + mock_build.assert_called_once_with(**datetest[1]) + + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_time" + ) as mock_build: + mock_build.return_value = timetest[0] + + result = BaseTimeBuilder._build_object(timetest[0]) + + self.assertEqual(result, timetest[0]) + mock_build.assert_called_once_with(**timetest[1]) + + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_datetime" + ) as mock_build: + mock_build.return_value = datetimetest[0] + + result = BaseTimeBuilder._build_object(datetimetest[0]) + + self.assertEqual(result, datetimetest[0]) + mock_build.assert_called_once_with(*datetimetest[1]) + + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_duration" + ) as mock_build: + mock_build.return_value = durationtest[0] + + result = BaseTimeBuilder._build_object(durationtest[0]) + + self.assertEqual(result, durationtest[0]) + mock_build.assert_called_once_with(**durationtest[1]) + + for intervaltest in intervaltests: + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_interval" + ) as mock_build: + mock_build.return_value = intervaltest[0] + + result = BaseTimeBuilder._build_object(intervaltest[0]) + + self.assertEqual(result, intervaltest[0]) + mock_build.assert_called_once_with(**intervaltest[1]) + + for repeatingintervaltest in repeatingintervaltests: + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_repeating_interval" + ) as mock_build: + mock_build.return_value = repeatingintervaltest[0] + + result = BaseTimeBuilder._build_object(repeatingintervaltest[0]) + + self.assertEqual(result, repeatingintervaltest[0]) + mock_build.assert_called_once_with(**repeatingintervaltest[1]) + + with mock.patch.object( + aniso8601.builders.BaseTimeBuilder, "build_timezone" + ) as mock_build: + mock_build.return_value = timezonetest[0] + + result = BaseTimeBuilder._build_object(timezonetest[0]) + + self.assertEqual(result, timezonetest[0]) + mock_build.assert_called_once_with(**timezonetest[1]) + + def test_is_interval_end_concise(self): + self.assertTrue( + BaseTimeBuilder._is_interval_end_concise(TimeTuple("1", "2", "3", None)) + ) + self.assertTrue( + BaseTimeBuilder._is_interval_end_concise( + DateTuple(None, "2", "3", "4", "5", "6") + ) + ) + self.assertTrue( + BaseTimeBuilder._is_interval_end_concise( + DatetimeTuple( + DateTuple(None, "2", "3", "4", "5", "6"), + TimeTuple("7", "8", "9", None), + ) + ) + ) + + self.assertFalse( + BaseTimeBuilder._is_interval_end_concise( + DateTuple("1", "2", "3", "4", "5", "6") + ) + ) + self.assertFalse( + BaseTimeBuilder._is_interval_end_concise( + DatetimeTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple("7", "8", "9", None), + ) + ) + ) + + def test_combine_concise_interval_tuples(self): + testtuples = ( + ( + DateTuple("2020", "01", "01", None, None, None), + DateTuple(None, None, "02", None, None, None), + DateTuple("2020", "01", "02", None, None, None), + ), + ( + DateTuple("2008", "02", "15", None, None, None), + DateTuple(None, "03", "14", None, None, None), + DateTuple("2008", "03", "14", None, None, None), + ), + ( + DatetimeTuple( + DateTuple("2007", "12", "14", None, None, None), + TimeTuple("13", "30", None, None), + ), + TimeTuple("15", "30", None, None), + DatetimeTuple( + DateTuple("2007", "12", "14", None, None, None), + TimeTuple("15", "30", None, None), + ), + ), + ( + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + DatetimeTuple( + DateTuple("2007", "11", "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + ), + ( + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + DatetimeTuple( + DateTuple(None, None, "16", None, None, None), + TimeTuple("00", "00", None, None), + ), + DatetimeTuple( + DateTuple("2007", "11", "16", None, None, None), + TimeTuple("00", "00", None, None), + ), + ), + ( + DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple( + "09", "00", None, TimezoneTuple(False, True, None, None, "Z") + ), + ), + DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + DatetimeTuple( + DateTuple("2007", "11", "15", None, None, None), + TimeTuple( + "17", "00", None, TimezoneTuple(False, True, None, None, "Z") + ), + ), + ), + ) + + for testtuple in testtuples: + result = BaseTimeBuilder._combine_concise_interval_tuples( + testtuple[0], testtuple[1] + ) + self.assertEqual(result, testtuple[2]) + + +class TestTupleBuilder(unittest.TestCase): + def test_build_date(self): + datetuple = TupleBuilder.build_date() + + self.assertEqual(datetuple, DateTuple(None, None, None, None, None, None)) + + datetuple = TupleBuilder.build_date( + YYYY="1", MM="2", DD="3", Www="4", D="5", DDD="6" + ) + + self.assertEqual(datetuple, DateTuple("1", "2", "3", "4", "5", "6")) + + def test_build_time(self): + testtuples = ( + ({}, TimeTuple(None, None, None, None)), + ( + {"hh": "1", "mm": "2", "ss": "3", "tz": None}, + TimeTuple("1", "2", "3", None), + ), + ( + { + "hh": "1", + "mm": "2", + "ss": "3", + "tz": TimezoneTuple(False, False, "4", "5", "tz name"), + }, + TimeTuple( + "1", "2", "3", TimezoneTuple(False, False, "4", "5", "tz name") + ), + ), + ) + + for testtuple in testtuples: + self.assertEqual(TupleBuilder.build_time(**testtuple[0]), testtuple[1]) + + def test_build_datetime(self): + testtuples = ( + ( + { + "date": DateTuple("1", "2", "3", "4", "5", "6"), + "time": TimeTuple("7", "8", "9", None), + }, + DatetimeTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple("7", "8", "9", None), + ), + ), + ( + { + "date": DateTuple("1", "2", "3", "4", "5", "6"), + "time": TimeTuple( + "7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") + ), + }, + DatetimeTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple( + "7", "8", "9", TimezoneTuple(True, False, "10", "11", "tz name") + ), + ), + ), + ) + + for testtuple in testtuples: + self.assertEqual(TupleBuilder.build_datetime(**testtuple[0]), testtuple[1]) + + def test_build_duration(self): + testtuples = ( + ({}, DurationTuple(None, None, None, None, None, None, None)), + ( + { + "PnY": "1", + "PnM": "2", + "PnW": "3", + "PnD": "4", + "TnH": "5", + "TnM": "6", + "TnS": "7", + }, + DurationTuple("1", "2", "3", "4", "5", "6", "7"), + ), + ) + + for testtuple in testtuples: + self.assertEqual(TupleBuilder.build_duration(**testtuple[0]), testtuple[1]) + + def test_build_interval(self): + testtuples = ( + ({}, IntervalTuple(None, None, None)), + ( + { + "start": DateTuple("1", "2", "3", "4", "5", "6"), + "end": DateTuple("7", "8", "9", "10", "11", "12"), + }, + IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + DateTuple("7", "8", "9", "10", "11", "12"), + None, + ), + ), + ( + { + "start": TimeTuple( + "1", "2", "3", TimezoneTuple(True, False, "7", "8", "tz name") + ), + "end": TimeTuple( + "4", "5", "6", TimezoneTuple(False, False, "9", "10", "tz name") + ), + }, + IntervalTuple( + TimeTuple( + "1", "2", "3", TimezoneTuple(True, False, "7", "8", "tz name") + ), + TimeTuple( + "4", "5", "6", TimezoneTuple(False, False, "9", "10", "tz name") + ), + None, + ), + ), + ( + { + "start": DatetimeTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple( + "7", + "8", + "9", + TimezoneTuple(True, False, "10", "11", "tz name"), + ), + ), + "end": DatetimeTuple( + DateTuple("12", "13", "14", "15", "16", "17"), + TimeTuple( + "18", + "19", + "20", + TimezoneTuple(False, False, "21", "22", "tz name"), + ), + ), + }, + IntervalTuple( + DatetimeTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + TimeTuple( + "7", + "8", + "9", + TimezoneTuple(True, False, "10", "11", "tz name"), + ), + ), + DatetimeTuple( + DateTuple("12", "13", "14", "15", "16", "17"), + TimeTuple( + "18", + "19", + "20", + TimezoneTuple(False, False, "21", "22", "tz name"), + ), + ), + None, + ), + ), + ( + { + "start": DateTuple("1", "2", "3", "4", "5", "6"), + "end": None, + "duration": DurationTuple("7", "8", "9", "10", "11", "12", "13"), + }, + IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + None, + DurationTuple("7", "8", "9", "10", "11", "12", "13"), + ), + ), + ( + { + "start": None, + "end": TimeTuple( + "1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") + ), + "duration": DurationTuple("6", "7", "8", "9", "10", "11", "12"), + }, + IntervalTuple( + None, + TimeTuple( + "1", "2", "3", TimezoneTuple(True, False, "4", "5", "tz name") + ), + DurationTuple("6", "7", "8", "9", "10", "11", "12"), + ), + ), + ) + + for testtuple in testtuples: + self.assertEqual(TupleBuilder.build_interval(**testtuple[0]), testtuple[1]) + + def test_build_repeating_interval(self): + testtuples = ( + ({}, RepeatingIntervalTuple(None, None, None)), + ( + { + "R": True, + "interval": IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + DateTuple("7", "8", "9", "10", "11", "12"), + None, + ), + }, + RepeatingIntervalTuple( + True, + None, + IntervalTuple( + DateTuple("1", "2", "3", "4", "5", "6"), + DateTuple("7", "8", "9", "10", "11", "12"), + None, + ), + ), + ), + ( + { + "R": False, + "Rnn": "1", + "interval": IntervalTuple( + DatetimeTuple( + DateTuple("2", "3", "4", "5", "6", "7"), + TimeTuple("8", "9", "10", None), + ), + DatetimeTuple( + DateTuple("11", "12", "13", "14", "15", "16"), + TimeTuple("17", "18", "19", None), + ), + None, + ), + }, + RepeatingIntervalTuple( + False, + "1", + IntervalTuple( + DatetimeTuple( + DateTuple("2", "3", "4", "5", "6", "7"), + TimeTuple("8", "9", "10", None), + ), + DatetimeTuple( + DateTuple("11", "12", "13", "14", "15", "16"), + TimeTuple("17", "18", "19", None), + ), + None, + ), + ), + ), + ) + + for testtuple in testtuples: + result = TupleBuilder.build_repeating_interval(**testtuple[0]) + self.assertEqual(result, testtuple[1]) + + def test_build_timezone(self): + testtuples = ( + ({}, TimezoneTuple(None, None, None, None, "")), + ( + {"negative": False, "Z": True, "name": "UTC"}, + TimezoneTuple(False, True, None, None, "UTC"), + ), + ( + {"negative": False, "Z": False, "hh": "1", "mm": "2", "name": "+01:02"}, + TimezoneTuple(False, False, "1", "2", "+01:02"), + ), + ( + {"negative": True, "Z": False, "hh": "1", "mm": "2", "name": "-01:02"}, + TimezoneTuple(True, False, "1", "2", "-01:02"), + ), + ) + + for testtuple in testtuples: + result = TupleBuilder.build_timezone(**testtuple[0]) + self.assertEqual(result, testtuple[1]) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/test_python.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/test_python.py new file mode 100644 index 00000000..ccbed27d --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/builders/tests/test_python.py @@ -0,0 +1,1723 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import datetime +import unittest + +from aniso8601 import compat +from aniso8601.builders import ( + DatetimeTuple, + DateTuple, + DurationTuple, + IntervalTuple, + Limit, + TimeTuple, + TimezoneTuple, +) +from aniso8601.builders.python import ( + FractionalComponent, + PythonTimeBuilder, + _cast_to_fractional_component, + fractional_range_check, + year_range_check, +) +from aniso8601.exceptions import ( + DayOutOfBoundsError, + HoursOutOfBoundsError, + ISOFormatError, + LeapSecondError, + MidnightBoundsError, + MinutesOutOfBoundsError, + MonthOutOfBoundsError, + SecondsOutOfBoundsError, + WeekOutOfBoundsError, + YearOutOfBoundsError, +) +from aniso8601.utcoffset import UTCOffset + + +class TestPythonTimeBuilder_UtiltyFunctions(unittest.TestCase): + def test_year_range_check(self): + yearlimit = Limit( + "Invalid year string.", + 0000, + 9999, + YearOutOfBoundsError, + "Year must be between 1..9999.", + None, + ) + + self.assertEqual(year_range_check("19", yearlimit), 1900) + self.assertEqual(year_range_check("1234", yearlimit), 1234) + self.assertEqual(year_range_check("1985", yearlimit), 1985) + + def test_fractional_range_check(self): + limit = Limit( + "Invalid string.", -1, 1, ValueError, "Value must be between -1..1.", None + ) + + self.assertEqual(fractional_range_check(10, "1", limit), 1) + self.assertEqual(fractional_range_check(10, "-1", limit), -1) + self.assertEqual( + fractional_range_check(10, "0.1", limit), FractionalComponent(0, 1) + ) + self.assertEqual( + fractional_range_check(10, "-0.1", limit), FractionalComponent(-0, 1) + ) + + with self.assertRaises(ValueError): + fractional_range_check(10, "1.1", limit) + + with self.assertRaises(ValueError): + fractional_range_check(10, "-1.1", limit) + + def test_cast_to_fractional_component(self): + self.assertEqual( + _cast_to_fractional_component(10, "1.1"), FractionalComponent(1, 1) + ) + self.assertEqual( + _cast_to_fractional_component(10, "-1.1"), FractionalComponent(-1, 1) + ) + + self.assertEqual( + _cast_to_fractional_component(100, "1.1"), FractionalComponent(1, 10) + ) + self.assertEqual( + _cast_to_fractional_component(100, "-1.1"), FractionalComponent(-1, 10) + ) + + +class TestPythonTimeBuilder(unittest.TestCase): + def test_build_date(self): + testtuples = ( + ( + { + "YYYY": "2013", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(2013, 1, 1), + ), + ( + { + "YYYY": "0001", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1, 1, 1), + ), + ( + { + "YYYY": "19", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1900, 1, 1), + ), + ( + { + "YYYY": "10", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1000, 1, 1), + ), + ( + { + "YYYY": "1981", + "MM": "04", + "DD": "05", + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1981, 4, 5), + ), + ( + { + "YYYY": "1981", + "MM": "04", + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1981, 4, 1), + ), + ( + { + "YYYY": "1981", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": "095", + }, + datetime.date(1981, 4, 5), + ), + ( + { + "YYYY": "1981", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": "365", + }, + datetime.date(1981, 12, 31), + ), + ( + { + "YYYY": "1980", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": "366", + }, + datetime.date(1980, 12, 31), + ), + # Make sure we shift in zeros + ( + { + "YYYY": "1", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1000, 1, 1), + ), + ( + { + "YYYY": "12", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1200, 1, 1), + ), + ( + { + "YYYY": "123", + "MM": None, + "DD": None, + "Www": None, + "D": None, + "DDD": None, + }, + datetime.date(1230, 1, 1), + ), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_date(**testtuple[0]) + self.assertEqual(result, testtuple[1]) + + # Test weekday + testtuples = ( + ( + { + "YYYY": "2004", + "MM": None, + "DD": None, + "Www": "53", + "D": None, + "DDD": None, + }, + datetime.date(2004, 12, 27), + 0, + ), + ( + { + "YYYY": "2009", + "MM": None, + "DD": None, + "Www": "01", + "D": None, + "DDD": None, + }, + datetime.date(2008, 12, 29), + 0, + ), + ( + { + "YYYY": "2010", + "MM": None, + "DD": None, + "Www": "01", + "D": None, + "DDD": None, + }, + datetime.date(2010, 1, 4), + 0, + ), + ( + { + "YYYY": "2009", + "MM": None, + "DD": None, + "Www": "53", + "D": None, + "DDD": None, + }, + datetime.date(2009, 12, 28), + 0, + ), + ( + { + "YYYY": "2009", + "MM": None, + "DD": None, + "Www": "01", + "D": "1", + "DDD": None, + }, + datetime.date(2008, 12, 29), + 0, + ), + ( + { + "YYYY": "2009", + "MM": None, + "DD": None, + "Www": "53", + "D": "7", + "DDD": None, + }, + datetime.date(2010, 1, 3), + 6, + ), + ( + { + "YYYY": "2010", + "MM": None, + "DD": None, + "Www": "01", + "D": "1", + "DDD": None, + }, + datetime.date(2010, 1, 4), + 0, + ), + ( + { + "YYYY": "2004", + "MM": None, + "DD": None, + "Www": "53", + "D": "6", + "DDD": None, + }, + datetime.date(2005, 1, 1), + 5, + ), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_date(**testtuple[0]) + self.assertEqual(result, testtuple[1]) + self.assertEqual(result.weekday(), testtuple[2]) + + def test_build_time(self): + testtuples = ( + ({}, datetime.time()), + ({"hh": "12.5"}, datetime.time(hour=12, minute=30)), + ( + {"hh": "23.99999999997"}, + datetime.time(hour=23, minute=59, second=59, microsecond=999999), + ), + ({"hh": "1", "mm": "23"}, datetime.time(hour=1, minute=23)), + ( + {"hh": "1", "mm": "23.4567"}, + datetime.time(hour=1, minute=23, second=27, microsecond=402000), + ), + ( + {"hh": "14", "mm": "43.999999997"}, + datetime.time(hour=14, minute=43, second=59, microsecond=999999), + ), + ( + {"hh": "1", "mm": "23", "ss": "45"}, + datetime.time(hour=1, minute=23, second=45), + ), + ( + {"hh": "23", "mm": "21", "ss": "28.512400"}, + datetime.time(hour=23, minute=21, second=28, microsecond=512400), + ), + ( + {"hh": "01", "mm": "03", "ss": "11.858714"}, + datetime.time(hour=1, minute=3, second=11, microsecond=858714), + ), + ( + {"hh": "14", "mm": "43", "ss": "59.9999997"}, + datetime.time(hour=14, minute=43, second=59, microsecond=999999), + ), + ({"hh": "24"}, datetime.time(hour=0)), + ({"hh": "24", "mm": "00"}, datetime.time(hour=0)), + ({"hh": "24", "mm": "00", "ss": "00"}, datetime.time(hour=0)), + ( + {"tz": TimezoneTuple(False, None, "00", "00", "UTC")}, + datetime.time(tzinfo=UTCOffset(name="UTC", minutes=0)), + ), + ( + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "00", "00", "+00:00"), + }, + datetime.time( + hour=23, + minute=21, + second=28, + microsecond=512400, + tzinfo=UTCOffset(name="+00:00", minutes=0), + ), + ), + ( + { + "hh": "1", + "mm": "23", + "tz": TimezoneTuple(False, None, "01", "00", "+1"), + }, + datetime.time( + hour=1, minute=23, tzinfo=UTCOffset(name="+1", minutes=60) + ), + ), + ( + { + "hh": "1", + "mm": "23.4567", + "tz": TimezoneTuple(True, None, "01", "00", "-1"), + }, + datetime.time( + hour=1, + minute=23, + second=27, + microsecond=402000, + tzinfo=UTCOffset(name="-1", minutes=-60), + ), + ), + ( + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "01", "30", "+1:30"), + }, + datetime.time( + hour=23, + minute=21, + second=28, + microsecond=512400, + tzinfo=UTCOffset(name="+1:30", minutes=90), + ), + ), + ( + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "11", "15", "+11:15"), + }, + datetime.time( + hour=23, + minute=21, + second=28, + microsecond=512400, + tzinfo=UTCOffset(name="+11:15", minutes=675), + ), + ), + ( + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "12", "34", "+12:34"), + }, + datetime.time( + hour=23, + minute=21, + second=28, + microsecond=512400, + tzinfo=UTCOffset(name="+12:34", minutes=754), + ), + ), + ( + { + "hh": "23", + "mm": "21", + "ss": "28.512400", + "tz": TimezoneTuple(False, None, "00", "00", "UTC"), + }, + datetime.time( + hour=23, + minute=21, + second=28, + microsecond=512400, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + # https://bitbucket.org/nielsenb/aniso8601/issues/21/sub-microsecond-precision-is-lost-when + ( + {"hh": "14.9999999999999999"}, + datetime.time(hour=14, minute=59, second=59, microsecond=999999), + ), + ({"mm": "0.00000000999"}, datetime.time()), + ({"mm": "0.0000000999"}, datetime.time(microsecond=5)), + ({"ss": "0.0000001"}, datetime.time()), + ({"ss": "2.0000048"}, datetime.time(second=2, microsecond=4)), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_time(**testtuple[0]) + self.assertEqual(result, testtuple[1]) + + def test_build_datetime(self): + testtuples = ( + ( + ( + DateTuple("2019", "06", "05", None, None, None), + TimeTuple("01", "03", "11.858714", None), + ), + datetime.datetime( + 2019, 6, 5, hour=1, minute=3, second=11, microsecond=858714 + ), + ), + ( + ( + DateTuple("1234", "02", "03", None, None, None), + TimeTuple("23", "21", "28.512400", None), + ), + datetime.datetime( + 1234, 2, 3, hour=23, minute=21, second=28, microsecond=512400 + ), + ), + ( + ( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple( + "23", + "21", + "28.512400", + TimezoneTuple(False, None, "11", "15", "+11:15"), + ), + ), + datetime.datetime( + 1981, + 4, + 5, + hour=23, + minute=21, + second=28, + microsecond=512400, + tzinfo=UTCOffset(name="+11:15", minutes=675), + ), + ), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_datetime(*testtuple[0]) + self.assertEqual(result, testtuple[1]) + + def test_build_duration(self): + testtuples = ( + ( + { + "PnY": "1", + "PnM": "2", + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6", + }, + datetime.timedelta(days=428, hours=4, minutes=54, seconds=6), + ), + ( + { + "PnY": "1", + "PnM": "2", + "PnD": "3", + "TnH": "4", + "TnM": "54", + "TnS": "6.5", + }, + datetime.timedelta(days=428, hours=4, minutes=54, seconds=6.5), + ), + ({"PnY": "1", "PnM": "2", "PnD": "3"}, datetime.timedelta(days=428)), + ({"PnY": "1", "PnM": "2", "PnD": "3.5"}, datetime.timedelta(days=428.5)), + ( + {"TnH": "4", "TnM": "54", "TnS": "6.5"}, + datetime.timedelta(hours=4, minutes=54, seconds=6.5), + ), + ( + {"TnH": "1", "TnM": "3", "TnS": "11.858714"}, + datetime.timedelta(hours=1, minutes=3, seconds=11, microseconds=858714), + ), + ( + {"TnH": "4", "TnM": "54", "TnS": "28.512400"}, + datetime.timedelta( + hours=4, minutes=54, seconds=28, microseconds=512400 + ), + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + # https://bitbucket.org/nielsenb/aniso8601/issues/21/sub-microsecond-precision-is-lost-when + ( + {"PnY": "1999.9999999999999999"}, + datetime.timedelta(days=729999, seconds=86399, microseconds=999999), + ), + ( + {"PnM": "1.9999999999999999"}, + datetime.timedelta( + days=59, hours=23, minutes=59, seconds=59, microseconds=999999 + ), + ), + ( + {"PnW": "1.9999999999999999"}, + datetime.timedelta( + days=13, hours=23, minutes=59, seconds=59, microseconds=999999 + ), + ), + ( + {"PnD": "1.9999999999999999"}, + datetime.timedelta( + days=1, hours=23, minutes=59, seconds=59, microseconds=999999 + ), + ), + ( + {"TnH": "14.9999999999999999"}, + datetime.timedelta( + hours=14, minutes=59, seconds=59, microseconds=999999 + ), + ), + ({"TnM": "0.00000000999"}, datetime.timedelta(0)), + ({"TnM": "0.0000000999"}, datetime.timedelta(microseconds=5)), + ({"TnS": "0.0000001"}, datetime.timedelta(0)), + ({"TnS": "2.0000048"}, datetime.timedelta(seconds=2, microseconds=4)), + ({"PnY": "1"}, datetime.timedelta(days=365)), + ({"PnY": "1.5"}, datetime.timedelta(days=547.5)), + ({"PnM": "1"}, datetime.timedelta(days=30)), + ({"PnM": "1.5"}, datetime.timedelta(days=45)), + ({"PnW": "1"}, datetime.timedelta(days=7)), + ({"PnW": "1.5"}, datetime.timedelta(days=10.5)), + ({"PnD": "1"}, datetime.timedelta(days=1)), + ({"PnD": "1.5"}, datetime.timedelta(days=1.5)), + ( + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05", + }, + datetime.timedelta(days=1279, hours=12, minutes=30, seconds=5), + ), + ( + { + "PnY": "0003", + "PnM": "06", + "PnD": "04", + "TnH": "12", + "TnM": "30", + "TnS": "05.5", + }, + datetime.timedelta(days=1279, hours=12, minutes=30, seconds=5.5), + ), + # Test timedelta limit + ( + {"PnD": "999999999", "TnH": "23", "TnM": "59", "TnS": "59.999999"}, + datetime.timedelta.max, + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + { + "PnY": "0001", + "PnM": "02", + "PnD": "03", + "TnH": "14", + "TnM": "43", + "TnS": "59.9999997", + }, + datetime.timedelta( + days=428, hours=14, minutes=43, seconds=59, microseconds=999999 + ), + ), + # Verify overflows + ({"TnH": "36"}, datetime.timedelta(days=1, hours=12)), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_duration(**testtuple[0]) + self.assertEqual(result, testtuple[1]) + + def test_build_interval(self): + testtuples = ( + ( + { + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, None, None, None, None), + }, + datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), + datetime.datetime(year=1981, month=3, day=6, hour=1, minute=1), + ), + ( + { + "end": DateTuple("1981", "04", "05", None, None, None), + "duration": DurationTuple(None, "1", None, None, None, None, None), + }, + datetime.date(year=1981, month=4, day=5), + datetime.date(year=1981, month=3, day=6), + ), + ( + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + "1.5", None, None, None, None, None, None + ), + }, + datetime.date(year=2018, month=3, day=6), + datetime.datetime(year=2016, month=9, day=4, hour=12), + ), + ( + { + "end": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + }, + datetime.date(year=2014, month=11, day=12), + datetime.datetime(year=2014, month=11, day=11, hour=23), + ), + ( + { + "end": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), + }, + datetime.date(year=2014, month=11, day=12), + datetime.datetime( + year=2014, + month=11, + day=11, + hour=19, + minute=5, + second=53, + microsecond=500000, + ), + ), + ( + { + "end": DatetimeTuple( + DateTuple("2050", "03", "01", None, None, None), + TimeTuple( + "13", + "00", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "duration": DurationTuple(None, None, None, None, "10", None, None), + }, + datetime.datetime( + year=2050, + month=3, + day=1, + hour=13, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + datetime.datetime( + year=2050, + month=3, + day=1, + hour=3, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + # https://bitbucket.org/nielsenb/aniso8601/issues/21/sub-microsecond-precision-is-lost-when + ( + { + "end": DateTuple("2000", "01", "01", None, None, None), + "duration": DurationTuple( + "1999.9999999999999999", None, None, None, None, None, None + ), + }, + datetime.date(year=2000, month=1, day=1), + datetime.datetime( + year=1, month=4, day=30, hour=0, minute=0, second=0, microsecond=1 + ), + ), + ( + { + "end": DateTuple("1989", "03", "01", None, None, None), + "duration": DurationTuple( + None, "1.9999999999999999", None, None, None, None, None + ), + }, + datetime.date(year=1989, month=3, day=1), + datetime.datetime( + year=1988, + month=12, + day=31, + hour=0, + minute=0, + second=0, + microsecond=1, + ), + ), + ( + { + "end": DateTuple("1989", "03", "01", None, None, None), + "duration": DurationTuple( + None, None, "1.9999999999999999", None, None, None, None + ), + }, + datetime.date(year=1989, month=3, day=1), + datetime.datetime( + year=1989, + month=2, + day=15, + hour=0, + minute=0, + second=0, + microsecond=1, + ), + ), + ( + { + "end": DateTuple("1989", "03", "01", None, None, None), + "duration": DurationTuple( + None, None, None, "1.9999999999999999", None, None, None + ), + }, + datetime.date(year=1989, month=3, day=1), + datetime.datetime( + year=1989, + month=2, + day=27, + hour=0, + minute=0, + second=0, + microsecond=1, + ), + ), + ( + { + "end": DateTuple("2001", "01", "01", None, None, None), + "duration": DurationTuple( + None, None, None, None, "14.9999999999999999", None, None + ), + }, + datetime.date(year=2001, month=1, day=1), + datetime.datetime( + year=2000, + month=12, + day=31, + hour=9, + minute=0, + second=0, + microsecond=1, + ), + ), + ( + { + "end": DateTuple("2001", "01", "01", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, "0.00000000999", None + ), + }, + datetime.date(year=2001, month=1, day=1), + datetime.datetime(year=2001, month=1, day=1), + ), + ( + { + "end": DateTuple("2001", "01", "01", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, "0.0000000999", None + ), + }, + datetime.date(year=2001, month=1, day=1), + datetime.datetime( + year=2000, + month=12, + day=31, + hour=23, + minute=59, + second=59, + microsecond=999995, + ), + ), + ( + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "0.0000001" + ), + }, + datetime.date(year=2018, month=3, day=6), + datetime.datetime(year=2018, month=3, day=6), + ), + ( + { + "end": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "2.0000048" + ), + }, + datetime.date(year=2018, month=3, day=6), + datetime.datetime( + year=2018, + month=3, + day=5, + hour=23, + minute=59, + second=57, + microsecond=999996, + ), + ), + ( + { + "start": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "duration": DurationTuple(None, "1", None, "1", None, "1", None), + }, + datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), + datetime.datetime(year=1981, month=5, day=6, hour=1, minute=2), + ), + ( + { + "start": DateTuple("1981", "04", "05", None, None, None), + "duration": DurationTuple(None, "1", None, "1", None, None, None), + }, + datetime.date(year=1981, month=4, day=5), + datetime.date(year=1981, month=5, day=6), + ), + ( + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, "2.5", None, None, None, None, None + ), + }, + datetime.date(year=2018, month=3, day=6), + datetime.date(year=2018, month=5, day=20), + ), + ( + { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "1", None, None), + }, + datetime.date(year=2014, month=11, day=12), + datetime.datetime(year=2014, month=11, day=12, hour=1, minute=0), + ), + ( + { + "start": DateTuple("2014", "11", "12", None, None, None), + "duration": DurationTuple(None, None, None, None, "4", "54", "6.5"), + }, + datetime.date(year=2014, month=11, day=12), + datetime.datetime( + year=2014, + month=11, + day=12, + hour=4, + minute=54, + second=6, + microsecond=500000, + ), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2050", "03", "01", None, None, None), + TimeTuple( + "13", + "00", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "duration": DurationTuple(None, None, None, None, "10", None, None), + }, + datetime.datetime( + year=2050, + month=3, + day=1, + hour=13, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + datetime.datetime( + year=2050, + month=3, + day=1, + hour=23, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + { + "start": DateTuple("0001", "01", "01", None, None, None), + "duration": DurationTuple( + "1999.9999999999999999", None, None, None, None, None, None + ), + }, + datetime.date(year=1, month=1, day=1), + datetime.datetime( + year=1999, + month=9, + day=3, + hour=23, + minute=59, + second=59, + microsecond=999999, + ), + ), + ( + { + "start": DateTuple("1989", "03", "01", None, None, None), + "duration": DurationTuple( + None, "1.9999999999999999", None, None, None, None, None + ), + }, + datetime.date(year=1989, month=3, day=1), + datetime.datetime( + year=1989, + month=4, + day=29, + hour=23, + minute=59, + second=59, + microsecond=999999, + ), + ), + ( + { + "start": DateTuple("1989", "03", "01", None, None, None), + "duration": DurationTuple( + None, None, "1.9999999999999999", None, None, None, None + ), + }, + datetime.date(year=1989, month=3, day=1), + datetime.datetime( + year=1989, + month=3, + day=14, + hour=23, + minute=59, + second=59, + microsecond=999999, + ), + ), + ( + { + "start": DateTuple("1989", "03", "01", None, None, None), + "duration": DurationTuple( + None, None, None, "1.9999999999999999", None, None, None + ), + }, + datetime.date(year=1989, month=3, day=1), + datetime.datetime( + year=1989, + month=3, + day=2, + hour=23, + minute=59, + second=59, + microsecond=999999, + ), + ), + ( + { + "start": DateTuple("2001", "01", "01", None, None, None), + "duration": DurationTuple( + None, None, None, None, "14.9999999999999999", None, None + ), + }, + datetime.date(year=2001, month=1, day=1), + datetime.datetime( + year=2001, + month=1, + day=1, + hour=14, + minute=59, + second=59, + microsecond=999999, + ), + ), + ( + { + "start": DateTuple("2001", "01", "01", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, "0.00000000999", None + ), + }, + datetime.date(year=2001, month=1, day=1), + datetime.datetime(year=2001, month=1, day=1), + ), + ( + { + "start": DateTuple("2001", "01", "01", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, "0.0000000999", None + ), + }, + datetime.date(year=2001, month=1, day=1), + datetime.datetime( + year=2001, month=1, day=1, hour=0, minute=0, second=0, microsecond=5 + ), + ), + ( + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "0.0000001" + ), + }, + datetime.date(year=2018, month=3, day=6), + datetime.datetime(year=2018, month=3, day=6), + ), + ( + { + "start": DateTuple("2018", "03", "06", None, None, None), + "duration": DurationTuple( + None, None, None, None, None, None, "2.0000048" + ), + }, + datetime.date(year=2018, month=3, day=6), + datetime.datetime( + year=2018, month=3, day=6, hour=0, minute=0, second=2, microsecond=4 + ), + ), + ( + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + }, + datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1), + datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), + ), + ( + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + "end": DateTuple("1981", "04", "05", None, None, None), + }, + datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1), + datetime.date(year=1981, month=4, day=5), + ), + ( + { + "start": DateTuple("1980", "03", "05", None, None, None), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + }, + datetime.date(year=1980, month=3, day=5), + datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1), + ), + ( + { + "start": DateTuple("1980", "03", "05", None, None, None), + "end": DateTuple("1981", "04", "05", None, None, None), + }, + datetime.date(year=1980, month=3, day=5), + datetime.date(year=1981, month=4, day=5), + ), + ( + { + "start": DateTuple("1981", "04", "05", None, None, None), + "end": DateTuple("1980", "03", "05", None, None, None), + }, + datetime.date(year=1981, month=4, day=5), + datetime.date(year=1980, month=3, day=5), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2050", "03", "01", None, None, None), + TimeTuple( + "13", + "00", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "end": DatetimeTuple( + DateTuple("2050", "05", "11", None, None, None), + TimeTuple( + "15", + "30", + "00", + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + }, + datetime.datetime( + year=2050, + month=3, + day=1, + hour=13, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + datetime.datetime( + year=2050, + month=5, + day=11, + hour=15, + minute=30, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + ), + # Test concise representation + ( + { + "start": DateTuple("2020", "01", "01", None, None, None), + "end": DateTuple(None, None, "02", None, None, None), + }, + datetime.date(year=2020, month=1, day=1), + datetime.date(year=2020, month=1, day=2), + ), + ( + { + "start": DateTuple("2008", "02", "15", None, None, None), + "end": DateTuple(None, "03", "14", None, None, None), + }, + datetime.date(year=2008, month=2, day=15), + datetime.date(year=2008, month=3, day=14), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2007", "12", "14", None, None, None), + TimeTuple("13", "30", None, None), + ), + "end": TimeTuple("15", "30", None, None), + }, + datetime.datetime(year=2007, month=12, day=14, hour=13, minute=30), + datetime.datetime(year=2007, month=12, day=14, hour=15, minute=30), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + "end": DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + }, + datetime.datetime(year=2007, month=11, day=13, hour=9), + datetime.datetime(year=2007, month=11, day=15, hour=17), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("00", "00", None, None), + ), + "end": DatetimeTuple( + DateTuple(None, None, "16", None, None, None), + TimeTuple("00", "00", None, None), + ), + }, + datetime.datetime(year=2007, month=11, day=13), + datetime.datetime(year=2007, month=11, day=16), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple( + "09", + "00", + None, + TimezoneTuple(False, True, None, None, "Z"), + ), + ), + "end": DatetimeTuple( + DateTuple(None, None, "15", None, None, None), + TimeTuple("17", "00", None, None), + ), + }, + datetime.datetime( + year=2007, + month=11, + day=13, + hour=9, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + datetime.datetime( + year=2007, + month=11, + day=15, + hour=17, + tzinfo=UTCOffset(name="UTC", minutes=0), + ), + ), + ( + { + "start": DatetimeTuple( + DateTuple("2007", "11", "13", None, None, None), + TimeTuple("09", "00", None, None), + ), + "end": TimeTuple("12", "34.567", None, None), + }, + datetime.datetime(year=2007, month=11, day=13, hour=9), + datetime.datetime( + year=2007, + month=11, + day=13, + hour=12, + minute=34, + second=34, + microsecond=20000, + ), + ), + ( + { + "start": DateTuple("2007", "11", "13", None, None, None), + "end": TimeTuple("12", "34", None, None), + }, + datetime.date(year=2007, month=11, day=13), + datetime.datetime(year=2007, month=11, day=13, hour=12, minute=34), + ), + # Make sure we truncate, not round + # https://bitbucket.org/nielsenb/aniso8601/issues/10/sub-microsecond-precision-in-durations-is + ( + { + "start": DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00.0000001", None), + ), + "end": DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("14", "43", "59.9999997", None), + ), + }, + datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1), + datetime.datetime( + year=1981, + month=4, + day=5, + hour=14, + minute=43, + second=59, + microsecond=999999, + ), + ), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_interval(**testtuple[0]) + self.assertEqual(result[0], testtuple[1]) + self.assertEqual(result[1], testtuple[2]) + + def test_build_repeating_interval(self): + args = { + "Rnn": "3", + "interval": IntervalTuple( + DateTuple("1981", "04", "05", None, None, None), + None, + DurationTuple(None, None, None, "1", None, None, None), + ), + } + results = list(PythonTimeBuilder.build_repeating_interval(**args)) + + self.assertEqual(results[0], datetime.date(year=1981, month=4, day=5)) + self.assertEqual(results[1], datetime.date(year=1981, month=4, day=6)) + self.assertEqual(results[2], datetime.date(year=1981, month=4, day=7)) + + args = { + "Rnn": "11", + "interval": IntervalTuple( + None, + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DurationTuple(None, None, None, None, "1", "2", None), + ), + } + results = list(PythonTimeBuilder.build_repeating_interval(**args)) + + for dateindex in compat.range(0, 11): + self.assertEqual( + results[dateindex], + datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) + - dateindex * datetime.timedelta(hours=1, minutes=2), + ) + + args = { + "Rnn": "2", + "interval": IntervalTuple( + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + None, + ), + } + results = list(PythonTimeBuilder.build_repeating_interval(**args)) + + self.assertEqual( + results[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) + ) + self.assertEqual( + results[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1) + ) + + args = { + "Rnn": "2", + "interval": IntervalTuple( + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DatetimeTuple( + DateTuple("1981", "04", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + None, + ), + } + results = list(PythonTimeBuilder.build_repeating_interval(**args)) + + self.assertEqual( + results[0], datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) + ) + self.assertEqual( + results[1], datetime.datetime(year=1981, month=4, day=5, hour=1, minute=1) + ) + + args = { + "R": True, + "interval": IntervalTuple( + None, + DatetimeTuple( + DateTuple("1980", "03", "05", None, None, None), + TimeTuple("01", "01", "00", None), + ), + DurationTuple(None, None, None, None, "1", "2", None), + ), + } + resultgenerator = PythonTimeBuilder.build_repeating_interval(**args) + + # Test the first 11 generated + for dateindex in compat.range(0, 11): + self.assertEqual( + next(resultgenerator), + datetime.datetime(year=1980, month=3, day=5, hour=1, minute=1) + - dateindex * datetime.timedelta(hours=1, minutes=2), + ) + + args = { + "R": True, + "interval": IntervalTuple( + DateTuple("1981", "04", "05", None, None, None), + None, + DurationTuple(None, None, None, "1", None, None, None), + ), + } + resultgenerator = PythonTimeBuilder.build_repeating_interval(**args) + + # Test the first 11 generated + for dateindex in compat.range(0, 11): + self.assertEqual( + next(resultgenerator), + ( + datetime.datetime(year=1981, month=4, day=5, hour=0, minute=0) + + dateindex * datetime.timedelta(days=1) + ).date(), + ) + + def test_build_timezone(self): + testtuples = ( + ({"Z": True, "name": "Z"}, datetime.timedelta(hours=0), "UTC"), + ( + {"negative": False, "hh": "00", "mm": "00", "name": "+00:00"}, + datetime.timedelta(hours=0), + "+00:00", + ), + ( + {"negative": False, "hh": "01", "mm": "00", "name": "+01:00"}, + datetime.timedelta(hours=1), + "+01:00", + ), + ( + {"negative": True, "hh": "01", "mm": "00", "name": "-01:00"}, + -datetime.timedelta(hours=1), + "-01:00", + ), + ( + {"negative": False, "hh": "00", "mm": "12", "name": "+00:12"}, + datetime.timedelta(minutes=12), + "+00:12", + ), + ( + {"negative": False, "hh": "01", "mm": "23", "name": "+01:23"}, + datetime.timedelta(hours=1, minutes=23), + "+01:23", + ), + ( + {"negative": True, "hh": "01", "mm": "23", "name": "-01:23"}, + -datetime.timedelta(hours=1, minutes=23), + "-01:23", + ), + ( + {"negative": False, "hh": "00", "name": "+00"}, + datetime.timedelta(hours=0), + "+00", + ), + ( + {"negative": False, "hh": "01", "name": "+01"}, + datetime.timedelta(hours=1), + "+01", + ), + ( + {"negative": True, "hh": "01", "name": "-01"}, + -datetime.timedelta(hours=1), + "-01", + ), + ( + {"negative": False, "hh": "12", "name": "+12"}, + datetime.timedelta(hours=12), + "+12", + ), + ( + {"negative": True, "hh": "12", "name": "-12"}, + -datetime.timedelta(hours=12), + "-12", + ), + ) + + for testtuple in testtuples: + result = PythonTimeBuilder.build_timezone(**testtuple[0]) + self.assertEqual(result.utcoffset(None), testtuple[1]) + self.assertEqual(result.tzname(None), testtuple[2]) + + def test_range_check_date(self): + # 0 isn't a valid year for a Python builder + with self.assertRaises(YearOutOfBoundsError): + PythonTimeBuilder.build_date(YYYY="0000") + + # Leap year + # https://bitbucket.org/nielsenb/aniso8601/issues/14/parsing-ordinal-dates-should-only-allow + with self.assertRaises(DayOutOfBoundsError): + PythonTimeBuilder.build_date(YYYY="1981", DDD="366") + + def test_range_check_time(self): + # Hour 24 can only represent midnight + with self.assertRaises(MidnightBoundsError): + PythonTimeBuilder.build_time(hh="24", mm="00", ss="01") + + with self.assertRaises(MidnightBoundsError): + PythonTimeBuilder.build_time(hh="24", mm="00.1") + + with self.assertRaises(MidnightBoundsError): + PythonTimeBuilder.build_time(hh="24", mm="01") + + with self.assertRaises(MidnightBoundsError): + PythonTimeBuilder.build_time(hh="24.1") + + def test_range_check_duration(self): + with self.assertRaises(YearOutOfBoundsError): + PythonTimeBuilder.build_duration( + PnY=str((datetime.timedelta.max.days // 365) + 1) + ) + + with self.assertRaises(MonthOutOfBoundsError): + PythonTimeBuilder.build_duration( + PnM=str((datetime.timedelta.max.days // 30) + 1) + ) + + with self.assertRaises(DayOutOfBoundsError): + PythonTimeBuilder.build_duration(PnD=str(datetime.timedelta.max.days + 1)) + + with self.assertRaises(WeekOutOfBoundsError): + PythonTimeBuilder.build_duration( + PnW=str((datetime.timedelta.max.days // 7) + 1) + ) + + with self.assertRaises(HoursOutOfBoundsError): + PythonTimeBuilder.build_duration( + TnH=str((datetime.timedelta.max.days * 24) + 24) + ) + + with self.assertRaises(MinutesOutOfBoundsError): + PythonTimeBuilder.build_duration( + TnM=str((datetime.timedelta.max.days * 24 * 60) + 24 * 60) + ) + + with self.assertRaises(SecondsOutOfBoundsError): + PythonTimeBuilder.build_duration( + TnS=str((datetime.timedelta.max.days * 24 * 60 * 60) + 24 * 60 * 60) + ) + + # Split max range across all parts + maxpart = datetime.timedelta.max.days // 7 + + with self.assertRaises(DayOutOfBoundsError): + PythonTimeBuilder.build_duration( + PnY=str((maxpart // 365) + 1), + PnM=str((maxpart // 30) + 1), + PnD=str((maxpart + 1)), + PnW=str((maxpart // 7) + 1), + TnH=str((maxpart * 24) + 1), + TnM=str((maxpart * 24 * 60) + 1), + TnS=str((maxpart * 24 * 60 * 60) + 1), + ) + + def test_range_check_interval(self): + with self.assertRaises(YearOutOfBoundsError): + PythonTimeBuilder.build_interval( + start=DateTuple("0007", None, None, None, None, None), + duration=DurationTuple( + None, None, None, str(datetime.timedelta.max.days), None, None, None + ), + ) + + with self.assertRaises(YearOutOfBoundsError): + PythonTimeBuilder.build_interval( + start=DatetimeTuple( + DateTuple("0007", None, None, None, None, None), + TimeTuple("1", None, None, None), + ), + duration=DurationTuple( + str(datetime.timedelta.max.days // 365), + None, + None, + None, + None, + None, + None, + ), + ) + + with self.assertRaises(YearOutOfBoundsError): + PythonTimeBuilder.build_interval( + end=DateTuple("0001", None, None, None, None, None), + duration=DurationTuple("3", None, None, None, None, None, None), + ) + + with self.assertRaises(YearOutOfBoundsError): + PythonTimeBuilder.build_interval( + end=DatetimeTuple( + DateTuple("0001", None, None, None, None, None), + TimeTuple("1", None, None, None), + ), + duration=DurationTuple("2", None, None, None, None, None, None), + ) + + def test_build_week_date(self): + weekdate = PythonTimeBuilder._build_week_date(2009, 1) + self.assertEqual(weekdate, datetime.date(year=2008, month=12, day=29)) + + weekdate = PythonTimeBuilder._build_week_date(2009, 53, isoday=7) + self.assertEqual(weekdate, datetime.date(year=2010, month=1, day=3)) + + def test_build_ordinal_date(self): + ordinaldate = PythonTimeBuilder._build_ordinal_date(1981, 95) + self.assertEqual(ordinaldate, datetime.date(year=1981, month=4, day=5)) + + def test_iso_year_start(self): + yearstart = PythonTimeBuilder._iso_year_start(2004) + self.assertEqual(yearstart, datetime.date(year=2003, month=12, day=29)) + + yearstart = PythonTimeBuilder._iso_year_start(2010) + self.assertEqual(yearstart, datetime.date(year=2010, month=1, day=4)) + + yearstart = PythonTimeBuilder._iso_year_start(2009) + self.assertEqual(yearstart, datetime.date(year=2008, month=12, day=29)) + + def test_date_generator(self): + startdate = datetime.date(year=2018, month=8, day=29) + timedelta = datetime.timedelta(days=1) + iterations = 10 + + generator = PythonTimeBuilder._date_generator(startdate, timedelta, iterations) + + results = list(generator) + + for dateindex in compat.range(0, 10): + self.assertEqual( + results[dateindex], + datetime.date(year=2018, month=8, day=29) + + dateindex * datetime.timedelta(days=1), + ) + + def test_date_generator_unbounded(self): + startdate = datetime.date(year=2018, month=8, day=29) + timedelta = datetime.timedelta(days=5) + + generator = PythonTimeBuilder._date_generator_unbounded(startdate, timedelta) + + # Check the first 10 results + for dateindex in compat.range(0, 10): + self.assertEqual( + next(generator), + datetime.date(year=2018, month=8, day=29) + + dateindex * datetime.timedelta(days=5), + ) + + def test_distribute_microseconds(self): + self.assertEqual(PythonTimeBuilder._distribute_microseconds(1, (), ()), (1,)) + self.assertEqual( + PythonTimeBuilder._distribute_microseconds(11, (0,), (10,)), (1, 1) + ) + self.assertEqual( + PythonTimeBuilder._distribute_microseconds(211, (0, 0), (100, 10)), + (2, 1, 1), + ) + + self.assertEqual(PythonTimeBuilder._distribute_microseconds(1, (), ()), (1,)) + self.assertEqual( + PythonTimeBuilder._distribute_microseconds(11, (5,), (10,)), (6, 1) + ) + self.assertEqual( + PythonTimeBuilder._distribute_microseconds(211, (10, 5), (100, 10)), + (12, 6, 1), + ) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/compat.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/compat.py new file mode 100644 index 00000000..00cd3f9b --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/compat.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +import sys + +PY2 = sys.version_info[0] == 2 + +if PY2: # pragma: no cover + range = xrange # pylint: disable=undefined-variable +else: + range = range + + +def is_string(tocheck): + # pylint: disable=undefined-variable + if PY2: # pragma: no cover + return isinstance(tocheck, str) or isinstance(tocheck, unicode) + + return isinstance(tocheck, str) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/date.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/date.py new file mode 100644 index 00000000..42128483 --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/date.py @@ -0,0 +1,164 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +from aniso8601.builders import TupleBuilder +from aniso8601.builders.python import PythonTimeBuilder +from aniso8601.compat import is_string +from aniso8601.exceptions import ISOFormatError +from aniso8601.resolution import DateResolution + + +def get_date_resolution(isodatestr): + # Valid string formats are: + # + # Y[YYY] + # YYYY-MM-DD + # YYYYMMDD + # YYYY-MM + # YYYY-Www + # YYYYWww + # YYYY-Www-D + # YYYYWwwD + # YYYY-DDD + # YYYYDDD + isodatetuple = parse_date(isodatestr, builder=TupleBuilder) + + if isodatetuple.DDD is not None: + # YYYY-DDD + # YYYYDDD + return DateResolution.Ordinal + + if isodatetuple.D is not None: + # YYYY-Www-D + # YYYYWwwD + return DateResolution.Weekday + + if isodatetuple.Www is not None: + # YYYY-Www + # YYYYWww + return DateResolution.Week + + if isodatetuple.DD is not None: + # YYYY-MM-DD + # YYYYMMDD + return DateResolution.Day + + if isodatetuple.MM is not None: + # YYYY-MM + return DateResolution.Month + + # Y[YYY] + return DateResolution.Year + + +def parse_date(isodatestr, builder=PythonTimeBuilder): + # Given a string in any ISO 8601 date format, return a datetime.date + # object that corresponds to the given date. Valid string formats are: + # + # YY + # YYYY + # YYYY-MM-DD + # YYYYMMDD + # YYYY-MM + # YYYY-Www + # YYYYWww + # YYYY-Www-D + # YYYYWwwD + # YYYY-DDD + # YYYYDDD + if is_string(isodatestr) is False: + raise ValueError("Date must be string.") + + if isodatestr.startswith("+") or isodatestr.startswith("-"): + raise NotImplementedError( + "ISO 8601 extended year representation not supported." + ) + + if len(isodatestr) == 0 or isodatestr.count("-") > 2: + raise ISOFormatError('"{0}" is not a valid ISO 8601 date.'.format(isodatestr)) + + yearstr = None + monthstr = None + daystr = None + weekstr = None + weekdaystr = None + ordinaldaystr = None + + if len(isodatestr) in (2, 4): + # YY + # YYYY + yearstr = isodatestr + elif "W" in isodatestr: + if len(isodatestr) == 10: + # YYYY-Www-D + yearstr = isodatestr[0:4] + weekstr = isodatestr[6:8] + weekdaystr = isodatestr[9] + elif len(isodatestr) == 8: + if "-" in isodatestr: + # YYYY-Www + yearstr = isodatestr[0:4] + weekstr = isodatestr[6:] + else: + # YYYYWwwD + yearstr = isodatestr[0:4] + weekstr = isodatestr[5:7] + weekdaystr = isodatestr[7] + elif len(isodatestr) == 7: + # YYYYWww + yearstr = isodatestr[0:4] + weekstr = isodatestr[5:] + elif len(isodatestr) == 7: + if "-" in isodatestr: + # YYYY-MM + yearstr = isodatestr[0:4] + monthstr = isodatestr[5:] + else: + # YYYYDDD + yearstr = isodatestr[0:4] + ordinaldaystr = isodatestr[4:] + elif len(isodatestr) == 8: + if "-" in isodatestr: + # YYYY-DDD + yearstr = isodatestr[0:4] + ordinaldaystr = isodatestr[5:] + else: + # YYYYMMDD + yearstr = isodatestr[0:4] + monthstr = isodatestr[4:6] + daystr = isodatestr[6:] + elif len(isodatestr) == 10: + # YYYY-MM-DD + yearstr = isodatestr[0:4] + monthstr = isodatestr[5:7] + daystr = isodatestr[8:] + else: + raise ISOFormatError('"{0}" is not a valid ISO 8601 date.'.format(isodatestr)) + + hascomponent = False + + for componentstr in [yearstr, monthstr, daystr, weekstr, weekdaystr, ordinaldaystr]: + if componentstr is not None: + hascomponent = True + + if componentstr.isdigit() is False: + raise ISOFormatError( + '"{0}" is not a valid ISO 8601 date.'.format(isodatestr) + ) + + if hascomponent is False: + raise ISOFormatError('"{0}" is not a valid ISO 8601 date.'.format(isodatestr)) + + return builder.build_date( + YYYY=yearstr, + MM=monthstr, + DD=daystr, + Www=weekstr, + D=weekdaystr, + DDD=ordinaldaystr, + ) diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/decimalfraction.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/decimalfraction.py new file mode 100644 index 00000000..204d8a3f --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/decimalfraction.py @@ -0,0 +1,12 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + + +def normalize(value): + """Returns the string with decimal separators normalized.""" + return value.replace(",", ".") diff --git a/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/duration.py b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/duration.py new file mode 100644 index 00000000..8509dcef --- /dev/null +++ b/Domains/FullStack/MiniProjects/QuizMaster/venv/lib/python3.12/site-packages/aniso8601/duration.py @@ -0,0 +1,291 @@ +# -*- coding: utf-8 -*- + +# Copyright (c) 2025, Brandon Nielsen +# All rights reserved. +# +# This software may be modified and distributed under the terms +# of the BSD license. See the LICENSE file for details. + +from aniso8601 import compat +from aniso8601.builders import TupleBuilder +from aniso8601.builders.python import PythonTimeBuilder +from aniso8601.date import parse_date +from aniso8601.decimalfraction import normalize +from aniso8601.exceptions import ISOFormatError +from aniso8601.resolution import DurationResolution +from aniso8601.time import parse_time + + +def get_duration_resolution(isodurationstr): + # Valid string formats are: + # + # PnYnMnDTnHnMnS (or any reduced precision equivalent) + # PnW + # PT