├── tests ├── __init__.py └── test_main.py ├── .gitignore ├── requirements.txt ├── app └── main.py ├── .flake8 ├── checklist.md ├── .github └── workflows │ └── test.yml └── README.md /tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vscode/ 3 | *.iml 4 | .env 5 | .DS_Store 6 | venv/ 7 | .pytest_cache/ 8 | **__pycache__/ 9 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | flake8==5.0.4 2 | flake8-annotations==2.9.1 3 | flake8-quotes==3.3.1 4 | flake8-variables-names==0.0.5 5 | pep8-naming==0.13.2 6 | pytest==7.1.3 7 | -------------------------------------------------------------------------------- /app/main.py: -------------------------------------------------------------------------------- 1 | class Person: 2 | # write your code here 3 | pass 4 | 5 | 6 | def create_person_list(people: list) -> list: 7 | # write your code here 8 | pass 9 | -------------------------------------------------------------------------------- /.flake8: -------------------------------------------------------------------------------- 1 | [flake8] 2 | inline-quotes = " 3 | ignore = E203, E266, W503, ANN002, ANN003, ANN101, ANN102, ANN401, N807, N818 4 | max-line-length = 79 5 | max-complexity = 18 6 | select = B,C,E,F,W,T4,B9,ANN,Q0,N8,VNE 7 | exclude = venv, tests 8 | -------------------------------------------------------------------------------- /checklist.md: -------------------------------------------------------------------------------- 1 | # Check Your Code Against the Following Points 2 | 3 | ## Code Style 4 | 5 | 1. Use descriptive and correct variable names. 6 | 7 | Good example: 8 | 9 | ```python 10 | def get_full_name(first_name: str, last_name: str) -> str: 11 | return f"{first_name} {last_name}" 12 | ``` 13 | 14 | Bad example: 15 | ```python 16 | def get_full_name(x: str, y: str) -> str: 17 | return f"{x} {y}" 18 | ``` 19 | 20 | 2. Avoid nested `if` by using `and`, `or` logical operators. 21 | 22 | ## Clean Code 23 | 24 | Add comments, prints, and functions to check your solution when you write your code. 25 | Don't forget to delete them when you are ready to commit and push your code. 26 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - "master" 7 | 8 | jobs: 9 | test: 10 | runs-on: ubuntu-latest 11 | 12 | steps: 13 | - name: Checkout repo 14 | uses: actions/checkout@v2 15 | 16 | - name: Set Up Python 3.10 17 | uses: actions/setup-python@v2 18 | with: 19 | python-version: "3.10" 20 | 21 | - name: Install pytest and flake8 22 | run: | 23 | python -m pip install --upgrade pip 24 | pip install -r requirements.txt 25 | 26 | - name: Run flake8 27 | run: flake8 app/ 28 | - name: Run tests 29 | timeout-minutes: 5 30 | run: pytest tests/ 31 | 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Class Person 2 | 3 | - Read [the guideline](https://github.com/mate-academy/py-task-guideline/blob/main/README.md) before start 4 | 5 | 6 | You have a list of dicts `people`, every dict means 7 | a **person**, it has keys: `name`, `age`, 8 | `wife`/`husband` - depends on person is male or 9 | female. All `names` are different. Key 10 | `wife`/`husband` can be either `None` or 11 | name of another person. 12 | 13 | Create class `Person`. It's `__init__` method takes 14 | and store `name`, `age` of a person. 15 | This class also should have a class attribute 16 | `people`, it is a dict that stores `Person` 17 | instances by their `name`. The `__init__` method should 18 | add elements to this attribute. 19 | 20 | Write function `create_person_list`, this function 21 | takes list `people` and return list with 22 | `Person` instances instead of dicts. 23 | 24 | **Note:** 25 | 26 | If **person's** key `wife`/`husband` is not 27 | `None` - `create_person_list` should add 28 | attribute `wife`/`husband` respectively 29 | to its instance. This attribute should 30 | be a link to a `Person` instance with `name` the 31 | same as `wife`/`husband` key in person's dict. 32 | 33 | 34 | Example: 35 | ```python 36 | people = [ 37 | {"name": "Ross", "age": 30, "wife": "Rachel"}, 38 | {"name": "Joey", "age": 29, "wife": None}, 39 | {"name": "Rachel", "age": 28, "husband": "Ross"} 40 | ] 41 | 42 | person_list = create_person_list(people) 43 | isinstance(person_list[0], Person) # True 44 | person_list[0].name == "Ross" 45 | person_list[0].wife is person_list[2] # True 46 | person_list[0].wife.name == "Rachel" 47 | 48 | person_list[1].name == "Joey" 49 | person_list[1].wife 50 | # AttributeError 51 | 52 | isinstance(person_list[2], Person) # True 53 | person_list[2].name == "Rachel" 54 | person_list[2].husband is person_list[0] # True 55 | # The same as person_list[0] 56 | person_list[2].husband.name == "Ross" 57 | person_list[2].husband.wife is person_list[2] # True 58 | 59 | Person.people == { 60 | "Ross": <__main__.Person object at 0x10c20ca60>, 61 | "Joey": <__main__.Person object at 0x10c180a00>, 62 | "Rachel": <__main__.Person object at 0x10c1804f0> 63 | } 64 | ``` 65 | `Hint` - use `pytest` for testing 66 | 67 | ### Note: Check your code using this [checklist](checklist.md) before pushing your solution. 68 | -------------------------------------------------------------------------------- /tests/test_main.py: -------------------------------------------------------------------------------- 1 | import ast 2 | import os 3 | 4 | import pytest 5 | 6 | from app.main import create_person_list 7 | from app.main import Person 8 | 9 | 10 | def path_to_main(): 11 | base_path = os.path.join("app", "main.py") 12 | return ( 13 | base_path if os.path.exists(base_path) else os.path.join(os.pardir, base_path) 14 | ) 15 | 16 | 17 | @pytest.fixture() 18 | def people_data(): 19 | return [ 20 | {"name": "Ross", "age": 30, "wife": "Rachel"}, 21 | {"name": "Joey", "age": 29, "wife": None}, 22 | {"name": "Phoebe", "age": 31, "husband": None}, 23 | {"name": "Chandler", "age": 30, "wife": "Monica"}, 24 | {"name": "Monica", "age": 32, "husband": "Chandler"}, 25 | {"name": "Rachel", "age": 28, "husband": "Ross"}, 26 | ] 27 | 28 | 29 | @pytest.fixture() 30 | def created_person_list(people_data): 31 | return create_person_list(people_data) 32 | 33 | 34 | def test_person_class_attribute_people_exists(): 35 | assert hasattr( 36 | Person, "people" 37 | ), "Class Person should have class attribute 'people'" 38 | assert ( 39 | len(Person.people) == 0 40 | ), "Initial length of 'Person.people' should equal to 0" 41 | 42 | 43 | @pytest.mark.parametrize("name,age", [("Ross", 30), ("Joey", 29)]) 44 | def test_person_class_name_age(name, age): 45 | person_inst = Person(name, age) 46 | assert person_inst.name == name, "Person instance should have attribute 'name'" 47 | assert person_inst.age == age, "Person instance should have attribute 'age'" 48 | 49 | 50 | def test_create_person_list_all_persons(people_data, created_person_list): 51 | assert all(isinstance(person, Person) for person in created_person_list), ( 52 | "All elements in result of 'create_person_list' should be instance " 53 | "of Person class" 54 | ) 55 | assert len(created_person_list) == len( 56 | people_data 57 | ), "Length of initial list should equal to length of function result" 58 | 59 | 60 | def test_create_person_list_order(people_data, created_person_list): 61 | assert [person_dict["name"] for person_dict in people_data] == [ 62 | person.name for person in created_person_list 63 | ], "Order in function result should be the same" 64 | 65 | 66 | def test_create_person_list_has_wife(people_data, created_person_list): 67 | assert hasattr(created_person_list[0], "wife"), ( 68 | f"Person with 'name' {created_person_list[0].name} should have " 69 | f"attribute 'wife' with name {people_data[0].wife.name}" 70 | ) 71 | 72 | 73 | def test_create_person_list_has_wife_and_wife_have_husband( 74 | people_data, created_person_list 75 | ): 76 | assert ( 77 | hasattr(created_person_list[0], "wife") 78 | and created_person_list[0].wife.husband == created_person_list[0] 79 | ), ( 80 | f"Person with 'name' {created_person_list[0].name} should have " 81 | f"attribute 'wife' with name {created_person_list[0].wife.name} and " 82 | f"Person.wife.husband should links to that Person" 83 | ) 84 | 85 | 86 | def test_create_person_list_has_no_wife(people_data, created_person_list): 87 | assert hasattr(created_person_list[1], "wife") is False, ( 88 | f"Person with 'name' {created_person_list[1].name} should not have " 89 | f"attribute wife" 90 | ) 91 | 92 | 93 | def test_person_class_attribute_people(people_data, created_person_list): 94 | assert all( 95 | isinstance(person, Person) for person in Person.people.values() 96 | ), "All elements of Person class attribute 'people' should be Person instances" 97 | assert len(Person.people) == len( 98 | people_data 99 | ), "Length of Person class attribute people should be equal to length of initial list" 100 | 101 | 102 | def test_create_person_list_returns_only_entering_people( 103 | people_data, created_person_list 104 | ): 105 | assert len(people_data) == len( 106 | created_person_list 107 | ), "Length of passed list should equal to length of returned list" 108 | assert [person["name"] for person in people_data] == [ 109 | person.name for person in created_person_list 110 | ], ( 111 | "People, that are passed to the function should equal to people, " 112 | "that are returned" 113 | ) 114 | 115 | 116 | def test_person_instance_attribute_wife_and_husband_doesnt_exists(): 117 | with open(path_to_main()) as file: 118 | tree = ast.parse(file.read()) 119 | 120 | assert ( 121 | len( 122 | tree.__dict__["body"][0] 123 | .__dict__["body"][1] 124 | .__dict__["args"] 125 | .__dict__["args"] 126 | ) 127 | == 3 128 | ), "'__init__' should takes only two arguments 'name' and 'age'!" 129 | 130 | 131 | def test_removed_comment(): 132 | with open(path_to_main(), "r") as file: 133 | main_content = file.read() 134 | 135 | assert ( 136 | "# write your code here" not in main_content 137 | ), "You have to remove the unnecessary comment '# write your code here'" 138 | 139 | 140 | def test_double_quotes_instead_of_single(): 141 | with open(path_to_main(), "r") as file: 142 | main_content = file.read() 143 | 144 | assert ( 145 | "'" not in main_content 146 | ), "You have to use a double quotes \"\" instead of single ''" 147 | --------------------------------------------------------------------------------