diff --git a/.flake8 b/.flake8 index 42ac991b..46b65244 100644 --- a/.flake8 +++ b/.flake8 @@ -1,3 +1,8 @@ [flake8] show_source = True statistics = True + +# E501: line to long. +# E203: whitespace before ':' to accept black code style +# W503: line break before binary operator +ignore = E501,E203,W503 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0778f514..2b8cdbe2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,5 +17,7 @@ jobs: pip install -r requirements-dev.txt - name: Lint run: flake8 --show-source --statistics + - name: Blint + run: black --check --diff . - name: Run tests run: pytest diff --git a/.travis.yml b/.travis.yml index 777ad19f..9e1232ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,4 +9,5 @@ install: script: - flake8 + - black - pytest --check-links --ignore=docs/course_materials/reveal diff --git a/Pipfile b/Pipfile index 963702a7..1352868a 100644 --- a/Pipfile +++ b/Pipfile @@ -7,6 +7,7 @@ name = "pypi" [dev-packages] +black = "*" flake8 = "*" pytest = "*" pytest-coverage = "*" diff --git a/classroom/connect_service.py b/classroom/connect_service.py index 97be7f44..12c78d3d 100644 --- a/classroom/connect_service.py +++ b/classroom/connect_service.py @@ -6,9 +6,11 @@ # If modifying these scopes, delete the file token.pickle. -SCOPES = ['https://www.googleapis.com/auth/classroom.rosters', - 'https://www.googleapis.com/auth/classroom.courses', - 'https://www.googleapis.com/auth/classroom.topics'] +SCOPES = [ + "https://www.googleapis.com/auth/classroom.rosters", + "https://www.googleapis.com/auth/classroom.courses", + "https://www.googleapis.com/auth/classroom.topics", +] def create_service(): @@ -20,21 +22,19 @@ def create_service(): # The file token.pickle stores the user's access and refresh tokens, and is # created automatically when the authorization flow completes for the first # time. - if os.path.exists('token.pickle'): - with open('token.pickle', 'rb') as token: + if os.path.exists("token.pickle"): + with open("token.pickle", "rb") as token: creds = pickle.load(token) # If there are no (valid) credentials available, let the user log in. if not creds or not creds.valid: if creds and creds.expired and creds.refresh_token: creds.refresh(Request()) else: - flow = InstalledAppFlow.from_client_secrets_file( - 'credentials.json', SCOPES) + flow = InstalledAppFlow.from_client_secrets_file("credentials.json", SCOPES) creds = flow.run_local_server(port=0) # Save the credentials for the next run - with open('token.pickle', 'wb') as token: + with open("token.pickle", "wb") as token: pickle.dump(creds, token) - service = build('classroom', 'v1', credentials=creds, - cache_discovery=False) + service = build("classroom", "v1", credentials=creds, cache_discovery=False) return service diff --git a/classroom/content_edit.py b/classroom/content_edit.py index 3b90b2c5..9112a48e 100644 --- a/classroom/content_edit.py +++ b/classroom/content_edit.py @@ -6,21 +6,21 @@ def print_topics(service, course_id): # Call the Classroom API results = service.courses().topics().list(courseId=course_id).execute() - topics = results.get('topic', []) + topics = results.get("topic", []) if not topics: - print('No topics found.') + print("No topics found.") else: - print('Topics:') + print("Topics:") for topic in topics: print(f"Title: {topic['name']}, ID: {topic['topicId']}") def create_topic(service, title, course_id): - topic = {'name': title} - new_topic = service.courses().topics().create( - courseId=course_id, - body=topic).execute() + topic = {"name": title} + new_topic = ( + service.courses().topics().create(courseId=course_id, body=topic).execute() + ) print(f"Topic created: {new_topic['name']} {new_topic['topicId']}") diff --git a/classroom/course_creator.py b/classroom/course_creator.py index ab72ae36..aa9aee01 100644 --- a/classroom/course_creator.py +++ b/classroom/course_creator.py @@ -6,39 +6,43 @@ def print_courses(service): # Call the Classroom API results = service.courses().list(pageSize=10).execute() - courses = results.get('courses', []) + courses = results.get("courses", []) if not courses: - print('No courses found.') + print("No courses found.") else: - print('Courses:') + print("Courses:") for course in courses: print(f"Title: {course['name']}, ID: {course['id']}") def create_course(service, course_name): - course_data = {'name': course_name, - 'descriptionHeading': 'Welcome to ' + course_name, - 'description': ("We'll be learning about coding from a " + - "combination of lectures, course work, " + - "and home projects. Let's start!"), - 'ownerId': 'me', - 'courseState': 'PROVISIONED'} + course_data = { + "name": course_name, + "descriptionHeading": "Welcome to " + course_name, + "description": ( + "We'll be learning about coding from a " + + "combination of lectures, course work, " + + "and home projects. Let's start!" + ), + "ownerId": "me", + "courseState": "PROVISIONED", + } course = service.courses().create(body=course_data).execute() print(f"Course created: {course.get('name')} {course.get('id')}") - return course.get('id') + return course.get("id") def load_list(list_path): - ''' + """ Load student/teacher list from csv file. - ''' + """ if not os.path.exists(list_path): print(f"This path does not exist: {list_path}.") return None - with open(list_path, mode='r') as csv_file: + with open(list_path, mode="r") as csv_file: mail_list = [] - csv_reader = csv.reader(csv_file, delimiter=',') + csv_reader = csv.reader(csv_file, delimiter=",") if csv_reader is None: print("The list is empty.") return mail_list @@ -59,7 +63,7 @@ def create_invitation(service, args, invite_type): Function: - Creates invitations using the mail list. """ - if invite_type == 'STUDENT': + if invite_type == "STUDENT": list_path = args.student_list else: list_path = args.teacher_list @@ -67,17 +71,17 @@ def create_invitation(service, args, invite_type): if len(mail_list) > 0: for mail in mail_list: invitation = { - 'courseId': args.id, - 'role': invite_type, - 'userId': mail, + "courseId": args.id, + "role": invite_type, + "userId": mail, } try: service.invitations().create(body=invitation).execute() - print(f'Invitation was sent to {mail}') + print(f"Invitation was sent to {mail}") except errors.HttpError as e: - if '409' in str(e): - print(f'Not added, {mail} has a pending invitation.') - elif '400' in str(e): - print(f'Not added, {mail} already listed in course.') + if "409" in str(e): + print(f"Not added, {mail} has a pending invitation.") + elif "400" in str(e): + print(f"Not added, {mail} already listed in course.") else: - print('No permissions or wrong course ID.') + print("No permissions or wrong course ID.") diff --git a/classroom/rose_class.py b/classroom/rose_class.py index 7f3c1029..3fcb8e2c 100644 --- a/classroom/rose_class.py +++ b/classroom/rose_class.py @@ -6,75 +6,97 @@ def main(): - ''' + """ Getting user input and preforming corresponding actions. Available functions: - Create course/topic in classroom. - Print existing courses/topics. - Update teacher list. - Update student list. - ''' + """ logging.basicConfig(level=logging.INFO) - parser = argparse.ArgumentParser(description='ROSE Classroom') - parser.add_argument('--course', action='store_true', - help='A flag for course actions, stores True. ' - 'Has to be followed by an action as --create. ' - 'If not specified, will be False.') - parser.add_argument('--topic', action='store_true', - help='A flag for topic actions, stores True. ' - 'Has to be followed by an action as --create. ' - 'If not specified, will be False.') - parser.add_argument('--create', '-c', dest='name', - help='Creating a new instance using given name. ' - 'If not specified, cannot be created. ' - 'Follows an instance type as --course. ' - 'For creating Topics, Assignments and more ' - 'please specify the course/topic id using -i.') - parser.add_argument('--print', '-p', action="store_true", - help='Printing existing instances.') - parser.add_argument('--teacher_list', '-t', dest='teacher_list', - help='Adding teachers using a list, ' - 'expects a csv file. ' - 'If course exists, ' - 'please provide course ID using -i.') - parser.add_argument('--student_list', '-s', dest='student_list', - help='Adding students using a list, ' - 'expects csv file. ' - 'If course exists, ' - 'please provide course ID using -i.') - parser.add_argument('--id', '-i', - help='Specifies an instance id. Can be used for ' - 'adding student lists or teacher lists, adding ' - 'Topics, Homework and more. ' - 'Please specify the needed action. ' - 'Use combined with instance type as --course.') + parser = argparse.ArgumentParser(description="ROSE Classroom") + parser.add_argument( + "--course", + action="store_true", + help="A flag for course actions, stores True. " + "Has to be followed by an action as --create. " + "If not specified, will be False.", + ) + parser.add_argument( + "--topic", + action="store_true", + help="A flag for topic actions, stores True. " + "Has to be followed by an action as --create. " + "If not specified, will be False.", + ) + parser.add_argument( + "--create", + "-c", + dest="name", + help="Creating a new instance using given name. " + "If not specified, cannot be created. " + "Follows an instance type as --course. " + "For creating Topics, Assignments and more " + "please specify the course/topic id using -i.", + ) + parser.add_argument( + "--print", "-p", action="store_true", help="Printing existing instances." + ) + parser.add_argument( + "--teacher_list", + "-t", + dest="teacher_list", + help="Adding teachers using a list, " + "expects a csv file. " + "If course exists, " + "please provide course ID using -i.", + ) + parser.add_argument( + "--student_list", + "-s", + dest="student_list", + help="Adding students using a list, " + "expects csv file. " + "If course exists, " + "please provide course ID using -i.", + ) + parser.add_argument( + "--id", + "-i", + help="Specifies an instance id. Can be used for " + "adding student lists or teacher lists, adding " + "Topics, Homework and more. " + "Please specify the needed action. " + "Use combined with instance type as --course.", + ) args = parser.parse_args() - '''Set up the service to google classroom''' + """Set up the service to google classroom""" service = connect_service.create_service() if args.id and len(args.id) < 12: - print('Please check the ID specified and try again.') + print("Please check the ID specified and try again.") elif args.course and not args.topic: if args.name: args.id = course_creator.create_course(service, args.name) - print(f'The id returned {args.id}') + print(f"The id returned {args.id}") elif args.print: course_creator.print_courses(service) elif not args.id: - print('Please use --help to inspect the possible actions.') + print("Please use --help to inspect the possible actions.") else: if args.teacher_list: - course_creator.create_invitation(service, args, 'TEACHER') + course_creator.create_invitation(service, args, "TEACHER") if args.student_list: - course_creator.create_invitation(service, args, 'STUDENT') + course_creator.create_invitation(service, args, "STUDENT") no_list = args.student_list is None and args.teacher_list is None - if (no_list): - print('Please use -h to check the available actions.') + if no_list: + print("Please use -h to check the available actions.") elif args.topic: if args.course and args.id: if args.print: @@ -83,8 +105,8 @@ def main(): if args.name: content_edit.create_topic(service, args.name, args.id) else: - print('Wrong action') + print("Wrong action") -if __name__ == '__main__': +if __name__ == "__main__": main() diff --git a/docs/course_materials/exercises/09_Extra/class_exercise_1_solution.py b/docs/course_materials/exercises/09_Extra/class_exercise_1_solution.py index 8a6e9896..66bc637c 100644 --- a/docs/course_materials/exercises/09_Extra/class_exercise_1_solution.py +++ b/docs/course_materials/exercises/09_Extra/class_exercise_1_solution.py @@ -1,16 +1,15 @@ - def joing_files(file_name1, file_name2, out_file_name): """ Reads the first two files, joins them and writes the data to the third one. """ try: - file1 = open(file_name1, 'r').read() - file2 = open(file_name2, 'r').read() - open(out_file_name, 'w').write(file1 + file2) - print('The files were joined successfully!') + file1 = open(file_name1, "r").read() + file2 = open(file_name2, "r").read() + open(out_file_name, "w").write(file1 + file2) + print("The files were joined successfully!") except Exception as e: print(e) - print('Reading or writing error with one of the files!') + print("Reading or writing error with one of the files!") file_name_1 = input("Enter the first file name: ") diff --git a/docs/course_materials/exercises/09_Extra/class_exercise_2_solution.py b/docs/course_materials/exercises/09_Extra/class_exercise_2_solution.py index f28b1e35..43010978 100644 --- a/docs/course_materials/exercises/09_Extra/class_exercise_2_solution.py +++ b/docs/course_materials/exercises/09_Extra/class_exercise_2_solution.py @@ -3,4 +3,4 @@ end_of_the_year = datetime(2020, 12, 31) today = datetime.now() time_to_the_end_of_the_year = end_of_the_year - today -print('There are {} days left…'.format(time_to_the_end_of_the_year.days)) +print("There are {} days left…".format(time_to_the_end_of_the_year.days)) diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_1.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_1.py index b894876a..485b56a6 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_1.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_1.py @@ -1,5 +1,5 @@ -exercise_commands = ['mkdir', 'tree'] +exercise_commands = ["mkdir", "tree"] -exercise_paths = ['test', 'test/tmp'] +exercise_paths = ["test", "test/tmp"] exercise_deleted_paths = [] diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.1.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.1.py index 6afd8e1e..dbb85ae2 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.1.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.1.py @@ -1,5 +1,5 @@ -exercise_commands = ['vim', 'cp'] +exercise_commands = ["vim", "cp"] -exercise_paths = ['roses.txt'] +exercise_paths = ["roses.txt"] exercise_deleted_paths = [] diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.2.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.2.py index d9ab2af8..118f5e64 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.2.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_2.2.py @@ -1,5 +1,5 @@ -exercise_commands = ['vim', 'man'] +exercise_commands = ["vim", "man"] -exercise_paths = ['roses.txt'] +exercise_paths = ["roses.txt"] exercise_deleted_paths = [] diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_3.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_3.py index 5a8b187c..410c14cb 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_3.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_3.py @@ -1,8 +1,5 @@ -exercise_commands = ['mkdir', 'cp', 'mv', 'cat'] +exercise_commands = ["mkdir", "cp", "mv", "cat"] -exercise_paths = ['linux3', - 'linux3/roses.txt', - 'linux3/songs', - 'linux3/songs/red.txt'] +exercise_paths = ["linux3", "linux3/roses.txt", "linux3/songs", "linux3/songs/red.txt"] -exercise_deleted_paths = ['linux3/songs/roses.txt'] +exercise_deleted_paths = ["linux3/songs/roses.txt"] diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_4.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_4.py index e160dd86..90fe1bf5 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_4.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_class_exercise_4.py @@ -1,9 +1,11 @@ -exercise_commands = ['mkdir', 'cp', 'touch', 'head', 'tail', 'rm', 'rm -r'] +exercise_commands = ["mkdir", "cp", "touch", "head", "tail", "rm", "rm -r"] exercise_paths = [] -exercise_deleted_paths = ['linux4/roses.txt', - 'linux4', - 'linux4/roses1.txt', - 'linux4/roses2.txt', - 'linux4/roses3.txt'] +exercise_deleted_paths = [ + "linux4/roses.txt", + "linux4", + "linux4/roses1.txt", + "linux4/roses2.txt", + "linux4/roses3.txt", +] diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_1.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_1.py index b9370d91..18b53a27 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_1.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_1.py @@ -1,9 +1,16 @@ -exercise_commands = ['pwd', 'mkdir', 'cd', 'ls', 'tree', 'rm', 'cat', 'vim', - 'website.txt', 'docs'] +exercise_commands = [ + "pwd", + "mkdir", + "cd", + "ls", + "tree", + "rm", + "cat", + "vim", + "website.txt", + "docs", +] -exercise_paths = ['hw1', - 'hw1/web', - 'hw1/other'] +exercise_paths = ["hw1", "hw1/web", "hw1/other"] -exercise_deleted_paths = ['hw1/docs', - 'hw1/web/website.txt'] +exercise_deleted_paths = ["hw1/docs", "hw1/web/website.txt"] diff --git a/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_2.py b/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_2.py index 12f26b8e..39688748 100644 --- a/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_2.py +++ b/docs/course_materials/exercises/test_exercises/01_Linux/check_homework_2.py @@ -1,13 +1,28 @@ -exercise_commands = ['tree', 'cp', 'mv', 'vim', 'touch', 'head', 'tail', - 'diff', 'clear', 'man', 'cat', 'wc', 'wc -l', - 'mkdir songs drafts movies', 'my_song.txt ~/hw2/songs', - 'welcome.txt ~/hw2/docs/backup.txt'] +exercise_commands = [ + "tree", + "cp", + "mv", + "vim", + "touch", + "head", + "tail", + "diff", + "clear", + "man", + "cat", + "wc", + "wc -l", + "mkdir songs drafts movies", + "my_song.txt ~/hw2/songs", + "welcome.txt ~/hw2/docs/backup.txt", +] -exercise_paths = ['hw2/songs', - 'hw2/drafts', - 'hw2/movies', - 'hw2/songs/your_song.txt', - 'hw2/drafts/new_song.txt'] +exercise_paths = [ + "hw2/songs", + "hw2/drafts", + "hw2/movies", + "hw2/songs/your_song.txt", + "hw2/drafts/new_song.txt", +] -exercise_deleted_paths = ['hw2/songs/new_song.txt', - 'hw2/songs/my_song.txt'] +exercise_deleted_paths = ["hw2/songs/new_song.txt", "hw2/songs/my_song.txt"] diff --git a/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_strings.py b/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_strings.py index 7b14846a..41ace869 100644 --- a/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_strings.py +++ b/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_strings.py @@ -18,36 +18,50 @@ @pytest.mark.strings def test_names(helpers): - helpers.set_student_file('names.py') + helpers.set_student_file("names.py") helpers.expected_stdout = False helpers.tests_list = [ # Create a string variable with your name, call it my_name - [r'''\bmy_name\s*=\s*['"]\w+(\s\w+)*["']''', - 'check my_name variable definition'], + [ + r"""\bmy_name\s*=\s*['"]\w+(\s\w+)*["']""", + "check my_name variable definition", + ], # Create a string variable with your family name, # call it my_family_name - [r'''\bmy_family_name\s*=\s*['"]\w+(\s\w+)*["']''', - 'check my_family_name variable definition'], + [ + r"""\bmy_family_name\s*=\s*['"]\w+(\s\w+)*["']""", + "check my_family_name variable definition", + ], # Create a string variable called my_full_name which is composed from # the 2 variables my_name and my_family_name. - [r'''\bmy_full_name\s*=.*\bmy_name\b.*\bmy_family_name\b''', - 'expected my_full_name to be defined by the previous variables'], + [ + r"""\bmy_full_name\s*=.*\bmy_name\b.*\bmy_family_name\b""", + "expected my_full_name to be defined by the previous variables", + ], # Create a variable with your city name: call it my_city_name - [r'''\bmy_city_name\s*=\s*['"]\w+(\s\w+)*['"]''', - 'check my_city_name variable definition'], + [ + r"""\bmy_city_name\s*=\s*['"]\w+(\s\w+)*['"]""", + "check my_city_name variable definition", + ], # Create a variable msg with "My name is X and I’m from Y" using # the variables you created above - [r'''\bmy_message\s*=.*my_(full_)?name.*my_city_name.*''', - 'check my_message variable definition'], - [r'''.*f["']My name is \{my_(full_)?name''' + - r'''\} and I'm from \{my_city_name\}''', - [r'''(.*['"]My name is\s*['"]\s*[+]\s*my_(full_)?name''' + - r'''\s*[+]\s*['"]\s*and I'm from\s*['"]\s*[+]\s*''' + - r'''my_city_name)|(.*['"]My name is \%s and I'm from \%s''' + - r'''['"]\s*\%\s*\(my_(full_)?name,\s*my_city_name\))''', - 'a better definition for my_message should use f"string"', - 'expected the use of f"string" for my_message variable, ' + - 'make sure to include the previous variables']] + [ + r"""\bmy_message\s*=.*my_(full_)?name.*my_city_name.*""", + "check my_message variable definition", + ], + [ + r""".*f["']My name is \{my_(full_)?name""" + + r"""\} and I'm from \{my_city_name\}""", + [ + r"""(.*['"]My name is\s*['"]\s*[+]\s*my_(full_)?name""" + + r"""\s*[+]\s*['"]\s*and I'm from\s*['"]\s*[+]\s*""" + + r"""my_city_name)|(.*['"]My name is \%s and I'm from \%s""" + + r"""['"]\s*\%\s*\(my_(full_)?name,\s*my_city_name\))""", + 'a better definition for my_message should use f"string"', + 'expected the use of f"string" for my_message variable, ' + + "make sure to include the previous variables", + ], + ], ] helpers.test_assignment() @@ -55,18 +69,16 @@ def test_names(helpers): @pytest.mark.strings def test_times(helpers): - helpers.set_student_file('times.py') + helpers.set_student_file("times.py") helpers.expected_pycode = False - expected_msg = ('You have to spend {} minutes this week to ' + - 'complete ROSE homework') - helpers.input = [['2', '3'], ['11', '6'], ['10', '2']] + expected_msg = ( + "You have to spend {} minutes this week to " + "complete ROSE homework" + ) + helpers.input = [["2", "3"], ["11", "6"], ["10", "2"]] helpers.tests_list = [ - [expected_msg.format(6), - f'for input: {helpers.input[0]} expected output: 6'], - [expected_msg.format(66), - f'for input: {helpers.input[1]} expected output: 66'], - [expected_msg.format(20), - f'for input: {helpers.input[2]} expected output: 20'], + [expected_msg.format(6), f"for input: {helpers.input[0]} expected output: 6"], + [expected_msg.format(66), f"for input: {helpers.input[1]} expected output: 66"], + [expected_msg.format(20), f"for input: {helpers.input[2]} expected output: 20"], ] helpers.test_assignment() @@ -74,16 +86,20 @@ def test_times(helpers): @pytest.mark.strings def test_letter(helpers): - helpers.set_student_file('letter.py') + helpers.set_student_file("letter.py") helpers.input = [ - ['3.3.20', 'Anna', 'Rasbery, US', 'Hotel california, US'], + ["3.3.20", "Anna", "Rasbery, US", "Hotel california, US"], ] helpers.tests_list = [ - [r'''\bprint\(f['|"]\{.*[date].*\}\\n\\tFor\\n\\t\{.*[name].*\}.*''', - [r'.*\\n.*\\t.*', - 'For best solution, make sure to enter all the text', - 'make sure to use \\t, \\n and f\' in your print statement.']], + [ + r"""\bprint\(f['|"]\{.*[date].*\}\\n\\tFor\\n\\t\{.*[name].*\}.*""", + [ + r".*\\n.*\\t.*", + "For best solution, make sure to enter all the text", + "make sure to use \\t, \\n and f' in your print statement.", + ], + ], ] helpers.test_assignment() @@ -91,14 +107,12 @@ def test_letter(helpers): @pytest.mark.strings def test_manipulations(helpers): - helpers.set_student_file('manipulations.py') + helpers.set_student_file("manipulations.py") helpers.expected_stdout = False helpers.tests_list = [ # declare the variable s: "...very long line..." - [r'''\b[s]\s*=\s*[\'\'\'].*''', - 'missing \'\'\''], - [r'''.*[\'\'\']''', - 'expected several lines that end with \'\'\''] + [r"""\b[s]\s*=\s*[\'\'\'].*""", "missing '''"], + [r""".*[\'\'\']""", "expected several lines that end with '''"], ] helpers.test_assignment() diff --git a/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_variables.py b/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_variables.py index a808af28..d00ffcc1 100644 --- a/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_variables.py +++ b/docs/course_materials/exercises/test_exercises/03_Variables_and_datatypes/test_homework_variables.py @@ -18,14 +18,16 @@ @pytest.mark.variables def test_variables_1(helpers): - helpers.set_student_file('variables_1.py') + helpers.set_student_file("variables_1.py") helpers.tests_list = [ - [r'^.*x *= *(int\()?\s?9\s?(\))?$', 'x should equal 9'], - [r'^.*y *= *(int\()?\s?7\s?(\))?$', 'y should equal 7'], - [r'^.*z *= *(int\()?\s?x\s?(\))? *\+ *(int\()?\s?y\s?(\))?$', - 'z should equal the sum of x and y'], - [r'^.*print\(.*z.*\).*', 'print message is missing'], - [r'.*16', 'The expected output is 16'] + [r"^.*x *= *(int\()?\s?9\s?(\))?$", "x should equal 9"], + [r"^.*y *= *(int\()?\s?7\s?(\))?$", "y should equal 7"], + [ + r"^.*z *= *(int\()?\s?x\s?(\))? *\+ *(int\()?\s?y\s?(\))?$", + "z should equal the sum of x and y", + ], + [r"^.*print\(.*z.*\).*", "print message is missing"], + [r".*16", "The expected output is 16"], ] helpers.test_assignment() @@ -33,22 +35,21 @@ def test_variables_1(helpers): @pytest.mark.variables def test_variables_2(helpers): - helpers.set_student_file('variables_2.py') - helpers.input = [['10', '3']] - error_message = f'For input: {helpers.input[0]}, ' + helpers.set_student_file("variables_2.py") + helpers.input = [["10", "3"]] + error_message = f"For input: {helpers.input[0]}, " helpers.tests_list = [ - [r'^.*\*.*', 'The arithmetic operator * is missing'], - [r'^.*-.*', 'The arithmetic operator - is missing'], - [r'^.*/.*', 'The arithmetic operator / is missing'], - [r'^.*%.*', f'The arithmetic operator {"%"} is missing'], - [r'\D*30\D*', - error_message + f'the expected multiplication answer is {30}'], - [r'\D*7\D*', - error_message + f'the expected subtraction answer is {7}'], - [r'\D*' + str(10/3) + r'\D*', - error_message + f'the expected devision answer is {10/3}'], - [r'\D*1\D*', - error_message + f'the expected modulo answer is {1}'], + [r"^.*\*.*", "The arithmetic operator * is missing"], + [r"^.*-.*", "The arithmetic operator - is missing"], + [r"^.*/.*", "The arithmetic operator / is missing"], + [r"^.*%.*", f'The arithmetic operator {"%"} is missing'], + [r"\D*30\D*", error_message + f"the expected multiplication answer is {30}"], + [r"\D*7\D*", error_message + f"the expected subtraction answer is {7}"], + [ + r"\D*" + str(10 / 3) + r"\D*", + error_message + f"the expected devision answer is {10/3}", + ], + [r"\D*1\D*", error_message + f"the expected modulo answer is {1}"], ] helpers.test_assignment() @@ -56,21 +57,24 @@ def test_variables_2(helpers): @pytest.mark.variables def test_calculations(helpers): - helpers.set_student_file('calculations.txt') + helpers.set_student_file("calculations.txt") helpers.expected_stdout = False helpers.tests_list = [ # What is the result of 10 ** 3? - [r'\b1000\b', 'For 10 ** 3 the expected result is 1000'], + [r"\b1000\b", "For 10 ** 3 the expected result is 1000"], # Given (x = 1), what will be the value of after we run (x += 2)? - [r'\b3\b', 'for x=1; x+=2 the expected result is 3'], + [r"\b3\b", "for x=1; x+=2 the expected result is 3"], # What is the result of float(1)? - [r'\b1\.0\b', 'For float(1) the expected result is 1.0'], + [r"\b1\.0\b", "For float(1) the expected result is 1.0"], # What is the result of 10 == “10”? - ['False', 'For 10 == "10" the expected result is False'], + ["False", 'For 10 == "10" the expected result is False'], # Print the result of the following variable: # Number = ((((13 * 8 - 4) * 2 + 50) * 4 ) % 127 ) *5 - [r'\b555\b', 'For Number = ((((13 * 8 - 4) * 2 + 50) * 4 ) % 127 )' + - '*5\nthe expected result is 555'], + [ + r"\b555\b", + "For Number = ((((13 * 8 - 4) * 2 + 50) * 4 ) % 127 )" + + "*5\nthe expected result is 555", + ], ] helpers.test_assignment() diff --git a/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_dictionaries.py b/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_dictionaries.py index 55a367c3..0a7ba42c 100644 --- a/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_dictionaries.py +++ b/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_dictionaries.py @@ -18,30 +18,34 @@ @pytest.mark.dictionaries def test_countries(helpers): - helpers.set_student_file('countries.py') + helpers.set_student_file("countries.py") helpers.expected_stdout = False - dictionary_message = str('make sure to set all the values in the ' + - 'dictionary: {"Italy":2, "Spain":3, "Israel":1}') + dictionary_message = str( + "make sure to set all the values in the " + + 'dictionary: {"Italy":2, "Spain":3, "Israel":1}' + ) helpers.tests_list = [ # Create a new dictionary with Udi’s flights number to each country: # "Italy":2 "Spain":3 "Israel":1 - [r'''^.*\s*=\s*\{\s*''', - dictionary_message], - [r'''.*\bItaly\b.*\:\s?2\s?,.*''', - dictionary_message], - [r'''.*\bSpain\b.*\:\s?3\s?,.*''', - dictionary_message], - [r'''.*\bIsrael\b.*\:\s?1\s?.*\}?''', - dictionary_message], + [r"""^.*\s*=\s*\{\s*""", dictionary_message], + [r""".*\bItaly\b.*\:\s?2\s?,.*""", dictionary_message], + [r""".*\bSpain\b.*\:\s?3\s?,.*""", dictionary_message], + [r""".*\bIsrael\b.*\:\s?1\s?.*\}?""", dictionary_message], # Insert a new element : "Belgium":1 - [r'''^.*\[(('Belgium')|("Belgium"))\]\s*=\s*1''', - 'for adding a key-value pair expected: `dict["key"] = value`'], + [ + r"""^.*\[(('Belgium')|("Belgium"))\]\s*=\s*1""", + 'for adding a key-value pair expected: `dict["key"] = value`', + ], # Delete “Italy” record from the dictionary - [r'''^del\s*.*\[(('Italy')|("Italy"))\]$''', - 'for deleting a key-value pair expected: `del dict["key"]'], + [ + r"""^del\s*.*\[(('Italy')|("Italy"))\]$""", + 'for deleting a key-value pair expected: `del dict["key"]', + ], # Print how many times Udi was in Spain - [r'''print\(.*\[(('Spain')|("Spain"))\]\)''', - 'for printing a value expected: `print(dict["key"])`'], + [ + r"""print\(.*\[(('Spain')|("Spain"))\]\)""", + 'for printing a value expected: `print(dict["key"])`', + ], ] helpers.test_assignment() @@ -49,30 +53,44 @@ def test_countries(helpers): @pytest.mark.dictionaries def test_family(helpers): - helpers.set_student_file('family_members_average_age.py') + helpers.set_student_file("family_members_average_age.py") helpers.tests_list = [ # Define a dictionary called ages, ages should be empty in it this time - [r'''^ages\s*=\s*\{\}$''', - 'Expected to see a dictionary definition using `ages = {}`'], + [ + r"""^ages\s*=\s*\{\}$""", + "Expected to see a dictionary definition using `ages = {}`", + ], # Add your family members names to the dictionary with their ages. # for example: “Samira”:8 - [r'''(ages\.update\({)|(ages\[.*\]=\d*)''', - 'Expected the use of dict.update() or dict(key)=value for updating'], + [ + r"""(ages\.update\({)|(ages\[.*\]=\d*)""", + "Expected the use of dict.update() or dict(key)=value for updating", + ], # Print the dictionary - [r'''^print\(.*\bages\b.*\)''', - 'Expected the use of print(dict) for printing.'], - [r'''(\'\w*\':\s\d*)|(r'^[^{].*\b\d*\b')''', - 'please print the dictionary `ages`'], + [ + r"""^print\(.*\bages\b.*\)""", + "Expected the use of print(dict) for printing.", + ], + [ + r"""(\'\w*\':\s\d*)|(r'^[^{].*\b\d*\b')""", + "please print the dictionary `ages`", + ], # Calculate the average age in your family using the dictionary # and print it. - [r'''(for.*in ages\.values\(\):)|(sum\(ages\.values\(\)\))|''' + - r'''(ages\[.*\]\s*\+\s*ages\[.*\])''', - 'Expected the use of: sum(dict.values() or ' + - 'for loop on dict.values or by adding dict[key]'], - [r'''(\/\s*len\(ages\))''', - [r'''(\/\s*\d)''', - 'A better way to calculate average is using len(dict)', - 'to calculate average you can divide by using len(dict)']], + [ + r"""(for.*in ages\.values\(\):)|(sum\(ages\.values\(\)\))|""" + + r"""(ages\[.*\]\s*\+\s*ages\[.*\])""", + "Expected the use of: sum(dict.values() or " + + "for loop on dict.values or by adding dict[key]", + ], + [ + r"""(\/\s*len\(ages\))""", + [ + r"""(\/\s*\d)""", + "A better way to calculate average is using len(dict)", + "to calculate average you can divide by using len(dict)", + ], + ], ] helpers.test_assignment() diff --git a/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_lists.py b/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_lists.py index 5b67f71f..f33579cc 100644 --- a/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_lists.py +++ b/docs/course_materials/exercises/test_exercises/04_Compound_data_types/test_homework_lists.py @@ -18,99 +18,139 @@ @pytest.mark.lists def test_lists(helpers): - helpers.set_student_file('lists.py') + helpers.set_student_file("lists.py") helpers.tests_list = [ # comment for Create a list exercise - [r'''^[#]+.*((C|c)reate|1)''', - '1. Expected a comment for exercise 1 - create a list'], + [ + r"""^[#]+.*((C|c)reate|1)""", + "1. Expected a comment for exercise 1 - create a list", + ], # Create two empty lists list1 and list2 in different ways - [r'''^list.*\s*=\s*\[\]''', - '1.1. Expected the list to be set by using `= []`'], - [r'''^list.*\s*=\s*list\(\)''', - '1.1. Expected the list to be set by using list()'], + [r"""^list.*\s*=\s*\[\]""", "1.1. Expected the list to be set by using `= []`"], + [ + r"""^list.*\s*=\s*list\(\)""", + "1.1. Expected the list to be set by using list()", + ], # Create a list l1 with the numbers: 2,3,4,5,6. - [r'''^l1\s*=\s*\[2,\s*3,\s*4,\s*5,\s*6\]''', - '1.2. List l1 is not defined properly'], + [ + r"""^l1\s*=\s*\[2,\s*3,\s*4,\s*5,\s*6\]""", + "1.2. List l1 is not defined properly", + ], # Print the index of the number 6 - [r'''l1\.index\(6\)''', - '1.3. Expected the use of list.index()'], - [r'.*?\b4\b(.*[^\]])?$', - '1.3. Index of number 6 is incorrect'], + [r"""l1\.index\(6\)""", "1.3. Expected the use of list.index()"], + [r".*?\b4\b(.*[^\]])?$", "1.3. Index of number 6 is incorrect"], # Append the numbers 7 and 8 to the end of l1 - [r'''l1\.(extend|append)\(\[?7.*''', - '1.4. Expected list.extend([]) or list.append() to add values'], + [ + r"""l1\.(extend|append)\(\[?7.*""", + "1.4. Expected list.extend([]) or list.append() to add values", + ], # Print the length of the l1 - [r'''len\(l1\)''', - '1.5. Expected len(list) for getting the length of the list'], - [r'.*?\b7\b(.*[^\]])?$', - '1.5. Make sure to print out the length of l1'], + [ + r"""len\(l1\)""", + "1.5. Expected len(list) for getting the length of the list", + ], + [r".*?\b7\b(.*[^\]])?$", "1.5. Make sure to print out the length of l1"], # Add the number 1 to the l1 at index 0 - [r'''l1\.insert\(0,\s*1\)''', - '1.6. Expected the use of list.insert(index, value)'], + [ + r"""l1\.insert\(0,\s*1\)""", + "1.6. Expected the use of list.insert(index, value)", + ], # comment for Slicing a list exercise - [r'''^[#]+.*((S|s)licing|2)''', - '2. Expected a comment for exercise 2 - Slicing a list'], + [ + r"""^[#]+.*((S|s)licing|2)""", + "2. Expected a comment for exercise 2 - Slicing a list", + ], # Print first 4 numbers of l1 - [r'''(print\()?l1\[0?:4\]\)?''', - [r'for.*in range\(.*?4\)\:', - '2.1 A better solution is using slicing - list[:4]', - '2.1. Expected the use of slicing for printing a sequence']], - [r'\[1, 2, 3, 4\]', - [r'.*?\b1\b(.*[^\]])?$', - '2.1. Expected a one line print of the first 4 numbers', - '2.1. Expected the print of the first 4 numbers: 1,2,3,4']], + [ + r"""(print\()?l1\[0?:4\]\)?""", + [ + r"for.*in range\(.*?4\)\:", + "2.1 A better solution is using slicing - list[:4]", + "2.1. Expected the use of slicing for printing a sequence", + ], + ], + [ + r"\[1, 2, 3, 4\]", + [ + r".*?\b1\b(.*[^\]])?$", + "2.1. Expected a one line print of the first 4 numbers", + "2.1. Expected the print of the first 4 numbers: 1,2,3,4", + ], + ], # Print 4 last numbers of l1 - [r'''(print\()?l1\[-4:\]\)?''', - [r'for.*in range\(4,\s?8\)\:', - '2.2 A better solution is using slicing list[-4:]', - '2.2. Expected the use of slicing a list ' + - 'with negative values for printing a sequence']], - [r'\[5, 6, 7, 8\]', - [r'.*?\b5\b(.*[^\]])?$', - '2.2. Expected a one line print of the last 4 numbers', - '2.2. Expected the print of the last 4 numbers: 5,6,7,8']], + [ + r"""(print\()?l1\[-4:\]\)?""", + [ + r"for.*in range\(4,\s?8\)\:", + "2.2 A better solution is using slicing list[-4:]", + "2.2. Expected the use of slicing a list " + + "with negative values for printing a sequence", + ], + ], + [ + r"\[5, 6, 7, 8\]", + [ + r".*?\b5\b(.*[^\]])?$", + "2.2. Expected a one line print of the last 4 numbers", + "2.2. Expected the print of the last 4 numbers: 5,6,7,8", + ], + ], # Print the two numbers in the middle - [r'''(print\()?l1\[((3:5)|(\(len\(l1\)\/2\-1\):''' + - r'''\(len\(l1\)\/2\+1\)))\]\)''', - '2.3. Expected the use of len(list) in retrieving the middle values'], - [r'\[4, 5\]', - '2.3. Expected the print of the 2 middle numbers'], + [ + r"""(print\()?l1\[((3:5)|(\(len\(l1\)\/2\-1\):""" + + r"""\(len\(l1\)\/2\+1\)))\]\)""", + "2.3. Expected the use of len(list) in retrieving the middle values", + ], + [r"\[4, 5\]", "2.3. Expected the print of the 2 middle numbers"], # Print l1[20], which error did you get? # What does this error say? (write your answer in a comment #) - [r'''^[#].*IndexError: list index out of range''', - '2.4. Expected the `IndexError: ...`'], + [ + r"""^[#].*IndexError: list index out of range""", + "2.4. Expected the `IndexError: ...`", + ], # comment for Update a list exercise - [r'''^[#]+.*((U|u)pdate|3)''', - '3. Expected a comment for exercise 3 - Update the list'], + [ + r"""^[#]+.*((U|u)pdate|3)""", + "3. Expected a comment for exercise 3 - Update the list", + ], # Remove the items 4 and 5 from the l1 and print l1 - [r'''(del l1\[3:5\])|(l1.remove\(4\))''', - '3.1. Expected the use of `del list[:]` or list.remove(i)'], - [r'\[1, 2, 3, 6, 7, 8\]', - '3.1. Expected a print of l1 list after removing the middle numbers'], + [ + r"""(del l1\[3:5\])|(l1.remove\(4\))""", + "3.1. Expected the use of `del list[:]` or list.remove(i)", + ], + [ + r"\[1, 2, 3, 6, 7, 8\]", + "3.1. Expected a print of l1 list after removing the middle numbers", + ], # Create a new list l2, with the items: -1, -2 ,-3 and print l2 - [r'''^l2\s*=\s*\[\-1,\s*\-2,\s*\-3\]''', - '3.2. Expected the definition of l2 as a list of numbers'], - [r'print\(.*l2.*\)', - '3.2. Expected a print out of l2 list'], + [ + r"""^l2\s*=\s*\[\-1,\s*\-2,\s*\-3\]""", + "3.2. Expected the definition of l2 as a list of numbers", + ], + [r"print\(.*l2.*\)", "3.2. Expected a print out of l2 list"], # Create a new list l3, with the items of l1 and l2, print l3 - [r'''(l3\s*\=\s*l1\s*\+\s*l2)|(l3\.extend\(l1\))''', - '3.3. Expected l3 to be defined by l1 and l2'], - [r'\[1, 2, 3, 6, 7, 8, \-1, \-2, \-3\]', - '3.3. Expected the print of l3 list that contains l1 and l2'], + [ + r"""(l3\s*\=\s*l1\s*\+\s*l2)|(l3\.extend\(l1\))""", + "3.3. Expected l3 to be defined by l1 and l2", + ], + [ + r"\[1, 2, 3, 6, 7, 8, \-1, \-2, \-3\]", + "3.3. Expected the print of l3 list that contains l1 and l2", + ], # Sort l3 and print the sorted list - [r'''(print\()?l3\.sort\(\)''', - '3.4. Expected the use of list.sort() for sorting a list'], - [r'\[\-3, \-2, \-1, 1, 2, 3, 6, 7, 8\]', - '3.4. Expected a print of the sorted l3 list'], + [ + r"""(print\()?l3\.sort\(\)""", + "3.4. Expected the use of list.sort() for sorting a list", + ], + [ + r"\[\-3, \-2, \-1, 1, 2, 3, 6, 7, 8\]", + "3.4. Expected a print of the sorted l3 list", + ], # Print the length of l1, l2, and l3 - [r'''(print\()?.*\blen\(l1\).*\)?''', - '3.5. Expected the use of len(list)'], - [r'.*?\b6\b(.*[^\]])?$', - '3.5. len of l1 should be 6'], - [r'.*?\b3\b(.*[^\]])?$', - '3.5. len of l2 should be 3'], - [r'.*?\b9\b(.*[^\]])?$', - '3.5. len of l3 should be 9'], + [r"""(print\()?.*\blen\(l1\).*\)?""", "3.5. Expected the use of len(list)"], + [r".*?\b6\b(.*[^\]])?$", "3.5. len of l1 should be 6"], + [r".*?\b3\b(.*[^\]])?$", "3.5. len of l2 should be 3"], + [r".*?\b9\b(.*[^\]])?$", "3.5. len of l3 should be 9"], ] helpers.test_assignment() diff --git a/docs/course_materials/exercises/test_exercises/color_print.py b/docs/course_materials/exercises/test_exercises/color_print.py index b91bdde6..0d294730 100644 --- a/docs/course_materials/exercises/test_exercises/color_print.py +++ b/docs/course_materials/exercises/test_exercises/color_print.py @@ -1,12 +1,12 @@ -''' +""" Setting colors for the output: green for positive feedback red negative feedback yellow for commands and paths -''' -GREEN = '\033[92m' -RED = '\033[91m' -YELLOW = '\033[93m' +""" +GREEN = "\033[92m" +RED = "\033[91m" +YELLOW = "\033[93m" def info(feedback, new_line=True): @@ -22,11 +22,11 @@ def negative(feedback, new_line=True): def print_color(feedback, color, new_line): - ''' + """ Used to print in color with an option for new line. - ''' + """ if new_line: - p_end = '\n' + p_end = "\n" else: - p_end = '' - print(f'{color} {feedback}\033[00m', end=p_end) + p_end = "" + print(f"{color} {feedback}\033[00m", end=p_end) diff --git a/docs/course_materials/exercises/test_exercises/command_dictionary.py b/docs/course_materials/exercises/test_exercises/command_dictionary.py index c9f30e61..dfb15814 100644 --- a/docs/course_materials/exercises/test_exercises/command_dictionary.py +++ b/docs/course_materials/exercises/test_exercises/command_dictionary.py @@ -1,20 +1,20 @@ command_dictionary = { - 'pwd': 'printing current directory name', - 'mkdir': 'creating directories', - 'cd': 'changing between directories', - 'ls': 'listing directory contents', - 'tree': 'viewing the directory tree', - 'rm': 'removing a file or directory', - 'touch': 'creating new files', - 'cat': 'printing the contents of a file', - 'vim': 'editing a file', - 'cp': 'coping a file to a new file', - 'mv': 'moving directories or files', - 'head': 'printing the first part of files', - 'tail': 'printing the last part of files', - 'diff': 'compare files line by line', - 'clear': 'clearing the screen', - 'man': 'opening manual', - 'wc': 'counting words in file', - 'wc -l': 'counting lines in file' - } + "pwd": "printing current directory name", + "mkdir": "creating directories", + "cd": "changing between directories", + "ls": "listing directory contents", + "tree": "viewing the directory tree", + "rm": "removing a file or directory", + "touch": "creating new files", + "cat": "printing the contents of a file", + "vim": "editing a file", + "cp": "coping a file to a new file", + "mv": "moving directories or files", + "head": "printing the first part of files", + "tail": "printing the last part of files", + "diff": "compare files line by line", + "clear": "clearing the screen", + "man": "opening manual", + "wc": "counting words in file", + "wc -l": "counting lines in file", +} diff --git a/docs/course_materials/exercises/test_exercises/conftest.py b/docs/course_materials/exercises/test_exercises/conftest.py index baa181e5..2378ff7a 100644 --- a/docs/course_materials/exercises/test_exercises/conftest.py +++ b/docs/course_materials/exercises/test_exercises/conftest.py @@ -25,7 +25,7 @@ def home_folder(): class Test_helpers: def __init__(self): - self.student_file = '' + self.student_file = "" self.expected_pycode = True self.expected_stdout = True self.tests_list = [] @@ -33,103 +33,101 @@ def __init__(self): self.exact_answer = False def test_assignment(self): - ''' + """ Runs all checks on a assignment 1. If student answer file exists 2. If the written code corresponds to the requirements 3. If the output corresponds to the requirements - ''' - LOGGER.info(f'Started testing {self.student_file}:') + """ + LOGGER.info(f"Started testing {self.student_file}:") self.test_file_exist() - student_work = '' + student_work = "" # Runs the code and gets stdout if self.expected_stdout: - LOGGER.info('Running the code...') + LOGGER.info("Running the code...") if self.input: for data in self.input: student_work += self.run_cmd(data) else: student_work += self.run_cmd() - LOGGER.debug(f'run CMD, {student_work}') + LOGGER.debug(f"run CMD, {student_work}") # Reads code file if self.expected_pycode: student_work += self.get_student_code() # Tests the code - LOGGER.info('Testing the code...') + LOGGER.info("Testing the code...") LOGGER.debug(student_work) test_code, message = self.test_answers(self.tests_list, student_work) LOGGER.debug(message) - assert test_code, LOGGER.info('Good job, but needs ' + - f'some improvements:\n{message[:-1]}') - if message != '': - LOGGER.info(f'Better code can be achieved by:\n{message}') + assert test_code, LOGGER.info( + "Good job, but needs " + f"some improvements:\n{message[:-1]}" + ) + if message != "": + LOGGER.info(f"Better code can be achieved by:\n{message}") def test_file_exist(self): - ''' + """ Checks if the student file exists - ''' + """ assert os.path.exists(self.student_file), LOGGER.warning( - 'Student homework file ' + - 'not found: ' + - self.student_file) + "Student homework file " + "not found: " + self.student_file + ) def get_student_code(self): - ''' + """ Returns the student code/text as an array by lines - ''' - with open(self.student_file, 'r') as f: + """ + with open(self.student_file, "r") as f: student_code = f.read() return student_code def run_cmd(self, data=[], **kwargs): - ''' + """ Simulates a cmd action to check output - ''' - input_data = ('\n'.join(data).encode('utf-8') if data - else None) + """ + input_data = "\n".join(data).encode("utf-8") if data else None - p = Popen(['python', self.student_file], stdout=PIPE, stdin=PIPE, - stderr=PIPE) + p = Popen(["python", self.student_file], stdout=PIPE, stdin=PIPE, stderr=PIPE) try: - stdout, stderr = p.communicate(input=input_data, timeout=20, - **kwargs) + stdout, stderr = p.communicate(input=input_data, timeout=20, **kwargs) except TimeoutExpired: p.kill() stdout, stderr = p.communicate() if p.returncode != 0: - LOGGER.error('Failed while running' + - f' the code. The error is:\n{stderr.decode()}') + LOGGER.error( + "Failed while running" + f" the code. The error is:\n{stderr.decode()}" + ) - return stdout.decode('utf-8') + return stdout.decode("utf-8") @staticmethod def test_answers(expected_list, answer_list, word_pattern=False): - ''' + """ Compares students answers with expected ones. - ''' + """ better_code_only = True - message = '' - LOGGER.debug(f'expected: {expected_list}') + message = "" + LOGGER.debug(f"expected: {expected_list}") for answer in expected_list: - pattern = f'\\b{answer[0]}\\b' if word_pattern else answer[0] - LOGGER.debug(f'pattern: {pattern}') + pattern = f"\\b{answer[0]}\\b" if word_pattern else answer[0] + LOGGER.debug(f"pattern: {pattern}") answers = answer_list if word_pattern else answer_list.splitlines() - LOGGER.debug(f'answers: {answers}') + LOGGER.debug(f"answers: {answers}") matched = Test_helpers.test_answer_match(pattern, answers) if not matched: if isinstance(answer[1], list): test, better_message = Test_helpers.test_lesser_answers( - answer[1], - answers) + answer[1], answers + ) message += better_message if not test: better_code_only = False else: - message += answer[1] + '\n' + message += answer[1] + "\n" better_code_only = False return better_code_only, message @@ -140,7 +138,7 @@ def set_student_file(self, file): def test_answer_match(pattern, answers): matched = False for line in answers: - LOGGER.debug(f'line: |{line}|') + LOGGER.debug(f"line: |{line}|") if re.search(pattern, line, re.MULTILINE): LOGGER.debug("MATCHED") matched = True @@ -152,8 +150,8 @@ def test_lesser_answers(expected_answer, answers): LOGGER.debug("Checking lesser answers") matched = Test_helpers.test_answer_match(expected_answer[0], answers) if not matched: - return False, expected_answer[2]+'\n' - return True, expected_answer[1]+'\n' + return False, expected_answer[2] + "\n" + return True, expected_answer[1] + "\n" @pytest.fixture diff --git a/docs/course_materials/exercises/test_exercises/linux_tester.py b/docs/course_materials/exercises/test_exercises/linux_tester.py index dddd1e1b..2a4f4c79 100644 --- a/docs/course_materials/exercises/test_exercises/linux_tester.py +++ b/docs/course_materials/exercises/test_exercises/linux_tester.py @@ -4,23 +4,23 @@ import command_dictionary as cd # set from rose_check.py from the check_exercise file. -HOME = '' +HOME = "" COMMANDS = [] PATHS = [] DELETED_PATHS = [] def get_student_commands(): - ''' + """ Checks student history file for key commands in the past 'x' days. - ''' - shell_type = os.path.basename(os.environ['SHELL']) + """ + shell_type = os.path.basename(os.environ["SHELL"]) # used to get the epoch for x days back past_date = str((dt.datetime.today() - dt.timedelta(days=10)).timestamp()) - hist_path = HOME + '/.' + shell_type + '_history' + hist_path = HOME + "/." + shell_type + "_history" my_commands = [] - with open(hist_path, 'r') as rf: + with open(hist_path, "r") as rf: for line in rf: if line[2:12] >= past_date: my_commands.append(line[:-1]) @@ -29,29 +29,30 @@ def get_student_commands(): def print_command_description(command): if cd.command_dictionary.get(command) is not None: - print(f' for {cd.command_dictionary[command]} ... ', end='') + print(f" for {cd.command_dictionary[command]} ... ", end="") else: - print(' checking command structure', end='') + print(" checking command structure", end="") def used_all_commands(): student_commands = get_student_commands() num_of_used_commands = 0 - print('Checking used commands...') + print("Checking used commands...") for command in COMMANDS: # color_print.info('"' + command + '"', 0) color_print.info(command, 0) print_command_description(command) # Print feedback about the command - if not any(command in performed_command - for performed_command in student_commands): - color_print.negative('Missing') + if not any( + command in performed_command for performed_command in student_commands + ): + color_print.negative("Missing") else: - color_print.positive('Used') + color_print.positive("Used") num_of_used_commands += 1 - print(f'Got {str(num_of_used_commands)} out of {str(len(COMMANDS))}\n') + print(f"Got {str(num_of_used_commands)} out of {str(len(COMMANDS))}\n") if num_of_used_commands == len(COMMANDS): return True return False @@ -59,40 +60,42 @@ def used_all_commands(): def created(check_path): if os.path.exists(check_path): - color_print.positive('Created') + color_print.positive("Created") return 1 else: - color_print.negative('Missing') + color_print.negative("Missing") return 0 def deleted(check_path): if not os.path.exists(check_path): - color_print.positive('Deleted') + color_print.positive("Deleted") return 1 else: - color_print.negative('Was not deleted') + color_print.negative("Was not deleted") return 0 def check_all_paths(path_type): - ''' + """ paths_to_check will get a list of exercise paths to create or delete. path_type will get CREATED or DELETED. - ''' - case_type = {'created': created, 'deleted': deleted} - path_to_check = {'created': PATHS, 'deleted': DELETED_PATHS} + """ + case_type = {"created": created, "deleted": deleted} + path_to_check = {"created": PATHS, "deleted": DELETED_PATHS} num_of_paths = 0 - print('Checking ' + path_type + ' directories or files...') + print("Checking " + path_type + " directories or files...") for cur_path in path_to_check[path_type]: check_path = HOME + cur_path # color_print.info('"' + check_path + '"', 0) color_print.info(check_path, 0) - print(' ... ', end='') + print(" ... ", end="") num_of_paths += case_type[path_type](check_path) - print(f'{path_type.capitalize()} {str(num_of_paths)} out of ' - f'{str(len(path_to_check[path_type]))} directories.\n') + print( + f"{path_type.capitalize()} {str(num_of_paths)} out of " + f"{str(len(path_to_check[path_type]))} directories.\n" + ) if num_of_paths == len(path_to_check[path_type]): return True return False @@ -100,11 +103,11 @@ def check_all_paths(path_type): def is_exercise_done(): check_commands = used_all_commands() - check_paths = check_all_paths('created') + check_paths = check_all_paths("created") # run the function only if the check is needed if len(DELETED_PATHS) > 0: - check_deleted_paths = check_all_paths('deleted') + check_deleted_paths = check_all_paths("deleted") else: check_deleted_paths = True diff --git a/docs/course_materials/exercises/test_exercises/rose_check.py b/docs/course_materials/exercises/test_exercises/rose_check.py index c605e10c..9d0862eb 100644 --- a/docs/course_materials/exercises/test_exercises/rose_check.py +++ b/docs/course_materials/exercises/test_exercises/rose_check.py @@ -10,12 +10,12 @@ import pytest import linux_tester -HOME = '' -LINUX_FOLDER = './01_Linux/' +HOME = "" +LINUX_FOLDER = "./01_Linux/" def load_file(file_path): - ''' + """ Load the file as module Arguments: @@ -26,12 +26,11 @@ def load_file(file_path): Raises: FileNotFoundError if the file cannot be loaded - ''' + """ module_name = os.path.split(os.path.splitext(file_path)[0])[1] - module_spec = importlib.util.spec_from_file_location(module_name, - file_path) + module_spec = importlib.util.spec_from_file_location(module_name, file_path) if module_spec is None: - print(f'Module: {module_name} not found') + print(f"Module: {module_name} not found") return None else: try: @@ -39,14 +38,14 @@ def load_file(file_path): module_spec.loader.exec_module(module) return module except FileNotFoundError as e: - print(f'Error loading the file {file_path}: {e.strerror}') + print(f"Error loading the file {file_path}: {e.strerror}") sys.exit(2) def get_exe_list(): exercise_list = [] for file in os.listdir(LINUX_FOLDER): - if 'check_' in file and '.pyc' not in file: + if "check_" in file and ".pyc" not in file: exercise_list.append(file) exercise_list.sort() return exercise_list @@ -54,29 +53,29 @@ def get_exe_list(): def get_markers(): config = ConfigParser() - config.read('pytest.ini') + config.read("pytest.ini") # read values from a pytest section - markers = config.get('pytest', 'markers').split('\n') - select_markers = [x.split(':')[0] for x in markers if len(x) > 0] + markers = config.get("pytest", "markers").split("\n") + select_markers = [x.split(":")[0] for x in markers if len(x) > 0] select_markers.sort() return select_markers def update_folder(folder): - if folder == '': - HOME = (str(Path.home())+'/') + if folder == "": + HOME = str(Path.home()) + "/" else: - if folder[-1] != '/': - folder += '/' + if folder[-1] != "/": + folder += "/" HOME = folder return HOME -''' +""" Calling the corresponding test file. -''' +""" logging.basicConfig(level=logging.INFO) -parser = argparse.ArgumentParser(description='ROSE Exercise') +parser = argparse.ArgumentParser(description="ROSE Exercise") # getting exercise list fir linux exercises exercise_list = get_exe_list() @@ -84,44 +83,58 @@ def update_folder(folder): select_markers = get_markers() # Setting up the input options -parser = argparse.ArgumentParser(description='details', - usage='use "%(prog)s --help" for more ' - 'information', - formatter_class=argparse.RawTextHelpFormatter) -parser.add_argument('--exercises', '-e', action='store_true', - help='A flag for printing available exercises, ' - 'stores True. If not specified, will be False.') -parser.add_argument('--test_exercise', '-t', dest='test_exercise', - default='', - help='Allows to choose the exercise to be checked. ' - '(If not specified, the test will not execute).' - '\nMake sure to use the exact exercise name.') -parser.add_argument('--set_dir', '-s', dest='custom_dir', - default='', - help='Custom definition of the test directory or ' - 'HOME directory in Linux, ' - 'for example: /home/student/.\n' - 'You should enter the directory that contains ' - 'student files to be checked. \nIf not specified, ' - 'standard HOME folder will be used.') +parser = argparse.ArgumentParser( + description="details", + usage='use "%(prog)s --help" for more ' "information", + formatter_class=argparse.RawTextHelpFormatter, +) +parser.add_argument( + "--exercises", + "-e", + action="store_true", + help="A flag for printing available exercises, " + "stores True. If not specified, will be False.", +) +parser.add_argument( + "--test_exercise", + "-t", + dest="test_exercise", + default="", + help="Allows to choose the exercise to be checked. " + "(If not specified, the test will not execute)." + "\nMake sure to use the exact exercise name.", +) +parser.add_argument( + "--set_dir", + "-s", + dest="custom_dir", + default="", + help="Custom definition of the test directory or " + "HOME directory in Linux, " + "for example: /home/student/.\n" + "You should enter the directory that contains " + "student files to be checked. \nIf not specified, " + "standard HOME folder will be used.", +) args = parser.parse_args() # Running the tests according to user input if args.exercises: - print('Available tests for Linux are:\n\t' - + '\n\t'.join(exercise_list) - + '\nAvailable tests for Python are:\n\t' - + '\n\t'.join(select_markers)) + print( + "Available tests for Linux are:\n\t" + + "\n\t".join(exercise_list) + + "\nAvailable tests for Python are:\n\t" + + "\n\t".join(select_markers) + ) elif args.test_exercise: finished = False HOME = update_folder(args.custom_dir) - if '.py' in args.test_exercise: + if ".py" in args.test_exercise: # Running Linux tests if args.test_exercise in exercise_list: print(os.path.join(LINUX_FOLDER, args.test_exercise)) - exercise_mod = load_file(os.path.join(LINUX_FOLDER, - args.test_exercise)) + exercise_mod = load_file(os.path.join(LINUX_FOLDER, args.test_exercise)) linux_tester.COMMANDS = exercise_mod.exercise_commands linux_tester.PATHS = exercise_mod.exercise_paths linux_tester.DELETED_PATHS = exercise_mod.exercise_deleted_paths @@ -129,11 +142,11 @@ def update_folder(folder): finished = linux_tester.is_exercise_done() else: # Running Python tests - result = pytest.main(['-m ' + args.test_exercise]) + result = pytest.main(["-m " + args.test_exercise]) finished = True if result == 0 else False if finished: - print('Great work! You finished your exercise.') + print("Great work! You finished your exercise.") else: - print('Great effort! Try to correct the failed assignments.') + print("Great effort! Try to correct the failed assignments.") else: - print('Please reffer to `--help` for the available options.') + print("Please reffer to `--help` for the available options.") diff --git a/requirements-dev.txt b/requirements-dev.txt index 9deb44a4..87f645a1 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,4 +1,5 @@ autobahn +black flake8 pytest pytest-check-links diff --git a/rose/client/__init__.py b/rose/client/__init__.py index 81e37afa..8e79edb3 100644 --- a/rose/client/__init__.py +++ b/rose/client/__init__.py @@ -1 +1 @@ -__author__ = 'gickowic' +__author__ = "gickowic" diff --git a/rose/client/car.py b/rose/client/car.py index c4d79ac4..c6e05863 100644 --- a/rose/client/car.py +++ b/rose/client/car.py @@ -2,7 +2,6 @@ class Car(component.Component): - def __init__(self, id): self.id = id self.x = None @@ -10,6 +9,6 @@ def __init__(self, id): self.name = None def update(self, info): - self.x = info['x'] - self.y = info['y'] - self.name = info['name'] + self.x = info["x"] + self.y = info["y"] + self.name = info["name"] diff --git a/rose/client/component.py b/rose/client/component.py index a815519a..e24dab01 100644 --- a/rose/client/component.py +++ b/rose/client/component.py @@ -1,5 +1,5 @@ class Component(object): - """ Component intarface """ + """Component intarface""" def update(self, info): """ diff --git a/rose/client/game.py b/rose/client/game.py index 645d7da3..5d54e64b 100644 --- a/rose/client/game.py +++ b/rose/client/game.py @@ -10,32 +10,28 @@ from . import world from . import component -author = 'gickowic' -log = logging.getLogger('game') +author = "gickowic" +log = logging.getLogger("game") class Game(component.Component): - def __init__(self, client, name, drive_func): self.client = client self.drive_func = drive_func self.name = name self.track = track.Track() self.players = {} - self.cars = [car.Car(1), - car.Car(2), - car.Car(3), - car.Car(4)] + self.cars = [car.Car(1), car.Car(2), car.Car(3), car.Car(4)] self.world = world.generate_world(self) # Component interface def update(self, info): self.track.update(info) - self.players = {p["name"]: p for p in info['players']} + self.players = {p["name"]: p for p in info["players"]} for player in self.players.values(): - self.cars[player['car']].update(player) - if info['started']: + self.cars[player["car"]].update(player) + if info["started"]: self.drive() # Driving @@ -51,31 +47,32 @@ def drive(self): reactor.stop() raise response_time = time.time() - start - msg = message.Message('drive', {"action": action, - "response_time": response_time}) + msg = message.Message( + "drive", {"action": action, "response_time": response_time} + ) self.client.send_message(msg) # Accessing @property def car(self): - return self.cars[self.players[self.name]['car']] + return self.cars[self.players[self.name]["car"]] # Handling client events def client_connected(self): - log.info('client connected: joining as %s', self.name) - msg = message.Message('join', {"name": self.name}) + log.info("client connected: joining as %s", self.name) + msg = message.Message("join", {"name": self.name}) self.client.send_message(msg) def client_disconnected(self, reason): - log.info('client disconnected: %s', reason.getErrorMessage()) + log.info("client disconnected: %s", reason.getErrorMessage()) def client_failed(self, reason): - log.info('client failed: %s', reason.getErrorMessage()) + log.info("client failed: %s", reason.getErrorMessage()) def client_error(self, error): - log.info('client error: %s', error.get('message')) + log.info("client error: %s", error.get("message")) reactor.stop() def client_update(self, info): diff --git a/rose/client/main.py b/rose/client/main.py index 556d5f44..e44c9e36 100644 --- a/rose/client/main.py +++ b/rose/client/main.py @@ -9,11 +9,10 @@ from rose.common import config, message from . import game -log = logging.getLogger('main') +log = logging.getLogger("main") class Client(basic.LineReceiver): - def connectionMade(self): self.factory.connected(self) @@ -25,16 +24,15 @@ def connectionFailed(self, reason): def lineReceived(self, line): msg = message.parse(line) - if msg.action == 'update': + if msg.action == "update": self.factory.update(msg.payload) - elif msg.action == 'error': + elif msg.action == "error": self.factory.error(msg.payload) else: - log.info('Unexpected message: %s %s', msg.action, msg.payload) + log.info("Unexpected message: %s %s", msg.action, msg.payload) class ClientFactory(protocol.ReconnectingClientFactory): - protocol = Client initialDelay = 2 maxDelay = 2 @@ -67,7 +65,7 @@ def update(self, info): # Client interface def send_message(self, msg): - self.client.sendLine(str(msg).encode('utf-8')) + self.client.sendLine(str(msg).encode("utf-8")) def load_driver_module(file_path): @@ -92,13 +90,16 @@ def load_driver_module(file_path): def main(): logging.basicConfig(level=logging.INFO, format=config.logger_format) parser = argparse.ArgumentParser(description="ROSE Client") - parser.add_argument("--server-address", "-s", dest="server_address", - default="localhost", - help="The server address to connect to." - " For example: '10.20.30.44' or 'my-server.com'." - " If not specified, localhost will be used.") - parser.add_argument("driver_file", - help="The path to the driver python module") + parser.add_argument( + "--server-address", + "-s", + dest="server_address", + default="localhost", + help="The server address to connect to." + " For example: '10.20.30.44' or 'my-server.com'." + " If not specified, localhost will be used.", + ) + parser.add_argument("driver_file", help="The path to the driver python module") args = parser.parse_args() @@ -108,8 +109,11 @@ def main(): log.error("error loading driver module %r: %s", args.driver_file, e) sys.exit(2) - reactor.connectTCP(args.server_address, config.game_port, - ClientFactory(driver_mod.driver_name, driver_mod.drive)) + reactor.connectTCP( + args.server_address, + config.game_port, + ClientFactory(driver_mod.driver_name, driver_mod.drive), + ) url = "http://%s:%d" % (args.server_address, config.web_port) log.info("Please open %s to watch the game." % url) diff --git a/rose/client/track.py b/rose/client/track.py index 38a9b570..9a7428d1 100644 --- a/rose/client/track.py +++ b/rose/client/track.py @@ -3,20 +3,18 @@ class Track(component.Component): - def __init__(self): self._track = {} # Component interface def update(self, info): - self._track = {(obs["x"], obs["y"]): obs["name"] - for obs in info["track"]} + self._track = {(obs["x"], obs["y"]): obs["name"] for obs in info["track"]} # Track interface def get(self, x, y): - """ Return the obstacle in position x, y """ + """Return the obstacle in position x, y""" self._validate_pos(x, y) return self._track.get((x, y), obstacles.NONE) @@ -24,6 +22,6 @@ def get(self, x, y): def _validate_pos(self, x, y): if x < 0 or x > config.matrix_width - 1: - raise IndexError('x out of range: 0-%d', config.matrix_width - 1) + raise IndexError("x out of range: 0-%d", config.matrix_width - 1) if y < 0 or y > config.matrix_height - 1: - raise IndexError('y out of range: 0-%d', config.matrix_height - 1) + raise IndexError("y out of range: 0-%d", config.matrix_height - 1) diff --git a/rose/client/world.py b/rose/client/world.py index ef5b4494..20a84f5b 100644 --- a/rose/client/world.py +++ b/rose/client/world.py @@ -9,24 +9,22 @@ def generate_world(game): """ class Car(object): - @property def x(self): - """ Returns car x position in game logical units """ + """Returns car x position in game logical units""" return game.car.x @property def y(self): - """ Returns car y position in game logical units """ + """Returns car y position in game logical units""" return game.car.y car = Car() class World(object): - @property def car(self): - """ Return my car """ + """Return my car""" return car def get(self, pos): diff --git a/rose/common/actions.py b/rose/common/actions.py index 5d66539d..85bbffb1 100644 --- a/rose/common/actions.py +++ b/rose/common/actions.py @@ -1,10 +1,10 @@ """ Driving actions """ -NONE = "none" # NOQA -RIGHT = "right" # NOQA -LEFT = "left" # NOQA -PICKUP = "pickup" # NOQA -JUMP = "jump" # NOQA -BRAKE = "brake" # NOQA +NONE = "none" # NOQA +RIGHT = "right" # NOQA +LEFT = "left" # NOQA +PICKUP = "pickup" # NOQA +JUMP = "jump" # NOQA +BRAKE = "brake" # NOQA ALL = (NONE, RIGHT, LEFT, PICKUP, JUMP, BRAKE) diff --git a/rose/common/config.py b/rose/common/config.py index d08b2aa7..613a2459 100644 --- a/rose/common/config.py +++ b/rose/common/config.py @@ -45,5 +45,6 @@ # Logging -logger_format = ("%(asctime)s %(levelname)-7s [%(name)s] %(message)s " - "(%(module)s:%(lineno)d)") +logger_format = ( + "%(asctime)s %(levelname)-7s [%(name)s] %(message)s " "(%(module)s:%(lineno)d)" +) diff --git a/rose/common/error.py b/rose/common/error.py index f18af8ef..9402e9ea 100644 --- a/rose/common/error.py +++ b/rose/common/error.py @@ -1,5 +1,6 @@ class Error(Exception): - """ Base class for server errors """ + """Base class for server errors""" + def __str__(self): return self.message % self.args @@ -18,18 +19,21 @@ class TooManyPlayers(Error): class NoSuchPlayer(Error): def __init__(self, name): self.args = (name,) + message = "No such player: %s" class ActionForbidden(Error): def __init__(self, action): self.args = (action,) + message = "You are not allowed to %s" class InvalidMessage(Error): def __init__(self, reason): self.args = (reason,) + message = "Invalid message: %s" diff --git a/rose/common/message.py b/rose/common/message.py index 0b021d42..6a4634cc 100644 --- a/rose/common/message.py +++ b/rose/common/message.py @@ -7,17 +7,16 @@ def parse(line): d = json.loads(line) except ValueError as e: raise error.InvalidMessage(str(e)) - if 'action' not in d: + if "action" not in d: raise error.InvalidMessage("action required") - return Message(d['action'], d.get('payload')) + return Message(d["action"], d.get("payload")) class Message(object): - def __init__(self, action, payload=None): self.action = action self.payload = payload def __str__(self): - d = {'action': self.action, 'payload': self.payload} + d = {"action": self.action, "payload": self.payload} return json.dumps(d) diff --git a/rose/common/obstacles.py b/rose/common/obstacles.py index a22e93a8..3b84f9ca 100644 --- a/rose/common/obstacles.py +++ b/rose/common/obstacles.py @@ -2,12 +2,12 @@ import random -NONE = "" # NOQA -CRACK = "crack" # NOQA -TRASH = "trash" # NOQA +NONE = "" # NOQA +CRACK = "crack" # NOQA +TRASH = "trash" # NOQA PENGUIN = "penguin" # NOQA -BIKE = "bike" # NOQA -WATER = "water" # NOQA +BIKE = "bike" # NOQA +WATER = "water" # NOQA BARRIER = "barrier" # NOQA ALL = (NONE, CRACK, TRASH, PENGUIN, BIKE, WATER, BARRIER) diff --git a/rose/server/game.py b/rose/server/game.py index 4038494f..9c72daa1 100644 --- a/rose/server/game.py +++ b/rose/server/game.py @@ -9,7 +9,7 @@ from . import player from . import score -log = logging.getLogger('game') +log = logging.getLogger("game") class Game(object): @@ -35,7 +35,7 @@ def rate(self): @rate.setter def rate(self, value): if value != self._rate: - log.info('change game rate to %d frames per second', value) + log.info("change game rate to %d frames per second", value) self._rate = value if self.started: self.looper.stop() @@ -72,7 +72,7 @@ def add_player(self, name): self.free_cars.remove(car) lane = random.choice(tuple(self.free_lanes)) self.free_lanes.remove(lane) - log.info('add player: %r, lane: %r, car: %r', name, lane, car) + log.info("add player: %r, lane: %r, car: %r", name, lane, car) self.players[name] = player.Player(name, car, lane) reactor.callLater(0, self.update_clients) @@ -82,31 +82,30 @@ def remove_player(self, name): player = self.players.pop(name) self.free_cars.add(player.car) self.free_lanes.add(player.lane) - log.info('remove player: %r, lane: %r, car: %r', - name, player.lane, player.car) + log.info("remove player: %r, lane: %r, car: %r", name, player.lane, player.car) if not self.players and self.started: - log.info('Stopping game. No players connected.') + log.info("Stopping game. No players connected.") self.stop() else: reactor.callLater(0, self.update_clients) def drive_player(self, name, info): - log.info('drive_player: %r %r', name, info) + log.info("drive_player: %r %r", name, info) if name not in self.players: raise error.NoSuchPlayer(name) - if 'action' not in info: + if "action" not in info: raise error.InvalidMessage("action required") - action = info['action'] + action = info["action"] if action not in actions.ALL: raise error.InvalidMessage("invalid drive action %s" % action) self.players[name].action = action - self.players[name].response_time = info.get('response_time', 1.0) + self.players[name].response_time = info.get("response_time", 1.0) def print_stats(self): - lines = ['Stats:'] + lines = ["Stats:"] top_scorers = sorted(self.players.values(), reverse=True) for i, p in enumerate(top_scorers): - line = '%d %10s row:%d score:%d' % (i + 1, p.name, p.y, p.score) + line = "%d %10s row:%d score:%d" % (i + 1, p.name, p.y, p.score) lines.append(line) log.info("%s", os.linesep.join(lines)) @@ -120,12 +119,14 @@ def loop(self): self.stop() def update_clients(self): - msg = message.Message('update', self.state()) + msg = message.Message("update", self.state()) self.hub.broadcast(msg) def state(self): - return {'started': self.started, - 'track': self.track.state(), - 'players': [p.state() for p in self.players.values()], - 'timeleft': self.timeleft, - 'rate': self.rate} + return { + "started": self.started, + "track": self.track.state(), + "players": [p.state() for p in self.players.values()], + "timeleft": self.timeleft, + "rate": self.rate, + } diff --git a/rose/server/main.py b/rose/server/main.py index 9a2e0af9..1f8774ba 100644 --- a/rose/server/main.py +++ b/rose/server/main.py @@ -10,16 +10,21 @@ from rose.common import config from . import game, net -log = logging.getLogger('main') +log = logging.getLogger("main") def main(): logging.basicConfig(level=logging.INFO, format=config.logger_format) parser = argparse.ArgumentParser(description="ROSE Server") - parser.add_argument("--track_definition", "-t", dest="track_definition", - default="random", choices=["random", "same"], - help="Definition of driver tracks: random or same." - "If not specified, random will be used.") + parser.add_argument( + "--track_definition", + "-t", + dest="track_definition", + default="random", + choices=["random", "same"], + help="Definition of driver tracks: random or same." + "If not specified, random will be used.", + ) args = parser.parse_args() """ @@ -32,17 +37,17 @@ def main(): else: config.is_track_random = True - log.info('starting server') + log.info("starting server") g = game.Game() h = net.Hub(g) reactor.listenTCP(config.game_port, net.PlayerFactory(h)) root = static.File(config.web_root) - wsuri = u"ws://%s:%s" % (socket.gethostname(), config.web_port) + wsuri = "ws://%s:%s" % (socket.gethostname(), config.web_port) watcher = net.WatcherFactory(wsuri, h) root.putChild(b"ws", WebSocketResource(watcher)) - root.putChild(b'res', static.File(config.res_root)) - root.putChild(b'admin', net.WebAdmin(g)) - root.putChild(b'rpc2', net.CliAdmin(g)) + root.putChild(b"res", static.File(config.res_root)) + root.putChild(b"admin", net.WebAdmin(g)) + root.putChild(b"rpc2", net.CliAdmin(g)) site = server.Site(root) reactor.listenTCP(config.web_port, site) reactor.run() diff --git a/rose/server/net.py b/rose/server/net.py index bb7b170d..c8e44b95 100644 --- a/rose/server/net.py +++ b/rose/server/net.py @@ -9,11 +9,10 @@ from rose.common import error, message -log = logging.getLogger('net') +log = logging.getLogger("net") class Hub(object): - def __init__(self, game): game.hub = self self.game = game @@ -54,7 +53,6 @@ def broadcast(self, msg): class PlayerProtocol(basic.LineReceiver): - def __init__(self, hub): self.hub = hub self.name = None @@ -70,36 +68,35 @@ def lineReceived(self, line): self.dispatch(msg) except error.Error as e: log.warning("Error handling message: %s", e) - msg = message.Message('error', {'message': str(e)}) - self.sendLine(str(msg).encode('utf-8')) + msg = message.Message("error", {"message": str(e)}) + self.sendLine(str(msg).encode("utf-8")) self.transport.loseConnection() # Hub client interface def send_message(self, data): - self.sendLine(data.encode('utf-8')) + self.sendLine(data.encode("utf-8")) # Disaptching messages def dispatch(self, msg): if self.name is None: # New player - if msg.action != 'join': + if msg.action != "join": raise error.ActionForbidden(msg.action) - if 'name' not in msg.payload: + if "name" not in msg.payload: raise error.InvalidMessage("name required") - self.name = msg.payload['name'] + self.name = msg.payload["name"] self.hub.add_player(self) else: # Registered player - if msg.action == 'drive': + if msg.action == "drive": self.hub.drive_player(self, msg.payload) else: raise error.ActionForbidden(msg.action) class PlayerFactory(protocol.ServerFactory): - def __init__(self, hub): self.hub = hub @@ -110,7 +107,6 @@ def buildProtocol(self, addr): class WatcherProtocol(WebSocketServerProtocol): - def __init__(self, hub): self.hub = hub WebSocketServerProtocol.__init__(self) @@ -124,18 +120,18 @@ def onOpen(self): self.hub.add_watcher(self) def onClose(self, wasClean, code, reason): - log.info("watcher closed (wasClean=%s, code=%s, reason=%s)", - wasClean, code, reason) + log.info( + "watcher closed (wasClean=%s, code=%s, reason=%s)", wasClean, code, reason + ) self.hub.remove_watcher(self) # Hub client interface def send_message(self, data): - self.sendMessage(data.encode('utf-8'), False) + self.sendMessage(data.encode("utf-8"), False) class WatcherFactory(WebSocketServerFactory): - def __init__(self, url, hub): self.hub = hub WebSocketServerFactory.__init__(self, url) @@ -147,7 +143,6 @@ def buildProtocol(self, addr): class CliAdmin(xmlrpc.XMLRPC): - def __init__(self, game): self.game = game xmlrpc.XMLRPC.__init__(self, allowNone=True) @@ -169,7 +164,6 @@ def xmlrpc_set_rate(self, rate): class WebAdmin(resource.Resource): - def __init__(self, game): self.game = game resource.Resource.__init__(self) diff --git a/rose/server/player.py b/rose/server/player.py index f59b2108..d3afd32e 100644 --- a/rose/server/player.py +++ b/rose/server/player.py @@ -2,7 +2,6 @@ class Player(object): - def __init__(self, name, car, lane): """ Creates a new Driver object. @@ -36,11 +35,11 @@ def __init__(self, name, car, lane): # Game state interface def update(self): - """ Go to the next game state """ + """Go to the next game state""" def reset(self): self.x = self.lane * config.cells_per_player + 1 # | |0| | |1 | | - self.y = config.matrix_height // 3 * 2 # 1/3 of track + self.y = config.matrix_height // 3 * 2 # 1/3 of track self.action = actions.NONE self.response_time = None self.score = 0 @@ -58,10 +57,12 @@ def __lt__(self, other): return self.score < other.score def state(self): - """ Return read only serialize-able state for sending to client """ - return {'name': self.name, - 'car': self.car, - 'x': self.x, - 'y': self.y, - 'lane': self.lane, - 'score': self.score} + """Return read only serialize-able state for sending to client""" + return { + "name": self.name, + "car": self.car, + "x": self.x, + "y": self.y, + "lane": self.lane, + "score": self.score, + } diff --git a/rose/server/player_test.py b/rose/server/player_test.py index 6f23ac10..528af1e9 100644 --- a/rose/server/player_test.py +++ b/rose/server/player_test.py @@ -69,12 +69,12 @@ def test_player_state(): player1 = player.Player("John", 1, 1) expected_state = { - 'name': player1.name, - 'car': player1.car, - 'x': player1.x, - 'y': player1.y, - 'lane': player1.lane, - 'score': player1.score + "name": player1.name, + "car": player1.car, + "x": player1.x, + "y": player1.y, + "lane": player1.lane, + "score": player1.score, } assert player1.state() == expected_state diff --git a/rose/server/score.py b/rose/server/score.py index 590c6cde..136e4654 100644 --- a/rose/server/score.py +++ b/rose/server/score.py @@ -3,7 +3,7 @@ from rose.common import actions, config, obstacles -log = logging.getLogger('score') +log = logging.getLogger("score") def process(players, track): @@ -22,23 +22,26 @@ def process(players, track): if player.x > 0: player.x -= 1 log.debug( - 'player %s moved left to %d,%d', - player.name, player.x, player.y, + "player %s moved left to %d,%d", + player.name, + player.x, + player.y, ) elif player.action == actions.RIGHT: if player.x < config.matrix_width - 1: player.x += 1 log.debug( - 'player %s moved right to %d,%d', - player.name, player.x, player.y, + "player %s moved right to %d,%d", + player.name, + player.x, + player.y, ) # Proccess the players by order, first the ones in lane and then # the ones out of lane, this ensure the car in lane will have # priority when picking pinguins and in case of collisions. - sorted_players = sorted(players.values(), - key=lambda p: 0 if p.in_lane() else 1) + sorted_players = sorted(players.values(), key=lambda p: 0 if p.in_lane() else 1) positions = set() # Now handle obstacles, preferring players in their own lane. @@ -50,21 +53,23 @@ def process(players, track): # Move forward, leaving the obstacle on the track. player.score += config.score_move_forward log.debug( - 'player %s hit no obstacle: got %d points', - player.name, config.score_move_forward, + "player %s hit no obstacle: got %d points", + player.name, + config.score_move_forward, ) - elif obstacle in (obstacles.TRASH, - obstacles.BIKE, - obstacles.BARRIER): + elif obstacle in (obstacles.TRASH, obstacles.BIKE, obstacles.BARRIER): # Move back consuming the obstacle. track.clear(player.x, player.y) player.y += 1 player.score += config.score_move_backward log.debug( - 'player %s hit %s: lost %d points, moved back to %d,%d', - player.name, obstacle, -config.score_move_backward, - player.x, player.y, + "player %s hit %s: lost %d points, moved back to %d,%d", + player.name, + obstacle, + -config.score_move_backward, + player.x, + player.y, ) elif obstacle == obstacles.CRACK: @@ -73,8 +78,10 @@ def process(players, track): points = config.score_move_forward + config.score_jump player.score += points log.debug( - 'player %s avoided %s: got %d points', - player.name, obstacle, points, + "player %s avoided %s: got %d points", + player.name, + obstacle, + points, ) else: # Move back consuming the obstacle. @@ -82,9 +89,12 @@ def process(players, track): player.y += 1 player.score += config.score_move_backward log.debug( - 'player %s hit %s: lost %d points, moved back to %d,%d', - player.name, obstacle, -config.score_move_backward, - player.x, player.y, + "player %s hit %s: lost %d points, moved back to %d,%d", + player.name, + obstacle, + -config.score_move_backward, + player.x, + player.y, ) elif obstacle == obstacles.WATER: @@ -93,8 +103,10 @@ def process(players, track): points = config.score_move_forward + config.score_brake player.score += points log.debug( - 'player %s avoided %s: got %d points', - player.name, obstacle, points, + "player %s avoided %s: got %d points", + player.name, + obstacle, + points, ) else: # Move back consuming the obstacle. @@ -102,9 +114,12 @@ def process(players, track): player.y += 1 player.score += config.score_move_backward log.debug( - 'player %s hit %s: lost %d points, moved back to %d,%d', - player.name, obstacle, -config.score_move_backward, - player.x, player.y, + "player %s hit %s: lost %d points, moved back to %d,%d", + player.name, + obstacle, + -config.score_move_backward, + player.x, + player.y, ) elif obstacle == obstacles.PENGUIN: @@ -114,13 +129,15 @@ def process(players, track): points = config.score_move_forward + config.score_pickup player.score += points log.debug( - 'player %s picked up %s: got %d points', - player.name, obstacle, points, + "player %s picked up %s: got %d points", + player.name, + obstacle, + points, ) else: # Move forward leaving the obstacle on the track player.score += config.score_move_forward - log.debug('player %s missed %s', player.name, obstacle) + log.debug("player %s missed %s", player.name, obstacle) # Here we can end the game when player gets out of # the track bounds. For now, just keep the player at the same @@ -139,10 +156,13 @@ def process(players, track): elif player.x < config.matrix_width - 1: player.x += 1 log.debug( - 'player %s collision at %d,%d: lost %d points, ' - 'moved to %d,%d', - player.name, loc[0], loc[1], -config.score_move_backward, - player.x, player.y, + "player %s collision at %d,%d: lost %d points, " "moved to %d,%d", + player.name, + loc[0], + loc[1], + -config.score_move_backward, + player.x, + player.y, ) # Finally forget action @@ -150,7 +170,13 @@ def process(players, track): positions.add((player.x, player.y)) - log.info('process_actions: name=%s lane=%d pos=%d,%d score=%d ' - 'response_time=%0.6f', - player.name, player.lane, player.x, player.y, player.score, - player.response_time) + log.info( + "process_actions: name=%s lane=%d pos=%d,%d score=%d " + "response_time=%0.6f", + player.name, + player.lane, + player.x, + player.y, + player.score, + player.response_time, + ) diff --git a/rose/server/score_test.py b/rose/server/score_test.py index 13094f3e..872e5646 100644 --- a/rose/server/score_test.py +++ b/rose/server/score_test.py @@ -5,12 +5,10 @@ import pytest -FORWARD_ACTIONS = [a for a in actions.ALL - if a not in (actions.RIGHT, actions.LEFT)] +FORWARD_ACTIONS = [a for a in actions.ALL if a not in (actions.RIGHT, actions.LEFT)] class SinglePlayerTest(object): - # Must be defined in subclass obstacle = None @@ -58,7 +56,6 @@ def assert_remove_obstacle(self): class TestNoObstacle(SinglePlayerTest): - obstacle = obstacles.NONE def test_right(self): @@ -113,7 +110,8 @@ def test_left(self): self.assert_keep_obstacle() @pytest.mark.parametrize( - "action", [a for a in FORWARD_ACTIONS if a != actions.PICKUP]) + "action", [a for a in FORWARD_ACTIONS if a != actions.PICKUP] + ) def test_other(self, action): self.player.action = action self.process() @@ -213,6 +211,7 @@ class TestLimits(SinglePlayerTest): """ Handling movement out of the track """ + obstacle = obstacles.NONE def test_left(self): @@ -272,8 +271,7 @@ def setup_method(self, m): self.player2 = player.Player("B", car=0, lane=1) def process(self): - players = {self.player1.name: self.player1, - self.player2.name: self.player2} + players = {self.player1.name: self.player1, self.player2.name: self.player2} score.process(players, self.track) def test_player_in_lane_wins(self): diff --git a/rose/server/track.py b/rose/server/track.py index b691c817..308283db 100644 --- a/rose/server/track.py +++ b/rose/server/track.py @@ -3,7 +3,6 @@ class Track(object): - def __init__(self): self._matrix = None self.reset() @@ -11,12 +10,12 @@ def __init__(self): # Game state interface def update(self): - """ Go to the next game state """ + """Go to the next game state""" self._matrix.pop() self._matrix.insert(0, self._generate_row()) def state(self): - """ Return read only serialize-able state for sending to client """ + """Return read only serialize-able state for sending to client""" items = [] for y, row in enumerate(self._matrix): for x, obs in enumerate(row): @@ -27,20 +26,21 @@ def state(self): # Track interface def get(self, x, y): - """ Return the obstacle in position x, y """ + """Return the obstacle in position x, y""" return self._matrix[y][x] def set(self, x, y, obstacle): - """ Set obstacle in position x, y """ + """Set obstacle in position x, y""" self._matrix[y][x] = obstacle def clear(self, x, y): - """ Clear obstacle in position x, y """ + """Clear obstacle in position x, y""" self._matrix[y][x] = obstacles.NONE def reset(self): - self._matrix = [[obstacles.NONE] * config.matrix_width - for x in range(config.matrix_height)] + self._matrix = [ + [obstacles.NONE] * config.matrix_width for x in range(config.matrix_height) + ] # Private diff --git a/setup.py b/setup.py index dd78c7c2..17d6b979 100644 --- a/setup.py +++ b/setup.py @@ -9,22 +9,23 @@ def run(self): sdist.sdist.run(self) def generate_files(self): - with open('requirements.txt', 'w') as f: + with open("requirements.txt", "w") as f: f.write("# Generated automatically from Pipfile - do not edit!\n") f.flush() - subprocess.check_call(['pipenv', 'lock', '--requirements'], - stdout=f) + subprocess.check_call(["pipenv", "lock", "--requirements"], stdout=f) -setup(name='rose-project', - version='0.1', - license="GNU GPLv2+", - description="game", - packages=['rose', 'rose.server', 'rose.client', 'rose.common'], - package_data={'rose': ['res/*/*.png']}, - author='Yaniv Bronhaim', - author_email='ybronhei@redhat.com', - url="https://github.com/emesika/RaananaTiraProject", - scripts=["rose-client", "rose-server", "rose-admin"], - data_files=[('requirements.txt', ['requirements.txt'])], - cmdclass={'sdist': rose_sdist}) +setup( + name="rose-project", + version="0.1", + license="GNU GPLv2+", + description="game", + packages=["rose", "rose.server", "rose.client", "rose.common"], + package_data={"rose": ["res/*/*.png"]}, + author="Yaniv Bronhaim", + author_email="ybronhei@redhat.com", + url="https://github.com/emesika/RaananaTiraProject", + scripts=["rose-client", "rose-server", "rose-admin"], + data_files=[("requirements.txt", ["requirements.txt"])], + cmdclass={"sdist": rose_sdist}, +)