feat(curriculum): add salary tracker workshop (#62253)

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ilenia <26656284+ilenia-magoni@users.noreply.github.com>
This commit is contained in:
Dario
2025-10-03 09:50:08 +02:00
committed by GitHub
parent 80026e26c4
commit 3a5a409f8e
39 changed files with 2815 additions and 3 deletions
+5 -3
View File
@@ -4569,9 +4569,11 @@
"Learn about Understanding Object Oriented Programming and Encapsulation in these lessons."
]
},
"workshop-placeholder-oop-1": {
"title": "Placeholder - Waiting for title",
"intro": [""]
"workshop-salary-tracker": {
"title": "Build a Salary Tracker",
"intro": [
"In this workshop, you'll practice encapsulation, properties, and other OOP concepts by building a salary tracking system for employees."
]
},
"lab-placeholder-oop-1": {
"title": "Placeholder - Waiting for title",
@@ -0,0 +1,9 @@
---
title: Introduction to the Build a Salary Tracker
block: workshop-salary-tracker
superBlock: full-stack-developer
---
## Introduction to the Build a Salary Tracker
In this workshop, you'll practice encapsulation, properties, and other OOP concepts by building a salary tracking system for employees.
@@ -0,0 +1,48 @@
---
id: 68c15a2a1f0e70ca7154aba5
title: Step 1
challengeType: 20
dashedName: step-1
---
# --description--
In this workshop, you are going to build a salary tracking system for employees.
Start by creating a class named `Employee`. Inside it create an `__init__` method with `self`, `name`, and `level` parameters. Within the `__init__` method, assign `name` and `level` to the instance attributes with the same name.
# --hints--
You should have a class named `Employee`.
```js
({ test: () => assert(runPython(`_Node(_code).has_class("Employee")`)) })
```
Your `Employee` class should have an `__init__` method with three parameters `self`, `name`, and `level`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").has_args("self, name, level")`)) })
```
Your `__init__` method should set `self.name` to `name`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").has_stmt("self.name = name")`)) })
```
Your `__init__` method should set `self.level` to `level`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").has_stmt("self.level = level")`)) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,39 @@
---
id: 68c16924498728d259e4781c
title: Step 2
challengeType: 20
dashedName: step-2
---
# --description--
Now create an instance of the `Employee` class passing in the strings `Charlie Brown` and `trainee`. Assign the instance to a variable named `charlie_brown`.
# --hints--
You should have a variable named `charlie_brown`.
```js
({ test: () => assert(runPython(`_Node(_code).has_variable("charlie_brown")`)) })
```
You should assign `Employee('Charlie Brown', 'trainee')` to `charlie_brown`.
```js
({ test: () => assert(runPython(`_Node(_code).find_variable("charlie_brown").is_equivalent("charlie_brown = Employee('Charlie Brown', 'trainee')")`)) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Employee:
def __init__(self, name, level):
self.name = name
self.level = level
--fcc-editable-region--
```
@@ -0,0 +1,43 @@
---
id: 68c18b1d0fce081f85312d97
title: Step 3
challengeType: 20
dashedName: step-3
---
# --description--
In a previous lesson, you learned that an attribute prefixed with a single underscore is meant for internal use by convention.
Modify both `name` and `level` attributes into `_name` and `_level`, since these are not supposed to be modified from outside their class.
Note that this does not prevent the attribute from being accessed or modified outside the class. Also, in Python there's always a way to access private attributes (prefixed with a double underscore) as well.
# --hints--
Your `__init__` method should set `self._name` to `name`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").has_stmt("self._name = name")`)) })
```
Your `__init__` method should set `self._level` to `level`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").has_stmt("self._level = level")`)) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Employee:
def __init__(self, name, level):
self.name = name
self.level = level
charlie_brown = Employee('Charlie Brown', 'trainee')
--fcc-editable-region--
```
@@ -0,0 +1,64 @@
---
id: 68c27eb292bfec6b9402d58f
title: Step 4
challengeType: 20
dashedName: step-4
---
# --description--
Add a `__str__` method to the `Employee` class. Make it return an f-string with the format `name: level`, replacing `name` and `level` with the corresponding attributes.
After that, print `charlie_brown` to the console.
# --hints--
Your `Employee` class should have a `__str__` method with a `self` parameter .
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__str__").has_args("self")`)) })
```
Your `__str__` method should return an f-string with the format `name: level`, replacing `name` and `level` with the values of `self._name` and `self._level`, respectively.
```js
({ test: () => runPython(`
import io
import sys
captured_output = io.StringIO()
sys.stdout = captured_output
empl = Employee("Frank", "dreamer")
print(empl)
sys.stdout = sys.__stdout__
output = captured_output.getvalue()
assert "Frank: dreamer" in output
`) })
```
You should print `charlie_brown` to the console.
```js
({ test: () => assert(runPython(`_Node(_code).has_stmt("print(charlie_brown)")`)) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
charlie_brown = Employee('Charlie Brown', 'trainee')
--fcc-editable-region--
```
@@ -0,0 +1,66 @@
---
id: 68c27fa1697ceb7c578b2c9f
title: Step 5
challengeType: 20
dashedName: step-5
---
# --description--
The `@property` decorator is used in Python to turn a method into a property. It is typically used to define getter methods, which are methods used to retrieve the value of an attribute:
```py
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
p = Person('Alice')
print(p.name) # Alice
```
Create a method named `name` with a `self` parameter and decorate it with `@property`. Inside the method, return `self._name`.
# --hints--
Your `Employee` class should have a `name` method with a `self` parameter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("name").has_args("self")`)) })
```
Your `name` method should be decorated with `@property`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("name").has_decorators("property")`)) })
```
Your `name` method should return `self._name`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("name").has_return("self._name")`)) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self._name}: {self._level}'
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
--fcc-editable-region--
```
@@ -0,0 +1,41 @@
---
id: 68c281cecda94ba2c6838e17
title: Step 6
challengeType: 20
dashedName: step-6
---
# --description--
Now that you defined a getter for `name`, you can access the `_name` attribute through the `name` property. So print `charlie_brown.name` to the console.
# --hints--
You should print `charlie_brown.name` to the console.
```js
({ test: () => assert(runPython(`_Node(_code).has_stmt("print(charlie_brown.name)")`)) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self._name}: {self._level}'
@property
def name(self):
return self._name
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
--fcc-editable-region--
```
@@ -0,0 +1,55 @@
---
id: 68c28b7cc3ab8f4daf144b5e
title: Step 7
challengeType: 20
dashedName: step-7
---
# --description--
Following what you did in the previous steps, create a getter for the `_level` attribute.
# --hints--
Your `Employee` class should have `level` method with a `self` parameter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("level").has_args("self")`)) })
```
Your `level` method should be decorated with `@property`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("level").has_decorators("property")`)) })
```
Your `level` method should return `self._level`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("level").has_return("self._level")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self._name}: {self._level}'
--fcc-editable-region--
@property
def name(self):
return self._name
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(charlie_brown.name)
```
@@ -0,0 +1,46 @@
---
id: 68c28c29026ad559f8ab4329
title: Step 8
challengeType: 20
dashedName: step-8
---
# --description--
Now print `charlie_brown.level` to the console.
# --hints--
You should print `charlie_brown.level`.
```js
({ test: () => assert(runPython(`_Node(_code).has_stmt("print(charlie_brown.level)")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self._name}: {self._level}'
@property
def name(self):
return self._name
@property
def level(self):
return self._level
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(charlie_brown.name)
--fcc-editable-region--
```
@@ -0,0 +1,45 @@
---
id: 68c28c6517e7de5e85f8ebe5
title: Step 9
challengeType: 20
dashedName: step-9
---
# --description--
Now that you have getters for both `_name` and `_level` attributes, update the string returned by `__str__` to use `self.name` and `self.level`. This will call the getters instead of directly accessing the attributes.
# --hints--
Your `__str__` method should return `f'{self.name}: {self.level}'`
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__str__").has_return("f'{self.name}: {self.level}'")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
--fcc-editable-region--
def __str__(self):
return f'{self._name}: {self._level}'
--fcc-editable-region--
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(charlie_brown.name)
print(charlie_brown.level)
```
@@ -0,0 +1,53 @@
---
id: 68c28cfab52fef6913d18166
title: Step 10
challengeType: 20
dashedName: step-10
---
# --description--
Now remove the last two `print` calls.
# --hints--
You should not have `print(charlie_brown.name)` in your code.
```js
({ test: () => assert(runPython(`not _Node(_code).has_stmt("print(charlie_brown.name)")`)) })
```
You should not have `print(charlie_brown.level)` in your code.
```js
({ test: () => assert(runPython(`not _Node(_code).has_stmt("print(charlie_brown.level)")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self.name}: {self.level}'
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
--fcc-editable-region--
print(charlie_brown.name)
print(charlie_brown.level)
--fcc-editable-region--
```
@@ -0,0 +1,58 @@
---
id: 68c28d659d345270d4a61084
title: Step 11
challengeType: 20
dashedName: step-11
---
# --description--
The `__repr__` method is a special method that is supposed to return a string representation of the object that can be used to instantiate it.
For example, the `__repr__` method of `Employee('Charlie', 'developer')` should return the string `Employee('Charlie', 'developer')`, which is the same string used to create the object.
Give your `Employee` class a `__repr__` method with a `self` parameter, and make it return a string that can be used to instantiate the object.
# --hints--
Your `Employee` class should have a `__repr__` method with a `self` parameter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__repr__").has_args("self")`)) })
```
Your `__repr__` method should return the string used to instantiate the object.
```js
({ test: () => runPython(`
empl = Employee("Frank", "dreamer")
assert repr(empl) == 'Employee("Frank", "dreamer")' or repr(empl) == "Employee('Frank', 'dreamer')"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
--fcc-editable-region--
def __str__(self):
return f'{self.name}: {self.level}'
--fcc-editable-region--
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
```
@@ -0,0 +1,48 @@
---
id: 68c2d2414f5e111c02f4db5e
title: Step 12
challengeType: 20
dashedName: step-12
---
# --description--
The `__repr__` method is called under the hood when you call the `repr` function. To see it in action, print the result of calling `repr(charlie_brown)` at the bottom of your code.
# --hints--
You should print `repr(charlie_brown)`.
```js
({ test: () => assert(runPython(`_Node(_code).has_stmt("print(repr(charlie_brown))")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
--fcc-editable-region--
```
@@ -0,0 +1,71 @@
---
id: 68c2d31a86d5672ca49d4521
title: Step 14
challengeType: 20
dashedName: step-14
---
# --description--
Now it's time to add some validation to the `__init__` method. At the beginning of the method, create an `if` statement that checks if either `name` or `level` are not instances of `str`.
Inside the `if` statement, raise a `TypeError` with the message `'name' and 'level' attribute must be of type 'str'.`.
# --hints--
You should have an `if` statement in your `__init__` method.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").find_ifs()[0]`)) })
```
Your `if` statement should check if either `name` and `level` are not instances of `str`.
```js
({ test: () => runPython(`
cond = _Node(_code).find_class("Employee").find_function("__init__").find_ifs()[0].find_conditions()[0]
conditions = [
"not (isinstance(name, str) and isinstance(level, str))",
"not isinstance(name, str) or not isinstance(level, str)",
"not (isinstance(level, str) and isinstance(name, str))",
"not isinstance(level, str) or not isinstance(name, str)",
]
assert any(cond.is_equivalent(c) for c in conditions)
`) })
```
Your `if` statement should raise a `TypeError` with the message `'name' and 'level' attribute must be of type 'str'.`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("__init__").find_ifs()[0].find_bodies()[0].has_stmt('raise TypeError("\\'name\\' and \\'level\\' attribute must be of type \\'str\\'.")')`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
--fcc-editable-region--
def __init__(self, name, level):
self._name = name
self._level = level
--fcc-editable-region--
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
```
@@ -0,0 +1,68 @@
---
id: 68c7e19810faf3d00dba53d5
title: Step 15
challengeType: 20
dashedName: step-15
---
# --description--
Create a class attribute named `_base_salaries` and assign it the following dictionary:
```py
{
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000
}
```
# --hints--
Your `Employee` class should have a class attribute named `_base_salaries`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").has_variable("_base_salaries")`)) })
```
You should assign `{'trainee': 1000, 'junior': 2000, 'mid-level': 3000, 'senior': 4000}` to the `_base_salaries` class attribute.
```js
({ test: () => runPython(`
assert Employee._base_salaries == {'trainee': 1000, 'junior': 2000, 'mid-level': 3000, 'senior': 4000}
`) })
```
# --seed--
## --seed-contents--
```py
--fcc-editable-region--
class Employee:
--fcc-editable-region--
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
self._name = name
self._level = level
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
```
@@ -0,0 +1,86 @@
---
id: 68c7f7012a700243eff1cbc0
title: Step 16
challengeType: 20
dashedName: step-16
---
# --description--
The level used to instantiate an employee should be chosen among specific levels. You'll use `_base_salaries` to validate the level and set the right salary for the employee.
After your existing `if` statement, create another `if` that checks if `level` is not in `Employee._base_salaries`.
Inside the new `if` statement, raise a `ValueError` with the message `Invalid value '{level}' for 'level' attribute.`, where `{level}` should be replaced by the value of the `level` argument.
# --hints--
When `level` is not in `Employee._base_salaries`, you should raise a `ValueError` with the message `Invalid value '{level}' for 'level' attribute.`, where `{level}` should be replaced by the value of the `level` argument.
```js
({ test: () => runPython(`
try:
Employee("Frank", "dreamer")
except ValueError as e:
assert str(e) == "Invalid value 'dreamer' for 'level' attribute."
else:
assert False, "Expected to raise ValueError with invalid level"
`) })
```
You should not raise any exception when `level` is in `Employee._base_salaries`.
```js
({ test: () => runPython(`
levels = [
"trainee",
"junior",
"mid-level",
"senior"
]
for level in levels:
try:
Employee("Frank", level)
except Exception:
assert False, f"Expected not to raise ValueError with level '{level}'"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
--fcc-editable-region--
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
self._name = name
self._level = level
--fcc-editable-region--
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
```
@@ -0,0 +1,64 @@
---
id: 68c7fecda6d210cc8f1960e8
title: Step 17
challengeType: 20
dashedName: step-17
---
# --description--
Finally, at the bottom of the `__init__` method, set the `_salary` attribute to the value corresponding to `level` inside the `_base_salaries` dictionary.
# --hints--
You should set `self._salary` to the value corresponding to `level` inside the `_base_salaries` dictionary
```js
({ test: () => runPython(`
salaries = Employee._base_salaries
assert len(salaries) > 0
for level, salary in salaries.items():
emp = Employee("Frank", level)
assert emp._salary == salary
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
--fcc-editable-region--
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
--fcc-editable-region--
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
```
@@ -0,0 +1,98 @@
---
id: 68c8002beb157fe47d2e7699
title: Step 20
challengeType: 20
dashedName: step-20
---
# --description--
A setter is a method used to set the value of an attribute, allowing for validation checks and restrictions. You can create a setter using the `@propertyName.setter` decorator, where `propertyName` should match the name of the property to set:
```py
class Person:
def __init__(self, name):
self._name = name
@property
def name(self):
return self._name
@name.setter
def name(self, value):
self._name = value
p = Person('Alice')
p.name = 'Abigail' # Calls the setter
print(p.name) # Abigail
```
After your getter method, create a `name` method with parameters `self` and `new_name`. Decorate the method with `@name.setter`. Inside the method, set `self._name` to `new_name`.
# --hints--
Your `Employee` class should have a `name` method with parameters `self` and `new_name`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("name")[1].has_args("self, new_name")`)) })
```
Your `name` method should be decorated with `@name.setter`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("name")[1].has_decorators("name.setter")`)) })
```
Your `name` method should set `self._name` to `new_name`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("name")[1].has_stmt("self._name = new_name")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
--fcc-editable-region--
@property
def name(self):
return self._name
--fcc-editable-region--
@property
def level(self):
return self._level
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,97 @@
---
id: 68c809c990f253912a9e9209
title: Step 21
challengeType: 20
dashedName: step-21
---
# --description--
As you learned in a previous lesson, a setter offers a way to control how an attribute can be modified. To ensure that `new_name` is the right type, create an `if` statement that raises a `TypeError` with the message `'name' must be a string.` when `new_name` is not an instance of `str`.
# --hints--
You should have an `if` statement inside your `name` setter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("name")[1].find_ifs()[0]`)) })
```
You should raise a `TypeError` with the message `'name' must be a string.` when `new_name` is not an instance of `str`.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
non_names = [0, True, []]
for i in non_names:
try:
emp.name = i
except TypeError as e:
assert str(e) == "'name' must be a string."
else:
assert False, "Expected to raise TypeError with non-string new_name"
`) })
```
You should not raise any exception when `new_name` is a string.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
try:
emp.name = "Jack"
except Exception:
assert False, "Expected not to raise any exception with valid new_name"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
--fcc-editable-region--
@name.setter
def name(self, new_name):
self._name = new_name
--fcc-editable-region--
@property
def level(self):
return self._level
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,84 @@
---
id: 68c80b63ca4e5eadf1fac738
title: Step 22
challengeType: 20
dashedName: step-22
---
# --description--
Right after setting the `_name` attribute to `new_name`, print `'name' updated to 'new_name'.`, where `new_name` should be replaced by the new value of `_name`.
# --hints--
You should print `'name' updated to 'new_name'.`, where `new_name` should be replaced by the new value of `_name`.
```js
({ test: () => runPython(`
import io
import sys
captured_output = io.StringIO()
sys.stdout = captured_output
empl = Employee("Frank", "trainee")
empl.name = "Terry"
sys.stdout = sys.__stdout__
output = captured_output.getvalue()
assert "'name' updated to 'Terry'." in output
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
--fcc-editable-region--
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
--fcc-editable-region--
@property
def level(self):
return self._level
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,49 @@
---
id: 68c80c1a8e58febb77120bbc
title: Step 13
challengeType: 20
dashedName: step-13
---
# --description--
Remove the last `print` call from your code.
# --hints--
You should remove `print(repr(charlie_brown))` from your code.
```js
({ test: () => assert(runPython(`not _Node(_code).has_stmt("print(repr(charlie_brown))")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
def __init__(self, name, level):
self._name = name
self._level = level
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
--fcc-editable-region--
print(repr(charlie_brown))
--fcc-editable-region--
```
@@ -0,0 +1,78 @@
---
id: 68c9a09163d4a42e9bc9b638
title: Step 23
challengeType: 20
dashedName: step-23
---
# --description--
Now you have a setter method for the `_name` attribute that exposes the `name` property.
So try to update `charlie_brown`'s `name` to a different name as you would normally do with any attribute. This will call the setter method under the hood.
# --hints--
You should update `charlie_brown`'s `name` to a different name.
```js
({ test: () => runPython(`
assert _Node(_code).has_variable("charlie_brown.name")
assert charlie_brown.name != 'Charlie Brown'
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,75 @@
---
id: 68c9a1c241206043c81293d5
title: Step 18
challengeType: 20
dashedName: step-18
---
# --description--
Now that you have a new attribute, you're going to create a getter for it.
Create a method named `salary` with a `self` parameter and use the `@property` decorator on it. Inside the method, return `self._salary`.
# --hints--
Your `Employee` class should have a `salary` method with a `self` parameter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("salary").has_args("self")`)) })
```
Your `salary` method should be decorated with `@property`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("salary").has_decorators("property")`)) })
```
Your `salary` method should return `self._salary`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_function("salary").has_return("self._salary")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
--fcc-editable-region--
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
```
@@ -0,0 +1,64 @@
---
id: 68c9a2c015ed7b54cf81e789
title: Step 19
challengeType: 20
dashedName: step-19
---
# --description--
At the bottom of your code, use an f-string to print `Base salary: $` followed by the amount of `charlie_brown`'s salary.
# --hints--
You should print an f-string containing `Base salary: $` followed by the amount of `charlie_brown`'s salary.
```js
({ test: () => assert(runPython(`_Node(_code).has_stmt("print(f'Base salary: \${charlie_brown.salary}')")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@property
def level(self):
return self._level
@property
def salary(self):
return self._salary
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
--fcc-editable-region--
```
@@ -0,0 +1,75 @@
---
id: 68c9a624fd54288f694ebdbf
title: Step 24
challengeType: 20
dashedName: step-24
---
# --description--
Feel free to set the `name` property to something that is not a string to see the validation process in action. After that, restore `charlie_brown`'s `name` by removing any line of code that changes its name.
# --hints--
You should restore `charlie_brown`'s `name`.
```js
({ test: () => runPython(`
assert charlie_brown.name == 'Charlie Brown'
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
--fcc-editable-region--
charlie_brown.name = 'Snoopy'
--fcc-editable-region--
```
@@ -0,0 +1,87 @@
---
id: 68c9a72c9dab42a15ce6972b
title: Step 25
challengeType: 20
dashedName: step-25
---
# --description--
Now you'll create a setter for the `_level` attribute. Create a method named `level` with parameters `self` and `new_level`. Decorate the method with `@level.setter`.
Inside the method, set `self._level` to `new_level`.
# --hints--
Your `Employee` class should have a `level` method with parameters `self` and `new_level`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("level")[1].has_args("self, new_level")`)) })
```
Your `level` method should be decorated with `@level.setter`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("level")[1].has_decorators("level.setter")`)) })
```
Your `level` method should set `self._level` to `new_level`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("level")[1].has_stmt("self._level = new_level")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
--fcc-editable-region--
@property
def level(self):
return self._level
--fcc-editable-region--
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,105 @@
---
id: 68c9c3aa714bc326e026b826
title: Step 26
challengeType: 20
dashedName: step-26
---
# --description--
The new level cannot be set without checking if it's a valid level. At the beginning of your setter, create an `if` statement that raises a `ValueError` when `new_level` is not a key of `Employee._base_salaries`.
For the error message, use `Invalid value '{new_level}' for 'level' attribute.`, where `{new_level}` should be replaced by the argument passed to the setter.
# --hints--
You should have an `if` statement inside your `level` setter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("level")[1].find_ifs()[0]`)) })
```
When `new_level` is not a key of `Employee._base_salaries`, you should raise a `ValueError` with the message `Invalid value '{new_level}' for 'level' attribute.`, where `{new_level}` should be replaced by the argument passed to the setter.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
try:
emp.level = "dreamer"
except ValueError as e:
assert str(e) == "Invalid value 'dreamer' for 'level' attribute."
else:
assert False, "Expected to raise ValueError with invalid new_level"
`) })
```
You should not raise any exception when `new_level` is a key of `Employee._base_salaries`
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
for i in Employee._base_salaries:
try:
emp.level = i
except Exception:
assert False, "Expected not to raise any exception with valid new_level"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
--fcc-editable-region--
@level.setter
def level(self, new_level):
self._level = new_level
--fcc-editable-region--
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,95 @@
---
id: 68c9cab4b1118da59eecfc56
title: Step 27
challengeType: 20
dashedName: step-27
---
# --description--
After the existing `if` statement, create another one to raise a `ValueError` when `new_level` is already the selected level.
For the message, use `'{level}' is already the selected level.`, where `{level}` should be replaced by the current level.
# --hints--
You should have a second `if` statement inside your `level` setter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("level")[1].find_ifs()[1]`)) })
```
When `new_level` is equal to `self.level`, you should raise a `ValueError` with the message `'{level}' is already the selected level.`, where `{level}` should be replaced by the current level.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
try:
emp.level = "trainee"
except ValueError as e:
assert str(e) == "'trainee' is already the selected level."
else:
assert False, "Expected to raise ValueError with invalid new_level"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
--fcc-editable-region--
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
self._level = new_level
--fcc-editable-region--
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,106 @@
---
id: 68ca758f8160b11757f877ae
title: Step 28
challengeType: 20
dashedName: step-28
---
# --description--
Finally, create a third `if` statement that raises a `ValueError` with the message `Cannot change to lower level.` when the base salary of the new level is less than the base salary of the current level.
# --hints--
You should have a third `if` statement inside your `level` setter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("level")[1].find_ifs()[2]`)) })
```
When `new_level` is lower than `self.level`, you should raise a `ValueError` with the message `Cannot change to lower level.`.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'junior')
try:
emp.level = "trainee"
except ValueError as e:
assert str(e) == "Cannot change to lower level."
else:
assert False, "Expected to raise ValueError with invalid new_level"
`) })
```
You should not raise any exception when `new_level` is higher than `self.level`.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'junior')
try:
emp.level = "senior"
except Exception:
assert False, "Expected not to raise exception with valid new_level"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
--fcc-editable-region--
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
self._level = new_level
--fcc-editable-region--
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,90 @@
---
id: 68caa8fb3bed34833ef24aee
title: Step 29
challengeType: 20
dashedName: step-29
---
# --description--
When the level is modified, you need to update the salary as well.
Before setting `self._level`, set `self._salary` to the base salary for the new level.
# --hints--
You should set `self._salary` to the base salary for the new level.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
new_levels = ['junior', 'mid-level', 'senior']
for new_level in new_levels:
emp.level = new_level
assert emp.salary == Employee._base_salaries.get(new_level)
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
--fcc-editable-region--
self._level = new_level
--fcc-editable-region--
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,97 @@
---
id: 68caa9fb6f1602975e41b051
title: Step 30
challengeType: 20
dashedName: step-30
---
# --description--
Complete the setter by printing `'{name}' promoted to '{new_level}'.` inside your method. Replace `{name}` and `{new_level}` with the employee's name and new level, respectively.
# --hints--
You should print `'{name}' promoted to '{new_level}'.` where `{name}` and `{new_level}` should be replaced with the employee's name and new level, respectively.
```js
({ test: () => runPython(`
import io
import sys
captured_output = io.StringIO()
sys.stdout = captured_output
empl = Employee("Frank", "trainee")
empl.level = 'junior'
sys.stdout = sys.__stdout__
output = captured_output.getvalue()
assert "'Frank' promoted to 'junior'." in output
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
--fcc-editable-region--
self._salary = Employee._base_salaries[new_level]
self._level = new_level
--fcc-editable-region--
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
```
@@ -0,0 +1,87 @@
---
id: 68caaaef4afb18aab8a684d4
title: Step 31
challengeType: 20
dashedName: step-31
---
# --description--
It's time to test your new setter. Try to assign invalid values such as a random string or the current level (`trainee`) to `charlie_brown.level` and see the error messages in the console.
Once you've done, remove the lines raising errors and set `charlie_brown.level` to the string `junior`.
# --hints--
You should set `charlie_brown.level` to the string `junior`.
```js
({ test: () => assert(runPython(`_Node(_code).has_stmt("charlie_brown.level = 'junior'")`)) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
print(f"'{self.name}' promoted to '{new_level}'.")
self._salary = Employee._base_salaries[new_level]
self._level = new_level
@property
def salary(self):
return self._salary
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
--fcc-editable-region--
--fcc-editable-region--
```
@@ -0,0 +1,123 @@
---
id: 68caacb0f4311cc9be9a2132
title: Step 32
challengeType: 20
dashedName: step-32
---
# --description--
Now you'll focus on coding a `salary` setter. After the `salary` getter, create a simple setter for the `salary` property that sets `self._salary` to the value passed to the method as its argument. After that, print `Salary updated to $` followed by the new salary and a period.
You'll take care of validating the new salary in the next few steps.
# --hints--
Your `Employee` class should have a `salary` method decorated with `@salary.setter`.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("salary")[1].has_decorators("salary.setter")`)) })
```
Your `salary` setter should set `self._salary` to the value passed to it as the argument.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
built_in_print = print
print = lambda x: x
emp.salary += 100
print = built_in_print
assert emp.salary == Employee._base_salaries['trainee'] + 100
`) })
```
Your `salary` setter should print `Salary updated to $` followed by the new salary and a period.
```js
({ test: () => runPython(`
import io
import sys
captured_output = io.StringIO()
sys.stdout = captured_output
emp = Employee('Frank', 'trainee')
emp.salary += 100
sys.stdout = sys.__stdout__
output = captured_output.getvalue()
assert f"Salary updated to \${Employee._base_salaries['trainee'] + 100}" in output
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
print(f"'{self.name}' promoted to '{new_level}'.")
self._salary = Employee._base_salaries[new_level]
self._level = new_level
--fcc-editable-region--
@property
def salary(self):
return self._salary
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
charlie_brown.level = 'junior'
```
@@ -0,0 +1,127 @@
---
id: 68caaf1e590806f0b87f5922
title: Step 33
challengeType: 20
dashedName: step-33
---
# --description--
At the beginning of your setter, create an `if` statement that raises a `TypeError` with the message `'salary' must be a number.` when `new_salary` is not either an integer or a float.
# --hints--
You should have an `if` statement in your `salary` setter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("salary")[1].find_ifs()[0]`)) })
```
You should raise a `TypeError` with the message `'salary' must be a number.` when `new_salary` is not either an integer or a float.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
built_in_print = print
print = lambda x: x
try:
emp.salary = "100"
except TypeError as e:
print = built_in_print
assert str(e) == "'salary' must be a number."
else:
assert False, "Expected to raise TypeError with invalid new_salary"
`) })
```
You should not raise any exception when `new_salary` is either an integer or a float.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
built_in_print = print
print = lambda x: x
try:
emp.salary = 100
except Exception:
assert False, "Expected not to raise exception with valid new_salary"
try:
emp.salary = 100.00
except Exception:
assert False, "Expected not to raise exception with valid new_salary"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
print(f"'{self.name}' promoted to '{new_level}'.")
self._salary = Employee._base_salaries[new_level]
self._level = new_level
@property
def salary(self):
return self._salary
--fcc-editable-region--
@salary.setter
def salary(self, new_salary):
self._salary = new_salary
print(f'Salary updated to ${self.salary}.')
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
charlie_brown.level = 'junior'
```
@@ -0,0 +1,134 @@
---
id: 68cab02fd80a91042c0165b8
title: Step 34
challengeType: 20
dashedName: step-34
---
# --description--
After your existing `if` statement, create another one for when the new salary is less than the base salary for the current level.
Inside the `if` statement, raise a `ValueError` with the message `Salary must be higher than minimum salary $` followed by the base salary for the current level and a period.
# --hints--
You should have a second `if` statement in your `salary` setter.
```js
({ test: () => assert(runPython(`_Node(_code).find_class("Employee").find_functions("salary")[1].find_ifs()[1]`)) })
```
When the new salary is less than the base salary for the current level, you should raise a `ValueError` with the message `Salary must be higher than minimum salary $` followed by the base salary for the current level and a period.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
built_in_print = print
print = lambda x: x
minimum_salary = Employee._base_salaries['trainee']
try:
emp.salary = minimum_salary - 1
except ValueError as e:
print = built_in_print
assert str(e) == f"Salary must be higher than minimum salary \${minimum_salary}."
else:
assert False, "Expected to raise ValueError with invalid new_salary"
`) })
```
You should not raise any exception when `new_salary` is greater than the current salary.
```js
({ test: () => runPython(`
emp = Employee('Frank', 'trainee')
built_in_print = print
print = lambda x: x
minimum_salary = Employee._base_salaries['trainee']
try:
emp.salary = minimum_salary + 1
except Exception:
assert False, "Expected not to raise exception with valid new_salary"
try:
emp.salary = minimum_salary + 1000
except Exception:
assert False, "Expected not to raise exception with valid new_salary"
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
print(f"'{self.name}' promoted to '{new_level}'.")
self._salary = Employee._base_salaries[new_level]
self._level = new_level
@property
def salary(self):
return self._salary
--fcc-editable-region--
@salary.setter
def salary(self, new_salary):
if not isinstance(new_salary, (int, float)):
raise TypeError("'salary' must be a number.")
self._salary = new_salary
print(f'Salary updated to ${self.salary}.')
--fcc-editable-region--
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
charlie_brown.level = 'junior'
```
@@ -0,0 +1,187 @@
---
id: 68cab17ac51b861a8458c42d
title: Step 35
challengeType: 20
dashedName: step-35
---
# --description--
Finally, now that you have completed the `salary` setter, you can call it within the `level` setter.
Modify the line `self._salary = Employee._base_salaries[new_level]` so that it calls the `salary` setter instead of modifying directly the `_salary` attribute.
With that the salary tracker workshop is complete.
# --hints--
You should set `self.salary` to `Employee._base_salaries[new_level]` within the `level` setter.
```js
({ test: () => runPython(`
import io
import sys
captured_output = io.StringIO()
sys.stdout = captured_output
empl = Employee("Frank", "trainee")
empl.level = "junior"
sys.stdout = sys.__stdout__
output = captured_output.getvalue()
assert "'Frank' promoted to 'junior'." in output
assert f"Salary updated to \${Employee._base_salaries['junior']}." in output
`) })
```
# --seed--
## --seed-contents--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
print(f"'{self.name}' promoted to '{new_level}'.")
--fcc-editable-region--
self._salary = Employee._base_salaries[new_level]
--fcc-editable-region--
self._level = new_level
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, new_salary):
if not isinstance(new_salary, (int, float)):
raise TypeError("'salary' must be a number.")
if new_salary <= Employee._base_salaries[self.level]:
raise ValueError(f'Salary must be higher than minimum salary ${Employee._base_salaries[self.level]}.')
self._salary = new_salary
print(f'Salary updated to ${self.salary}.')
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
charlie_brown.level = 'junior'
```
# --solutions--
```py
class Employee:
_base_salaries = {
'trainee': 1000,
'junior': 2000,
'mid-level': 3000,
'senior': 4000,
}
def __init__(self, name, level):
if not (isinstance(name, str) and isinstance(level, str)):
raise TypeError("'name' and 'level' attribute must be of type 'str'.")
if level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{level}' for 'level' attribute.")
self._name = name
self._level = level
self._salary = Employee._base_salaries[level]
def __str__(self):
return f'{self.name}: {self.level}'
def __repr__(self):
return f"Employee('{self.name}', '{self.level}')"
@property
def name(self):
return self._name
@name.setter
def name(self, new_name):
if not isinstance(new_name, str):
raise TypeError("'name' must be a string.")
self._name = new_name
print(f"'name' updated to '{self.name}'.")
@property
def level(self):
return self._level
@level.setter
def level(self, new_level):
if new_level not in Employee._base_salaries:
raise ValueError(f"Invalid value '{new_level}' for 'level' attribute.")
if new_level == self.level:
raise ValueError(f"'{self.level}' is already the selected level.")
if Employee._base_salaries[new_level] < Employee._base_salaries[self.level]:
raise ValueError(f"Cannot change to lower level.")
print(f"'{self.name}' promoted to '{new_level}'.")
self.salary = Employee._base_salaries[new_level]
self._level = new_level
@property
def salary(self):
return self._salary
@salary.setter
def salary(self, new_salary):
if not isinstance(new_salary, (int, float)):
raise TypeError("'salary' must be a number.")
if new_salary <= Employee._base_salaries[self.level]:
raise ValueError(f'Salary must be higher than minimum salary ${Employee._base_salaries[self.level]}.')
self._salary = new_salary
print(f'Salary updated to ${self.salary}.')
charlie_brown = Employee('Charlie Brown', 'trainee')
print(charlie_brown)
print(f'Base salary: ${charlie_brown.salary}')
charlie_brown.level = 'junior'
```
@@ -0,0 +1,47 @@
{
"name": "Build a Salary Tracker",
"isUpcomingChange": true,
"dashedName": "workshop-salary-tracker",
"helpCategory": "Python",
"blockLayout": "challenge-grid",
"blockType": "workshop",
"challengeOrder": [
{ "id": "68c15a2a1f0e70ca7154aba5", "title": "Step 1" },
{ "id": "68c16924498728d259e4781c", "title": "Step 2" },
{ "id": "68c18b1d0fce081f85312d97", "title": "Step 3" },
{ "id": "68c27eb292bfec6b9402d58f", "title": "Step 4" },
{ "id": "68c27fa1697ceb7c578b2c9f", "title": "Step 5" },
{ "id": "68c281cecda94ba2c6838e17", "title": "Step 6" },
{ "id": "68c28b7cc3ab8f4daf144b5e", "title": "Step 7" },
{ "id": "68c28c29026ad559f8ab4329", "title": "Step 8" },
{ "id": "68c28c6517e7de5e85f8ebe5", "title": "Step 9" },
{ "id": "68c28cfab52fef6913d18166", "title": "Step 10" },
{ "id": "68c28d659d345270d4a61084", "title": "Step 11" },
{ "id": "68c2d2414f5e111c02f4db5e", "title": "Step 12" },
{ "id": "68c80c1a8e58febb77120bbc", "title": "Step 13" },
{ "id": "68c2d31a86d5672ca49d4521", "title": "Step 14" },
{ "id": "68c7e19810faf3d00dba53d5", "title": "Step 15" },
{ "id": "68c7f7012a700243eff1cbc0", "title": "Step 16" },
{ "id": "68c7fecda6d210cc8f1960e8", "title": "Step 17" },
{ "id": "68c9a1c241206043c81293d5", "title": "Step 18" },
{ "id": "68c9a2c015ed7b54cf81e789", "title": "Step 19" },
{ "id": "68c8002beb157fe47d2e7699", "title": "Step 20" },
{ "id": "68c809c990f253912a9e9209", "title": "Step 21" },
{ "id": "68c80b63ca4e5eadf1fac738", "title": "Step 22" },
{ "id": "68c9a09163d4a42e9bc9b638", "title": "Step 23" },
{ "id": "68c9a624fd54288f694ebdbf", "title": "Step 24" },
{ "id": "68c9a72c9dab42a15ce6972b", "title": "Step 25" },
{ "id": "68c9c3aa714bc326e026b826", "title": "Step 26" },
{ "id": "68c9cab4b1118da59eecfc56", "title": "Step 27" },
{ "id": "68ca758f8160b11757f877ae", "title": "Step 28" },
{ "id": "68caa8fb3bed34833ef24aee", "title": "Step 29" },
{ "id": "68caa9fb6f1602975e41b051", "title": "Step 30" },
{ "id": "68caaaef4afb18aab8a684d4", "title": "Step 31" },
{ "id": "68caacb0f4311cc9be9a2132", "title": "Step 32" },
{ "id": "68caaf1e590806f0b87f5922", "title": "Step 33" },
{ "id": "68cab02fd80a91042c0165b8", "title": "Step 34" },
{ "id": "68cab17ac51b861a8458c42d", "title": "Step 35" }
],
"usesMultifileEditor": true,
"hasEditableBoundaries": true
}
@@ -725,6 +725,7 @@
"comingSoon": true,
"blocks": [
"lecture-understanding-object-oriented-programming-and-encapsulation",
"workshop-salary-tracker",
"lecture-understanding-inheritance-and-polymorphism",
"workshop-media-catalogue",
"lab-polygon-area-calculator",