diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cbfb00d84..37aa9feb2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,8 @@ jobs: steps: - name: Checkout repo uses: actions/checkout@v2 + + - name: Set Up Python 3.10 uses: actions/setup-python@v2 with: @@ -21,6 +23,7 @@ jobs: run: | python -m pip install --upgrade pip pip install -r requirements.txt + - name: Run flake8 run: flake8 app/ - name: Run tests diff --git a/.github/workflows/workflow.yml b/.github/workflows/workflow.yml new file mode 100644 index 000000000..4d80e79b3 --- /dev/null +++ b/.github/workflows/workflow.yml @@ -0,0 +1,30 @@ +name: Test + +on: + pull_request: + branches: + - "master" + +jobs: + test: + runs-on: ubuntu-latest + + steps: + - name: Checkout repo + uses: actions/checkout@v2 + + - name: Set Up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: "3.10" + + - name: Install pytest and flake8 + run: | + python -m pip install --upgrade pip + pip install -r requirements.txt + + - name: Run flake8 + run: flake8 app/ + - name: Run tests + timeout-minutes: 5 + run: pytest tests/ diff --git a/.gitignore b/.gitignore index ac24bc428..384b5e77e 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,13 @@ .DS_Store venv/ .pytest_cache/ +<<<<<<< HEAD +**__pycache__/ +======= +>>>>>>> upstream/master +AppData/ +Cookies/ +Templates/ +AppData/ +Local Settings/ +NTUSER.DAT* diff --git a/README.md b/README.md index 5e09de186..2c9386a1b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,148 @@ +Read [the guideline](https://github.com/mate-academy/py-task-guideline/blob/main/README.md) before starting. + +You own a car washing station. Washing cost calculation +takes a lot of time, and you decide to automate this +calculation. The washing cost will depend on car comfort +class, car cleanness degree, wash station average rating +and wash station distance from the center of the city. + +Create class `Car`, its `__init__` method takes and stores +3 arguments: +1. `comfort_class` - comfort class of a car, from 1 to 7 +2. `clean_mark` - car cleanness mark, from very +dirty - 1 to absolutely clean - 10 +3. `brand` - brand of the car + +Create class `CarWashStation`, its `__init__` method takes and +stores 4 arguments: +1. `distance_from_city_center` - how far station from +the city center, from 1.0 to 10.0 +2. `clean_power` - `clean_mark` to which this car wash station +washes (yes, not all stations can clean your car completely) +3. `average_rating` - average rating of the station, +from 1.0 to 5.0, rounded to 1 decimal +4. `count_of_ratings` - number of people who rated + +`CarWashStation` should have such methods: +1. `serve_cars` - method, that takes a list of `Car`'s, washes only +cars with `clean_mark` < `clean_power` of wash station +and returns income of `CarWashStation` for serving this list of Car's, +rounded to 1 decimal: + +```python +bmw = Car(comfort_class=3, clean_mark=3, brand='BMW') +audi = Car(comfort_class=4, clean_mark=9, brand='Audi') + +print(bmw.clean_mark) # 3 + +wash_station = CarWashStation( + distance_from_city_center=5, + clean_power=6, + average_rating=3.5, + count_of_ratings=6 +) + +income = wash_station.serve_cars([bmw, audi]) + +print(income) # 6.3 +print(bmw.clean_mark) # 6 +``` + +So, only bmw was washed, because `audi.clean_mark` > `wash_station.clean_power`, +and `bmw.clean_mark` has changed, because we washed it. + +If `audi.clean_mark` was below `wash_station.clean_power` then `audi` would have been washed as well +and the income would have raised: + +```python +bmw = Car(comfort_class=3, clean_mark=3, brand='BMW') +audi = Car(comfort_class=4, clean_mark=2, brand='Audi') + +print(bmw.clean_mark) # 3 +print(audi.clean_mark) # 2 + +wash_station = CarWashStation( + distance_from_city_center=5, + clean_power=6, + average_rating=3.5, + count_of_ratings=6 +) + +income = wash_station.serve_cars([bmw, audi]) + +print(income) # 17.5 + +print(bmw.clean_mark) # 6 +print(audi.clean_mark) # 6 +``` + +2. `calculate_washing_price` - method, that calculates cost for a +single car wash, +cost is calculated as: car's comfort class * difference between +wash station's clean power and car's clean mark * car wash station +rating / car wash station +distance to the center of the city, returns number rounded +to 1 decimal; +3. `wash_single_car` - method, that washes a single car, so it should +have `clean_mark` equals wash station's `clean_power`, if +`wash_station.clean_power` is greater than `car.clean_mark`; +4. `rate_service` - method that adds a single rate to the wash station, and based on this single rate +`average_rating` and `count_of_ratings` should be changed: + +```python +wash_station = CarWashStation( + distance_from_city_center=6, + clean_power=8, + average_rating=3.9, + count_of_ratings=11 +) + +print(wash_station.average_rating) # 3.9 +print(wash_station.count_of_ratings) # 11 + +wash_station.rate_service(5) + +print(wash_station.average_rating) # 4.0 +print(wash_station.count_of_ratings) # 12 +``` + +You can add own methods if you need. + +Example: +```python +bmw = Car(3, 3, 'BMW') +audi = Car(4, 9, 'Audi') +mercedes = Car(7, 1, 'Mercedes') + +ws = CarWashStation(6, 8, 3.9, 11) + +income = ws.serve_cars([ + bmw, + audi, + mercedes +]) + +income == 41.7 + +bmw.clean_mark == 8 +audi.clean_mark == 9 +mercedes.clean_mark == 8 +# audi wasn't washed +# all other cars are washed to '8' + +ford = Car(2, 1, 'Ford') +wash_cost = ws.calculate_washing_price(ford) +# only calculating cost, not washing +wash_cost == 9.1 +ford.clean_mark == 1 + +ws.rate_service(5) + +ws.count_of_ratings == 12 +ws.average_rating == 4.0 +``` + +### Note: Check your code using this [checklist](checklist.md) before pushing your solution. # Mutable and Immutable - Read [the guideline](https://github.com/mate-academy/py-task-guideline/blob/main/README.md) before start @@ -21,3 +166,4 @@ sorted_variables = { "immutable": [a, c] } ``` +upstream/master diff --git a/app/main.py b/app/main.py index f07695b9b..e61a6f8cc 100644 --- a/app/main.py +++ b/app/main.py @@ -1,3 +1,15 @@ +class Car: + def __init__(self, brand: str, model: str, year: int) -> None: + self.brand = brand + self.model = model + self.year = year + + +class CarWashStation: + def wash_single_car(self, car: Car) -> float: + return 10.0 + + lucky_number = 777 pi = 3.14 one_is_a_prime_number = False @@ -16,4 +28,5 @@ } collection_of_coins = {1, 2, 25} -# write your code here +print(profile_info) +print(collection_of_coins) diff --git a/checklist.md b/checklist.md new file mode 100644 index 000000000..5598f87dc --- /dev/null +++ b/checklist.md @@ -0,0 +1,80 @@ +# Check Your Code Against the Following Points + +## Code Style + +1. If you have some long math, you can split it onto additional variables, +or break after binary operations (not before - it cause the W504 errors) + +Good example: + +```python +fuel_consumption = max_fuel_consumption * height_fuel_consumption_coeficient +estimated_speed = plan_max_speed - wind_awerage_speed * wind_angle_coefisient +estimated_time = distance_to_the_destinatoin / estimated_speed +how_much_fuel_needed = fuel_consumption * estimated_time * overlap_coeficient +``` + +Good example: + +```python +how_much_fuel_needed = (max_fuel_consumption + * height_fuel_consumption_coeficient + * distance_to_the_destinatoin + / (plan_max_speed + - wind_awerage_speed + * wind_angle_coefisient) + * overlap_coeficient) +``` + +Bad example: + +```python +how_much_fuel_needed = max_fuel_consumption \ + * height_fuel_consumption_coeficient \ + * distance_to_the_destinatoin / ( + plan_max_speed + - wind_awerage_speed + * wind_angle_coefisient + ) * overlap_coeficient +``` + +2. Use descriptive and correct variable names. + +Good example: + +```python +def get_full_name(first_name: str, last_name: str) -> str: + return f"{first_name} {last_name}" +``` + +Bad example: +```python +def get_full_name(x: str, y: str) -> str: + return f"{x} {y}" +``` + +## Clean Code + +1. You can avoid else when have return statement. + +Good example: + +```python +def is_adult(age: int) -> str: + if age >= 18: + return "adult" + return "not an adult" +``` + +Bad example: + +```python +def is_adult(age: int) -> str: + if age >= 18: + return "adult" + else: + return "not an adult" +``` + +2. Add comments, prints, and functions to check your solution when you write your code. +Don't forget to delete them when you are ready to commit and push your code. diff --git a/py-count-occurrences b/py-count-occurrences new file mode 160000 index 000000000..5c39139b2 --- /dev/null +++ b/py-count-occurrences @@ -0,0 +1 @@ +Subproject commit 5c39139b2cf7e3180fdd9761cb62385be7538fa4 diff --git a/py-linter-formatter b/py-linter-formatter new file mode 160000 index 000000000..232325907 --- /dev/null +++ b/py-linter-formatter @@ -0,0 +1 @@ +Subproject commit 2323259074cec14a564b352ed349a2d68a80bf76 diff --git a/py-mutable-immutable b/py-mutable-immutable new file mode 160000 index 000000000..b9a8d03c8 --- /dev/null +++ b/py-mutable-immutable @@ -0,0 +1 @@ +Subproject commit b9a8d03c839bcbed3ce71d2f963b890e1a966d67 diff --git a/tests/test_main.py b/tests/test_main.py index a5cb4cbc0..1eda8528d 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -1,3 +1,129 @@ +from unittest.mock import patch +import pytest +import os + +from app.main import Car, CarWashStation + + +def test_car(): + bmw = Car(2, 3, "BMW") + assert bmw.comfort_class == 2, "Class Car should store 'comfort_class'" + assert bmw.clean_mark == 3, "Class Car should store 'clean_mark'" + assert bmw.brand == "BMW", "Class Car should store 'brand'" + + +@pytest.mark.parametrize( + "cars,wash_station,total_cost", + [ + ([], CarWashStation(3, 9, 4.4, 144), 0), + ([Car(2, 1, "Ford")], CarWashStation(3, 9, 4.2, 11), 22.4), + ([Car(2, 9, "Ford")], CarWashStation(3, 8, 4.2, 11), 0), + ( + [Car(3, 3, "BMW"), Car(4, 5, "Audi"), Car(7, 1, "Mercedes")], + CarWashStation(6, 7, 3.9, 11), + 40.3, + ), + ( + [Car(3, 3, "BMW"), Car(4, 5, "Audi"), Car(7, 9, "Mercedes")], + CarWashStation(6, 7, 3.9, 11), + 13.0, + ), + ( + [Car(3, 8, "BMW"), Car(4, 8, "Audi"), Car(7, 9, "Mercedes")], + CarWashStation(6, 7, 3.9, 11), + 0, + ), + ], +) +def test_car_wash_station(cars, wash_station, total_cost): + income = wash_station.serve_cars(cars) + assert income == total_cost, f"Income should equal to {total_cost}" + +def test_wash_single_car_is_called(): + with patch.object(CarWashStation, 'wash_single_car') as mock_method: + CarWashStation(3, 9, 4, 11).serve_cars([Car(2, 1, "Ford")]) + assert mock_method.called, "Expected 'wash_single_car' to have " \ + "been called inside 'serve_cars' method" + + +@pytest.mark.parametrize( + "cars,wash_station,cars_clean_marks", + [ + ([Car(2, 1, "Ford")], CarWashStation(3, 9, 4.2, 11), [9]), + ([Car(2, 9, "Ford")], CarWashStation(3, 8, 4.2, 11), [9]), + ( + [Car(3, 3, "BMW"), Car(4, 5, "Audi"), Car(7, 1, "Mercedes")], + CarWashStation(6, 7, 3.9, 11), + [7, 7, 7], + ), + ( + [Car(3, 3, "BMW"), Car(4, 5, "Audi"), Car(7, 9, "Mercedes")], + CarWashStation(2, 8, 4.8, 13), + [8, 8, 9], + ), + ], +) +def test_car_is_washed(cars, wash_station, cars_clean_marks): + wash_station.serve_cars(cars) + assert [car.clean_mark for car in cars] == cars_clean_marks, ( + f"Car should keep his 'clear_mark' if it >= 'clear_power' of wash station, " + f"otherwise it should equal to 'clear_power'" + ) + + +@pytest.mark.parametrize( + "car,wash_station,mark", + [ + (Car(2, 1, "Ford"), CarWashStation(3, 10, 4.2, 11), 1), + (Car(4, 5, "Audi"), CarWashStation(6, 8, 3.9, 11), 5), + (Car(3, 3, "BMW"), CarWashStation(2, 9, 4.8, 13), 3), + ], +) +def test_car_cost_check_not_washed(car, wash_station, mark): + wash_station.calculate_washing_price(car) + assert car.clean_mark == mark, ( + f"Method 'calculate_washing_price' should not change" f"'car.clean_mark'" + ) + + +@pytest.mark.parametrize( + "init_avg_rating,init_num_ratings,mark,result_avg_rating,result_num_ratings", + [ + (2.2, 2, 5, 3.1, 3), + (2.4, 11, 5, 2.6, 12), + (3.8, 7, 2, 3.6, 8), + (4.4, 42, 4, 4.4, 43), + ], +) +def test_rate_service( + init_avg_rating, init_num_ratings, mark, result_avg_rating, result_num_ratings +): + ws = CarWashStation(2, 9, init_avg_rating, init_num_ratings) + ws.rate_service(mark) + assert ws.average_rating == result_avg_rating, ( + f"'average_rating' should equal to {result_avg_rating}, " + f"when initial 'average_rating' was {init_avg_rating}, " + f"and initial 'count_of_ratings' was {init_num_ratings}" + ) + assert ws.count_of_ratings == result_num_ratings, ( + f"'count_of_ratings' should equal to {result_num_ratings}, " + f"when initial 'count_of_ratings' was {init_num_ratings}" + ) + + +def test_unnecessary_comment(): + if os.path.exists(os.path.join(os.pardir, "app", "main.py")): + main_path = os.path.join(os.pardir, "app", "main.py") + else: + main_path = os.path.join("app", "main.py") + + with open(main_path, "r") as main: + main_content = main.read() + + assert ( + "# write your code here" not in main_content + ), "Remove unnecessary comment" + import pytest import inspect