Repository: exercism/python
Pull request #3425 (2023-06-06 02:50:16)

Remove footer macros, regen tests, all still pass

github-actions[bot]: Hello. Thanks for opening a PR on Exercism. We are currently in a phase of our journey where we have paused community contributions to allow us to take a breather and redesign our community model. You can learn more in [this blog post](https://exercism.org/blog/freeing-our-maintainers). **As such, all issues and PRs in this repository are being automatically closed.**

That doesn't mean we're not interested in your ideas, or that if you're stuck on something we don't want to help. The best place to discuss things is with our community on the Exercism Community Forum. You can use [this link](https://forum.exercism.org/new-topic?title=Remove%20footer%20macros,%20regen%20tests,%20all%20still%20pass&body=null&category=python) to copy this into a new topic there.

---

_Note: If this PR has been pre-approved, please link back to this PR on the forum thread and a maintainer or staff member will reopen it._

BethanyG: Looks like maybe the `scale-generator` exercise didn't get its tests re-generated?  The CI is failing with:

```bash
DEBUG:generator:scale-generator: attempting render
DEBUG:generator:scale-generator: writing render to tmp file /tmp/tmp3_tbmkev
DEBUG:generator:scale-generator: formatting tmp file /tmp/tmp3_tbmkev
DEBUG:generator:scale-generator: ##### DIFF START #####
DEBUG:generator:--- [current] scale_generator_test.py
DEBUG:generator:+++ [generated] tmp3_tbmkev
DEBUG:generator:@@ -8,6 +8,7 @@
DEBUG:generator:
DEBUG:generator:
DEBUG:generator:class ScaleGeneratorTest(unittest.TestCase):
DEBUG:generator:+
DEBUG:generator:# Test chromatic scales
DEBUG:generator:def test_chromatic_scale_with_sharps(self):
DEBUG:generator:expected = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]
DEBUG:generator:scale-generator: ##### DIFF END #####
ERROR:generator:scale-generator: check failed; tests must be regenerated with bin/generate_tests.py
DEBUG:generator:scale-generator: removing tmp file /tmp/tmp3_tbmkev
```

Patch:
diff --git a/concepts/binary-octal-hexadecimal/.meta/config.json b/concepts/binary-octal-hexadecimal/.meta/config.json
new file mode 100644
index 00000000..6e2a15b6
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/.meta/config.json
@@ -0,0 +1,4 @@
+{
+  "blurb": "Other numerical systems in Python: binary (0b11), octal (0o71), and hex (0xFF)",
+  "authors": ["BethanyG", "meatball133"]
+}
diff --git a/concepts/binary-octal-hexadecimal/about.md b/concepts/binary-octal-hexadecimal/about.md
new file mode 100644
index 00000000..667f3eb6
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/about.md
@@ -0,0 +1,224 @@
+# Binary, Octal, and Hexadecimal
+
+Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases.
+Binary is base 2, octal is base 8, and hexadecimal is base 16.
+Normal integers are base 10 in python.
+Binary, octal, and hexadecimal are all representations of integers.
+Which means that they represent positive and negative numbers (_including zero_) without fractions or decimals, and support all the operations that we can do with integers.
+
+## Binary
+
+[Binary][binary] is a base 2 numeral system, using only the digits 0 and 1.
+It commonly represents the 0 ("off") and 1 ("on") states of electrical flow through transistors and switches in computers, as well as the positive and negative charges in magnetic storage media.
+Binary can represent all the integers that are used in base 10.
+
+A snippet from the base 2 system looks like this, although it continues infinitely and doesn't stop at 128:
+
+| 128      | 64       | 32       | 16       | 8        | 4        | 2        | 1        |
+| -------- | -------- | -------- | -------- | -------- | -------- | -------- | -------- |
+| 2 \*\* 7 | 2 \*\* 6 | 2 \*\* 5 | 2 \*\* 4 | 2 \*\* 3 | 2 \*\* 2 | 2 \*\* 1 | 2 \*\* 0 |
+
+So if we want to represent the number 6, it would in binary be: 110
+
+| Place value   | 4   | 2   | 1   |
+| ------------- | --- | --- | --- |
+| Binary number | 1   | 1   | 0   |
+
+And the operation would be: `4 + 2 + 0 = 6`
+
+Another example: 19
+
+| Place value   | 16  | 8   | 4   | 2   | 1   |
+| ------------- | --- | --- | --- | --- | --- |
+| Binary number | 1   | 0   | 0   | 1   | 1   |
+
+The binary number would be: 10011
+And the operation would be: `16 + 0 + 0 + 2 + 1 = 19`
+
+## Binary in Python
+
+In Python, we can represent binary literals using the `0b` prefix.
+If we write `0b10011`, Python will interpret it as a binary number and convert it to base 10.
+
+```python
+# 0b10011
+>>> 0b10011
+19
+
+>>> type(0b10011)
+<class 'int'>
+```
+
+Binary in Python is just a different way of writing an integer and so the binary representation **is an integer** for all mathematical operations.
+
+If you write a number with a `0b` prefix that is not in the binary system, it will raise a `SyntaxError`.
+
+```python
+Traceback (most recent call last):
+  File "c:\binary.py", line 1, in <module>
+    0b10211
+SyntaxError: invalid digit '2' in binary literal
+```
+
+### Operations with Binary Numbers
+
+Since binary numbers are integers, we can perform all operations on them that we can with integers.
+
+```python
+# addition
+>>> 0b10011 + 0b10011
+38
+
+# multiplication
+>>> 0b10011 * 0b10011
+361
+```
+
+We can also perform operations between both binary and integer representations.
+However, the usual mathematical operator rules apply:  dividing two binary numbers or integer numbers will return a `float`, even if the division does not result in a decimal portion.
+
+```python
+>>> 0b10011 + 19
+38
+
+>>> 0b10011/0b10011
+1.0
+
+>>> 0b10011/3
+6.333333333333333
+
+### Converting to and from Binary Representation
+
+Python will automatically convert a binary literal into `int`.
+ To convert an `int` into a binary representation, use the built-in [`bin()`][bin] function.
+`bin()` will return a `str` of the binary equivalent with the prefix `0b` .
+
+```python
+>>> bin(19)
+'0b10011'
+```
+
+To convert a binary literal to an integer, we can use the built-in `int()` function, and pass a string of the binary representation and a base argument:
+
+```python
+>>> int("0b10011", 2)
+19
+```
+
+Giving the wrong base (_or an invalid binary representation_) will raise a `ValueError`:
+
+```python
+Traceback (most recent call last):
+  File "c:\binary.py", line 4, in <module>
+    int("0b10011", 3)
+ValueError: invalid literal for int() with base 3: '0b10011'
+```
+
+### Binary Methods
+
+There are also some special [methods][numeral-systems] that we can use on binary numbers.
+
+
+[`<int>.bit_length()`][bit_length] will return the number of bits that are needed to represent the number:
+
+```python
+>>> 0b11011.bit_length()
+5
+```
+
+
+[`<int>.bit_count()`][bit_count] will return the number of **ones** in the binary number.
+For example, `bit_count()` on '0b11011' will return 4:
+
+```python
+>>> 0b11011.bit_count()
+4
+~~~~exercism/note
+If you are working locally, `bit_count()` requires at least Python 3.10.
+The Exercism online editor currently supports all features through Python 3.11.
+~~~~ 
+
+
+## Octal
+
+[Octal][octal] is a base 8 numeral system.
+It uses the digits 0, 1, 2, 3, 4, 5, 6, and 7.
+
+In Python, we can represent octal numbers using the `0o` prefix.
+As with binary, Python automatically converts an octal representation to an `int`.
+
+```python
+# 0o123
+>>> 0o123
+83
+```
+
+As with binary, octal numbers **are ints** and support all integer operations.
+Prefixing a number with `0o` that is not in the octal system will raise a `SyntaxError`.
+
+ ### Converting to and from Octal Representation
+ 
+
+To convert an `int` into an octal representation, you can use the built-in [`oct()`][oct] function.
+This acts similarly to the `bin()` function, returning a string:
+
+```python
+>>> oct(83)
+'0o123'
+
+To convert an octal number to an integer, we can use the `int()` function, passing an octal string representation and the base (8) as arguments:
+
+```python
+>>> int("0o123", 8)
+83
+```
+
+As with binary, giving the wrong base will raise a `ValueError`.
+
+### Hexadecimal
+
+[Hexadecimal][hexadecimal] is a base 16 numeral system.
+It uses the digits 0 - 9 and the letters A, B, C, D, E, and F.
+A is 10, B is 11, C is 12, D is 13, E is 14, and F is 15.
+
+We can represent hexadecimal numbers in Python using the `0x` prefix.
+As with binary and octal, Python will automatically convert hexadecimal literals to `int`.
+
+```python
+# 0x123
+>>> 0x123
+291
+```
+
+As with binary and octal - hexidecimal literals **are ints**, and you can perform all integer operations.  
+Prefixing a non-hexidecimal number with `0x` will raise a `SyntaxError`.
+
+
+###  Converting to and from Hexadecimal Representation
+
+To convert an `int` into a hexadecimal representation, you can use the built-in [`hex()`][hex] function.
+This acts similarly to the `bin()` function, returning a string:
+
+```python
+>>> hex(291)
+'0x123'
+
+To convert a hexadecimal representation to an integer, we can use the `int()` function, passing a hexadecimal string with the base (16) as arguments:
+
+```python
+>>> int("0x123", 16)
+291
+```
+
+As with binary and octal, giving the wrong base will raise a `ValueError`.
+
+
+[binary]: https://en.wikipedia.org/wiki/Binary_number
+[bit_count]:  https://docs.python.org/3/library/stdtypes.html#int.bit_count
+[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length
+[bit_count]:  https://docs.python.org/3/library/stdtypes.html#int.bit_count
+[bit_length]: https://docs.python.org/3/library/stdtypes.html#int.bit_length
+[hexadecimal]: https://en.wikipedia.org/wiki/Hexadecimal
+[methods-int]: https://docs.python.org/3/library/stdtypes.html#additional-methods-on-integer-types
+[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system
+[octal]: https://en.wikipedia.org/wiki/Octal
diff --git a/concepts/binary-octal-hexadecimal/introduction.md b/concepts/binary-octal-hexadecimal/introduction.md
new file mode 100644
index 00000000..820aac33
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/introduction.md
@@ -0,0 +1,11 @@
+# binary, octal, hexadecimal
+
+Binary, octal, and hexadecimal (_also known as hex_) are different [numeral systems][numeral-systems] with different bases.
+Binary is base 2, octal is base 8, and hexadecimal is base 16.
+Normal integers are base 10 in python.
+Binary, octal, and hexadecimal literals are all considered `int` subtypes and Python automatically converts between them.
+This means that they can only represent zero, positive, and negative numbers that do not have a fractional or decimal part.
+Binary, octal, and hexidecimal numbers support all integer operations.
+However, division (_as with ints_) will return a `float`.
+
+[numeral-systems]: https://en.wikipedia.org/wiki/Numeral_system
diff --git a/concepts/binary-octal-hexadecimal/links.json b/concepts/binary-octal-hexadecimal/links.json
new file mode 100644
index 00000000..8826182c
--- /dev/null
+++ b/concepts/binary-octal-hexadecimal/links.json
@@ -0,0 +1,10 @@
+[
+  {
+    "url": "https://towardsdatascience.com/binary-hex-and-octal-in-python-20222488cee1",
+    "description": "Binary, octal, hex in python"
+  },
+  {
+    "url": "https://en.wikipedia.org/wiki/Numeral_system",
+    "description": "Numeral system"
+  }
+]
diff --git a/concepts/comparisons/about.md b/concepts/comparisons/about.md
index 568b6603..c2f5faaa 100644
--- a/concepts/comparisons/about.md
+++ b/concepts/comparisons/about.md
@@ -82,7 +82,7 @@ False
 Strings (`str`) are compared [_lexicographically_][lexographic order], using their individual Unicode code points (_the result of passing each code point in the `str` to the built-in function [`ord()`][ord], which returns an `int`_).
 If all code points in both strings match and are _**in the same order**_, the two strings are considered equal.
 This comparison is done in a 'pair-wise' fashion - first-to-first, second-to-second, etc.
-Unlike in Python 2.x, in Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
+In Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
 
 ```python
 >>> 'Python' > 'Rust'
diff --git a/concepts/dict-methods/about.md b/concepts/dict-methods/about.md
index e36fc708..d910d3e9 100644
--- a/concepts/dict-methods/about.md
+++ b/concepts/dict-methods/about.md
@@ -1,74 +1,75 @@
 # Dictionary Methods in Python
 
-A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a [hash table or hashmap][hashtable-wikipedia].
-In Python, it's considered a [mapping type][mapping-types-dict].
-`dicts` enable the retrieval of a value in constant time (on average), given the key.
-
-Compared to searching for a value within a list or array (_without knowing the index position_), a dictionary uses significantly more memory, but has very rapid retrieval.
-It's especially useful in scenarios where the collection of items is large and must be accessed/updated frequently.
-
-## Dictionary Methods
-
 The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries.
 Some were introduced in the concept for `dicts`.
-Here are a few more - along with some techniques for iterating through and manipulating `dicts`.
+Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries.
 
-To quickly populate a dictionary with various `keys` and default values, the _class method_ [`dict.fromkeys(iterable, <default value>)`][fromkeys] will iterate through the `keys` and create a new `dict`.  All `values` will be set to the `default` value provided.
+- `dict.setdefault()` automatically adds keys without throwing a KeyError.
+- `dict.fromkeys(iterable, <default value>)` creates a new `dict` from any number of iterables.
+- `.keys()`, `.values()`, and `.items()` provide convenient iterators.
+- `sorted(<dict>.items())`. can easily re-order entries in a `dict`.
+- `dict_one.update(<dict_two>)` updates one `dict` with overlapping values from another `dict`.
+- `dict | other_dict` and `dict |= other_dict` merges or updates two `dict`s via operators.
+- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` produce _reversed_ views.
+- `<dict>.popitem()` removes and returns a `key`, `value` pair.
 
-```python
->>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here')
->>> new_dict
-{'Grassy Green': 'fill in hex color here',
- 'Purple Mountains Majesty': 'fill in hex color here',
- 'Misty Mountain Pink': 'fill in hex color here'}
-```
 
-`dict.clear()` will removed all `key:value` pairs from the dictionary, leaving it empty and ready for new entries.
+## `setdefault()` for Error-Free Insertion
 
-```python
->>> pallette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
->>> pallette_II.clear()
->>> pallette_II
-{}
-```
+The dictionary concept previously covered that `.get(key, <default value>)` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`.
+This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present.
 
-`dict.get(key, <default return value>)` works similarly to `dict[key]` -- but it will return the `default` if the `key` is not in the dictionary.
-If no `default` is given, the method will return `None`.
+For a similarly "safe" (_without KeyError_) insertion operation, there is the `.setdefault(key, <default value>)` method.
+`setdefault(key, <default value>)` will return the `value` if the `key` is found in the dictionary.
+If the key is **not** found, it will _insert_ the (`key`, `default value`) pair and return the `default value` for use.
 
 ```python
 >>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
->>> palette_I['Factory Stone Purple']
-Traceback (most recent call last):
 
-  line 1, in <module>
-    palette_I['Factory Stone Purple']
+# Looking for the value associated with key "Rock Brown".The key does not exist, 
+# so it is added with the default value, and the value is returned.
+>>> palette.setdefault('Rock Brown', '#694605')
+'#694605'
 
-KeyError: 'Factory Stone Purple'
+# The (key, default value) pair has now been added to the dictionary.
+>>> palette_I
+{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'}
+```
 
->>> palette_I.get('Factory Stone Purple', 'That color was not found.')
-'That color was not found.'
+## `fromkeys()` to Populate a Dictionary from an Iterable
 
->>> palette_I.get('Factory Stone Purple', False)
-False
+To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, <default value>)`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`.
+All `values` will be set to the `default value` provided:
 
->>> palette_I.get('Factory Stone Purple')
-None
+```python
+>>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here')
+
+{'Grassy Green': 'fill in hex color here',
+ 'Purple Mountains Majesty': 'fill in hex color here',
+ 'Misty Mountain Pink': 'fill in hex color here'}
 ```
 
-`dict.popitem()`  removes & returns a single `key:value` pair from the `dict`.
-Pairs are returned in Last-in-First-out (LIFO) order.
-If the dictionary is empty, calling `.dict.popitem` will raise a `KeyError`.
+## Remove and Return a (key, value) Pair With `.popitem()`
+
+`.popitem()` removes & returns a single (`key`, `value`) pair from a dictionary.
+Pairs are returned in Last-in-First-out (`LIFO`) order.
+If the dictionary is empty, calling `popitem()` will raise a `KeyError`:
 
 ```python
->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_I = {'Grassy Green': '#9bc400', 
+                 'Purple Mountains Majesty': '#8076a3', 
+                 'Misty Mountain Pink': '#f9c5bd'}
 >>> palette_I.popitem()
 ('Misty Mountain Pink', '#f9c5bd')
+
 >>> palette_I.popitem()
 ('Purple Mountains Majesty', '#8076a3')
+
 >>> palette_I.popitem()
 ('Grassy Green', '#9bc400')
->>> palette_I.popitem()
 
+# All (key, value) pairs have been removed.
+>>> palette_I.popitem()
 Traceback (most recent call last):
 
   line 1, in <module>
@@ -77,25 +78,38 @@ Traceback (most recent call last):
 KeyError: 'popitem(): dictionary is empty'
 ```
 
-While `dict.clear()` and `dict.popitem()` are  _destructive_ actions, the  `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views].
-These views can be used for looping over `dict` content without altering it and are  _dynamic_ -- when underlying dictionary data changes, the associated view object will reflect the change.
+## Iterating Over Entries in a Dictionary Via Views
+
+The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary.
+
+These views can be used to easily loop over entries without altering them.
+Views are also _dynamic_ -- when underlying dictionary data changes, the associated `view object` will reflect the change:
 
 ```python
->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_I = {'Grassy Green': '#9bc400', 
+                 'Purple Mountains Majesty': '#8076a3', 
+                 'Misty Mountain Pink': '#f9c5bd'}
+
+# Using .keys() returns a list of keys.
 >>> palette_I.keys()
 dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'])
 
+# Using .values() returns a list of values.
 >>> palette_I.values()
 dict_values(['#9bc400', '#8076a3', '#f9c5bd'])
 
+# Using .items() returns a list of (key, value) tuples.
 >>> palette_I.items()
 dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')])
 
+# Views are dynamic.  Changing values in the dict 
+# changes all of the associated views.
 >>> palette_I['Purple Mountains Majesty'] = (128, 118, 163)
+>>> palette_I['Deep Red'] = '#932432'
+
 >>> palette_I.values()
-dict_values(['#9bc400', (128, 118, 163), '#f9c5bd'])
+dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432'])
 
->>> palette_I['Deep Red'] = '#932432'
 >>> palette_I.keys()
 dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red'])
 
@@ -103,32 +117,69 @@ dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'D
 dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')])
 ```
 
-`dict_one.update(<dict_two>)` can be used to _combine_ two dictionaries.
-This method will take the `key:value` pairs of `dict_two` and write them into `dict_one`.
+## More on `.keys()`, `.values()`, and `.items()`
+
+In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_),  iteration when using `.keys()`, `.values()`, or `.items()`.
+
+In Python 3.8+, views are also _reversible_.
+This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(<dict>.keys())`, `reversed(<dict>.values())`, or `reversed(<dict>.items())`:
 
 ```python
->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
->>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
->>> palette_I.update(palette_II)
->>> palette_I
+>>> palette_II = {'Factory Stone Purple': '#7c677f', 
+                  'Green Treeline': '#478559', 
+                  'Purple baseline': '#161748'}
+>>> for item in palette_II.items():
+...     print(item)
 ...
+('Factory Stone Purple', '#7c677f')
+('Green Treeline', '#478559')
+('Purple baseline', '#161748')
 
-{'Grassy Green': '#9bc400',
- 'Purple Mountains Majesty': '#8076a3',
- 'Misty Mountain Pink': '#f9c5bd',
- 'Factory Stone Purple': '#7c677f',
- 'Green Treeline': '#478559', 
- 'Purple baseline': '#161748'}
+>>> for item in reversed(palette_II.items()):
+...    print (item)
+...
+('Purple baseline', '#161748')
+('Green Treeline', '#478559')
+('Factory Stone Purple', '#7c677f')
 ```
 
-Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`.
+## Combine Dictionaries with `.update()`
+
+`<dict_one>.update(<dict_two>)` can be used to _combine_ two dictionaries.
+This method will take the (`key`,`value`) pairs of `<dict_two>` and write them into `<dict_one>`:
 
 ```python
->>> palette_I =   {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 
-                   'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
->>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163),
+>>> palette_I = {'Grassy Green': '#9bc400',
+                 'Purple Mountains Majesty': '#8076a3',
+                  'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_II = {'Factory Stone Purple': '#7c677f',
+                  'Green Treeline': '#478559',
+                  'Purple Baseline': '#161748'}
+
+>>> palette_I.update(palette_II)
+
+# Note that new items from palette_II are added.
+>>> palette_I
+{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'}
+```
+
+Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`:
+
+```python
+>>> palette_I =   {'Grassy Green': '#9bc400', 
+                   'Purple Mountains Majesty': '#8076a3', 
+                   'Misty Mountain Pink': '#f9c5bd', 
+                   'Factory Stone Purple': '#7c677f', 
+                   'Green Treeline': '#478559', 
+                   'Purple baseline': '#161748'}
+                   
+>>> palette_III = {'Grassy Green': (155, 196, 0), 
+                   'Purple Mountains Majesty': (128, 118, 163),
                    'Misty Mountain Pink': (249, 197, 189)}
 >>> palette_I.update(palette_III)
+
+# Overlapping values in palette_I are replaced with 
+# values from palette_III
 >>> palette_I
 {'Grassy Green': (155, 196, 0),
   'Purple Mountains Majesty': (128, 118, 163), 
@@ -137,14 +188,21 @@ Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be
   'Green Treeline': '#478559', 'Purple baseline': '#161748'}
 ```
 
-Python 3.9 introduces a different means of merging `dicts`:  the `union` operators.
-`dict | other_dict` will create a **new** `dict`, made up of the `key:value` pairs of `dict` and `other_dict`.
-When both dictionaries share keys, the `other_dict` values will take precedence.
-`dict |= other` will behave similar to `dict.update()`, but in this case, `other` can be either a `dict` or an iterable of `key:value` pairs.
+## Merge or Update Dictionaries Via the Union (`|`) Operators
+
+Python 3.9 introduces a different means of merging `dicts`: the `union` operators.
+`dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`.
+When both dictionaries share keys, `dict_two` values take precedence.
 
 ```python
->>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
->>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
+>>> palette_I = {'Grassy Green': '#9bc400', 
+                 'Purple Mountains Majesty': '#8076a3', 
+                 'Misty Mountain Pink': '#f9c5bd'}
+
+>>> palette_II = {'Factory Stone Purple': '#7c677f', 
+                  'Green Treeline': '#478559', 
+                  'Purple baseline': '#161748'}
+
 >>> new_dict = palette_I | palette_II
 >>> new_dict
 ...
@@ -154,44 +212,34 @@ When both dictionaries share keys, the `other_dict` values will take precedence.
  'Factory Stone Purple': '#7c677f',
  'Green Treeline': '#478559',
  'Purple baseline': '#161748'}
- 
- >>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163), 'Misty Mountain Pink': (249, 197, 189)}
- >>> new_dict |= palette_III
- >>> new_dict
- ...
- {'Grassy Green': (155, 196, 0),
- 'Purple Mountains Majesty': (128, 118, 163),
- 'Misty Mountain Pink': (249, 197, 189),
- 'Factory Stone Purple': '#7c677f',
- 'Green Treeline': '#478559',
- 'Purple baseline': '#161748'}
 ```
 
-## Tips and Tricks
-
-As of Python 3.6, `dicts` preserve the order in which items are inserted, allowing ordered iteration using `.items()`.  As of Python 3.8, `dict` _views_ are reversible, allowing keys, values or items to be iterated over reverse of insertion order by using `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())`.
+`dict_one |= other` behaves similar to `<dict_one>.update(<dict_two>)`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs:
 
 ```python
->>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
->>> for item in palette_II.items():
-...     print(item)
-...
-('Factory Stone Purple', '#7c677f')
-('Green Treeline', '#478559')
-('Purple baseline', '#161748')
-
->>> for item in reversed(palette_II.items()):
-...    print (item)
+>>> palette_III = {'Grassy Green': (155, 196, 0),
+                   'Purple Mountains Majesty': (128, 118, 163),
+                   'Misty Mountain Pink': (249, 197, 189)}
+>>> new_dict |= palette_III
+>>> new_dict
 ...
-('Purple baseline', '#161748')
-('Green Treeline', '#478559')
-('Factory Stone Purple', '#7c677f')
-
+{'Grassy Green': (155, 196, 0),
+'Purple Mountains Majesty': (128, 118, 163),
+'Misty Mountain Pink': (249, 197, 189),
+'Factory Stone Purple': '#7c677f',
+'Green Treeline': '#478559',
+'Purple baseline': '#161748'}
 ```
 
-While `dict` does not have a built-in sorting method, it is possible to sort a dictionary _view_ by keys or values using the built-in `sorted()` with `dict.items()`.  The sorted view can then be used to create a new, sorted dictionary.  Unless a _sort key_ is specified, the default sort is over dictionary keys.
+## Sorting a Dictionary
+
+Dictionaries do not have a built-in sorting method.
+However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`.
+The sorted view can then be used to create a new dictionary.
+Unless a _sort key_ is specified, the default sort is over dictionary `keys`.
 
 ```python
+# Default ordering for a dictionary is last in, first out (LIFO).
 >>> color_palette = {'Grassy Green': '#9bc400', 
                     'Purple Mountains Majesty': '#8076a3', 
                     'Misty Mountain Pink': '#f9c5bd', 
@@ -200,6 +248,7 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d
                     'Purple baseline': '#161748'}
  
  
+# The default sort order for a dictionary uses the keys.
 >>> sorted_palette = dict(sorted(color_palette.items()))
 >>> sorted_palette
 {'Factory Stone Purple': '#7c677f',
@@ -208,7 +257,10 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d
  'Misty Mountain Pink': '#f9c5bd',
  'Purple Mountains Majesty': '#8076a3',
  'Purple baseline': '#161748'}
- 
+
+
+# A sort key can be provided in the form
+# of an anonymous function (lambda). 
 >>> value_sorted_palette = dict(sorted(color_palette.items(), key=lambda color: color[1]))
 >>> value_sorted_palette
 {'Purple baseline': '#161748',
@@ -217,110 +269,93 @@ While `dict` does not have a built-in sorting method, it is possible to sort a d
  'Purple Mountains Majesty': '#8076a3',
  'Grassy Green': '#9bc400',
  'Misty Mountain Pink': '#f9c5bd'} 
-
 ```
 
-Swapping keys and values reliably in a dictionary takes a little more work, but can be accomplished via a loop using `dict.items()`.  But if the values stored in the `dict` are not unique, extra checks are required.  Both methods assume that `dict` keys and values are _hashable_.
-
-```python
+## Transposing a Dictionaries Keys and Values
 
+Swapping keys and values reliably in a dictionary takes a little work, but can be accomplished via a `loop` using `dict.items()` or in a dictionary comprehension.
+Safe swapping assumes that `dict` keys and values are both _hashable_.
 
+```python
 color_reference = {'Purple Mountains Majesty': '#8076a3',
                    'Misty Mountain Pink': '#f9c5bd',
                    'Factory Stone Purple': '#7c677f',
                    'Green Treeline': '#478559',
-                   'Purple baseline': '#161748',
-                   'Pink highlight': '#f95d9b',
-                   'Bluewater lowlight': '#39a0ca',
-                   'Bright Red': '#DE354C',
-                   'Deep Red': '#932432',
-                   'Pure Purple': '#3C1874',
-                   'Purple Tinged Grey': '#283747',
-                   'Cloud': '#F3F3F3'}
-
->>> reversed_color_reference = {}
+                   'Purple baseline': '#161748'}
+
+# Creating a new dictionary to hold the swapped entries.
+>>> swapped_color_reference = {}
+
+# Iterating through the dictionary, using values as keys.
 >>> for key, value in color_reference.items():
-...     reversed_color_reference[value] = key
+...     swapped_color_reference[value] = key
+
+>>> swapped_color_reference
+{'#8076a3': 'Purple Mountains Majesty',
+ '#f9c5bd': 'Misty Mountain Pink',
+ '#7c677f': 'Factory Stone Purple',
+ '#478559': 'Green Treeline',
+ '#161748': 'Purple baseline'}
 
->>> reversed_color_reference
+ 
+# A dictionary comprehension can also be used to swap entries.
+>>> swapped = {value: key for key, value in
+               color_reference.items()}
+>>> swapped
 {'#8076a3': 'Purple Mountains Majesty',
  '#f9c5bd': 'Misty Mountain Pink',
  '#7c677f': 'Factory Stone Purple',
  '#478559': 'Green Treeline',
- '#161748': 'Purple baseline',
- '#f95d9b': 'Pink highlight',
- '#39a0ca': 'Bluewater lowlight',
- '#DE354C': 'Bright Red',
- '#932432': 'Deep Red',
- '#3C1874': 'Pure Purple',
- '#283747': 'Purple Tinged Grey',
- '#F3F3F3': 'Cloud'}
-    
-
->>> extended_color_reference = {'#8076a3': 'Purple Mountains Majesty',(128, 118, 163): 'Purple Mountains Majesty',
-                                (21, 28, 0, 36): 'Purple Mountains Majesty','#f9c5bd': 'Misty Mountain Pink',
-                                (249, 197, 189): 'Misty Mountain Pink',(0, 21, 24, 2): 'Misty Mountain Pink',
-                                '#7c677f': 'Factory Stone Purple',(124, 103, 127): 'Factory Stone Purple',
-                                (2, 19, 0, 50): 'Factory Stone Purple','#478559': 'Green Treeline',
-                                (71, 133, 89): 'Green Treeline',(47, 0, 33, 48): 'Green Treeline',
-                                '#161748': 'Purple baseline',(22, 23, 72): 'Purple baseline',
-                                (69, 68, 0, 72): 'Purple baseline','#f95d9b': 'Pink highlight',
-                                (249, 93, 155): 'Pink highlight',(0, 63, 38, 2): 'Pink highlight',
-                                '#39a0ca': 'Bluewater lowlight',(57, 160, 202): 'Bluewater lowlight',
-                                (72, 21, 0, 21): 'Bluewater lowlight','#DE354C': 'Bright Red',
-                                (222, 53, 76): 'Bright Red',(0, 76, 66, 13): 'Bright Red',
-                                '#932432': 'Deep Red',(147, 36, 50): 'Deep Red',
-                                (0, 76, 66, 42): 'Deep Red','#3C1874': 'Pure Purple',
-                                (60, 24, 116): 'Pure Purple',(48, 79, 0, 55): 'Pure Purple',
-                                '#283747': 'Purple Tinged Grey',(40, 55, 71): 'Purple Tinged Grey',
-                                (44, 23, 0, 72): 'Purple Tinged Grey','#F3F3F3': 'Cloud',
-                                (243, 243, 243): 'Cloud',(0, 0, 0, 5): 'Cloud'}
+ '#161748': 'Purple baseline'}
+```
 
+If the values stored in the `dict` are not unique, extra checks become necessary before key and value swapping can happen:
+
+```python
+# Things become more complicated if there are duplicates in 
+# potential key values.This dict is arranged by hex, RGB, and HSL
+# keys, but values repeat.
+>>> extended_colors = {'#8076a3': 'Purple Mountains Majesty',
+                       (128, 118, 163): 'Purple Mountains Majesty',
+                       (21, 28, 0, 36): 'Purple Mountains Majesty',
+                       '#f9c5bd': 'Misty Mountain Pink',
+                       (249, 197, 189): 'Misty Mountain Pink',
+                       (0, 21, 24, 2): 'Misty Mountain Pink',
+                       '#7c677f': 'Factory Stone Purple',
+                       (124, 103, 127): 'Factory Stone Purple',
+                       (2, 19, 0, 50): 'Factory Stone Purple',
+                       '#478559': 'Green Treeline',
+                       (71, 133, 89): 'Green Treeline',
+                       (47, 0, 33, 48): 'Green Treeline'}
+                                
+# New empty dictionary for holding swapped entries.
 >>> consolidated_colors = {}
+
+# Iterating over (key, value) pairs using .items()
 >>> for key, value in extended_color_reference.items():
-...    if value in consolidated_colors:
+...    if value in consolidated_colors: #Check if key has already been created.
 ...        consolidated_colors[value].append(key)
 ...    else:
-...        consolidated_colors[value] = [key]
+...        consolidated_colors[value] = [key]  #Create a value list with the former key in it.
 
 >>> consolidated_colors
 {'Purple Mountains Majesty': ['#8076a3', (128, 118, 163), (21, 28, 0, 36)],
  'Misty Mountain Pink': ['#f9c5bd', (249, 197, 189), (0, 21, 24, 2)],
  'Factory Stone Purple': ['#7c677f', (124, 103, 127), (2, 19, 0, 50)],
- 'Green Treeline': ['#478559', (71, 133, 89), (47, 0, 33, 48)],
- 'Purple baseline': ['#161748', (22, 23, 72), (69, 68, 0, 72)],
- 'Pink highlight': ['#f95d9b', (249, 93, 155), (0, 63, 38, 2)],
- 'Bluewater lowlight': ['#39a0ca', (57, 160, 202), (72, 21, 0, 21)],
- 'Bright Red': ['#DE354C', (222, 53, 76), (0, 76, 66, 13)],
- 'Deep Red': ['#932432', (147, 36, 50), (0, 76, 66, 42)],
- 'Pure Purple': ['#3C1874', (60, 24, 116), (48, 79, 0, 55)],
- 'Purple Tinged Grey': ['#283747', (40, 55, 71), (44, 23, 0, 72)],
- 'Cloud': ['#F3F3F3', (243, 243, 243), (0, 0, 0, 5)]}
-        
+ 'Green Treeline': ['#478559', (71, 133, 89), (47, 0, 33, 48)]}  
 ```
 
 For a detailed explanation of dictionaries and methods for working with them, the [official tutorial][dicts-docs] and the [official library reference][mapping-types-dict] are excellent starting places.
-For more on sorting, see the [Sorting HOW TO][sorting-howto] in the python docs.
- [Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries.
 
-## Extending Dictionaries: The collections module
+[Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries.
 
-The [`collections`][collections-docs] module adds more functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`).
-A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them a `dict` with the items as keys and their counts as values.
-There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary.
-Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign the value.
+For more on sorting, see the [Sorting HOW TO][sorting-howto] in the Python docs.
 
-[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
-[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
-[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
-[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
-[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
-[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
-[collections-docs]: https://docs.python.org/3/library/collections.html
-[counter-dicts]: https://docs.python.org/3/library/collections.html#collections.Counter
-[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict
-[default-dicts]: https://docs.python.org/2/library/collections.html#collections.defaultdict
-[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views
 [dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict
+[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views
+[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
 [fi-dict-guide]: https://blog.finxter.com/python-dictionary
+[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
+[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
 [sorting-howto]: https://docs.python.org/3/howto/sorting.html
diff --git a/concepts/dict-methods/introduction.md b/concepts/dict-methods/introduction.md
index 52868299..c15fbc11 100644
--- a/concepts/dict-methods/introduction.md
+++ b/concepts/dict-methods/introduction.md
@@ -1,17 +1,16 @@
 # Dictionary Methods in Python
 
-A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a [hash table or hashmap][hashtable-wikipedia].
-In Python, it's considered a [mapping type][mapping-types-dict].
-`dicts` enable the retrieval of a value in constant time (on average), given the key.
+The `dict` class in Python provides many useful [methods][dict-methods], some of which are introduced in the concept exercise for dictionaries.
 
-Compared to searching for a value within a list or array (_without knowing the index position_), a dictionary uses significantly more memory, but has very rapid retrieval.
-It's especially useful in scenarios where the collection of items is large and must be accessed/updated frequently.
+This concept tackles a few more:
 
-The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries.
-Some are introduced in the concept exercise for `dicts`.
-This concept tackles a few more - along with some techniques for iterating through and manipulating `dicts`.
+- `dict.setdefault()` automatically adds keys without throwing a `KeyError`.
+- `dict.fromkeys(iterable, <default value>)` creates a new `dict` from any number of iterables.
+- `.keys()`, `.values()`, and `.items()` provide convenient iterators.
+- `sorted(<dict>.items())`. can easily re-order entries in a `dict`.
+- `dict_one.update(<dict_two>)` updates one `dict` with overlapping values from another `dict`.
+- `dict | other_dict` and `dict |= other_dict` merges or updates two `dict`s via operators.
+- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` produce _reversed_ views.
+- `<dict>.popitem()` removes and returns a `key`, `value` pair.
 
-[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
-[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
-[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
 [dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict
diff --git a/concepts/dicts/about.md b/concepts/dicts/about.md
index fb43067e..c34160b2 100644
--- a/concepts/dicts/about.md
+++ b/concepts/dicts/about.md
@@ -1,55 +1,292 @@
 # About
 
+A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array].
+Dictionaries are Python's only built-in [mapping type][mapping-types-dict].
 
-A dictionary (`dict`) is a [mapping type][mapping-types-dict] data structure that associates [hashable][term-hashable] `keys` to `values` -- known in other programming languages as a resizable [hash table or hashmap][hashtable-wikipedia].
-  `Keys` can include `numbers`, `str`, `tuples` (of _immutable_ values), or `frozensets`, but must be hashable and unique across the dictionary.
-  `keys` are _immutable_ - once added to a `dict`, they can only be removed, they cannot be updated.
-  `values` can be of any or multiple data type(s) or structures, including other dictionaries, built-in types, custom types, or even objects like functions or classes.
-   `values` associated with any `key` are _mutable_, and can be replaced, updated or altered as long as the `key` entry exists.
-  Dictionaries enable the retrieval of a `value` in (on average) constant O(1) time, given the `key`.
+`Keys` must be hashable and unique across the dictionary.
+Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values).
+They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s.
+As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted.
 
-  Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more space in memory, but has significantly more rapid retrieval.
- Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and/or updated frequently.
+`values` can be of any data type or structure.
+ Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures.
 
-## Dictionary creation
+Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_).
+Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval.
 
-A simple `dict` can be declared using the literal form `{<key_1>: <value_1>, <key_2>: <value_2>}`:
+Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently.
 
- ```python
 
+## Dictionary Construction
 
+Dictionaries can be created in many different ways, including:
+ - Using the [`fromkeys()`][fromkeys] classmethod
+ - Creating [dictionary comprehensions][dict-comprehensions]
+ - Merging two dictionaries via unpacking (`**`)
+ - Merging dictionaries via the `|` (_update_) operator
+ - Using a loop to iteratively add entries to a previously created empty `dict`.
 
+The two most straightforward methods are the dictionary _constructor_ and the dictionary _literal_.
 
+### The Dictionary Constructor
+
+`dict()` (_the constructor for the `dict` class_) can be used with any iterable of `key`, `value` pairs.
+ It can also be called with a series of `<name>=<value>` _arguments_:
+
+```python
+# Passing a list of key,value tuples.
+>>> wombat = dict([('name', 'Wombat'),('speed', 23),
+                   ('land_animal', True)])
+{'name': 'Wombat', 'speed': 23, 'land_animal': True}
+
+
+# Using key=value arguments.
+>>> bear = dict(name="Black Bear", speed=40, land_animal=True)
+{'name': 'Black Bear', 'speed': 40, 'land_animal': True}
+```
+
+The [documentation on `dicts`][dicts-docs] outlines additional variations and options in constructor use.
+
+
+### Dictionary Literals
+
+A dictionary can also be directly entered as a _dictionary literal_, using curly brackets (`{}`) enclosing `key : value` pairs.
+Entries that are enclosed in the `{}` can also appear on separate lines:
+
+```python
+>>> whale = {"name": "Blue Whale", 
+             "speed": 35, 
+             "land_animal": False}
+{'name': 'Blue Whale', 'speed': 35, 'land_animal': False}
+
+>>> wombat = {'name': 'Wombat',
+              'speed': 23,
+              'land_animal': True,
+              'color': 'Brindle'}
+
+>>> wombat
+{'name': 'Wombat', 'speed': 23, 'land_animal': True, 'color': 'Brindle'}
+```
+
+### Nested Dictionaries
+
+Dictionaries can be arbitrarily nested:
+
+```python
+animals = {
+            "Real" : {
+                "Winged" : {
+                            "Sparrow" : {'name': 'sparrow','speed': 12, 'land_animal': True},
+                            "Kestrel" : {'name': 'kestrel', 'speed': 15, 'land_animal': True}
+                           },
+                "Legged" : {
+                            "Wombat" : {'name': 'Wombat', 'speed': 23, 'land_animal': True},
+                            "Black Bear": {'name': 'Black Bear', 'speed': 40, 'land_animal': True},
+                            "Polecat" : {'name': 'Polecat', 'speed': 15, 'land_animal': True}
+                           },
+                "Other" :  {
+                            "Whale" : {'name': 'Blue Whale', 'speed': 35, 'land_animal': False},
+                            "Orca" : {'name': 'Orca', 'speed': 45, 'land_animal': False},
+                            "Snake" : {'name': 'Python', 'speed': 25, 'land_animal': True}
+                            }
+                },
+        
+        "Imaginary": {
+                "Winged" : {
+                            "Dragon" : {'name': 'Fire Dragon','speed': 100, 'land_animal': True},
+                            "Phoenix" : {'name': 'Phoenix', 'speed': 1500, 'land_animal': True}
+                            },
+                "Legged" : {
+                            "Sphinx" : {'name': 'Sphinx','speed': 10, 'land_animal': True},
+                            "Minotaur" : {'name': 'Minotaur', 'speed': 5, 'land_animal': True}
+                            },
+                "Other" :  {}
+                }
+       }
+```
+
+## Accessing Values in a `dict`
+
+You can access a `value` in a dictionary using a _key_ in square brackets.
+If a key does not exist, a `KeyError` is thrown:
+
+```python
+>>> bear["speed"]
+40
+
+>>> bear["color"]
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+KeyError: 'color'
+```
+
+Accessing an entry via the `get(<key>, <default value>)` method can avoid the `KeyError`:
+
+```python
+>>> bear.get("color", 'not found')
+'not found'
+```
+
+### Accessing Nested Dictionary Entries
+
+To access entries in nested dictionaries, use successive brackets.
+If a given key is missing, the usual KeyError will be thrown:
+
+```python
+# Using the animals nested dictionary.
+>>> animals["Real"]["winged"]["Kestrel"]["speed"]
+15
+
+>>> animals["Imaginary"]["winged"]["Kestrel"]["speed"]
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+KeyError: 'Kestrel'
+```
+
+To avoid the `KeyError`, `.get()` can be used, but the calls to `.get()` must be _chained_:
+
+```python
+# Using the animals nested dictionary.
+# Note the use of parenthesis to enable placing the 
+# .get() calls on separate lines.
+>>> (animals.get("Imaginary", {})
+            .get("Legged", {})
+            .get("Sphinx", {})
+            .get("Color", "I have no idea!"))
+'I have no idea!'
+```
+
+## Changing or Adding Dictionary Values
+
+You can change an entry `value` by assigning to its _key_:
+
+```python
+# Assigning the value "Grizzly Bear" to the name key.
+>>> bear["name"] = "Grizzly Bear"
+{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True}
+
+>>> whale["speed"] = 25
+{'name': 'Blue Whale', 'speed': 25, 'land_animal': False}
+```
+
+New `key`:`value` pairs can be _added_ in the same fashion:
+
+```python
+# Adding an new "color" key with a new "tawney" value.
+>>> bear["color"] = 'tawney'
+{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'}
+
+>>> whale["blowholes"] = 1
+{'name': 'Blue Whale', 'speed': 25, 'land_animal': False, 'blowholes': 1}
+```
+
+
+## Removing (Pop-ing and del) Dictionary Entries
+
+You can use the `.pop(<key>)` method to delete a dictionary entry.
+`.pop()` removes the (`key`, `value`) pair and returns the `value` for use.
+Like `.get()`, `.pop(<key>)` accepts second argument (_`dict.pop(<key>, <default value>)`_) that will be returned if the `key` is not found.
+This prevents a `KeyError` being raised:
+
+```python
+# Using .pop() removes both the key and value, returning the value.
+>>> bear.pop("name")
+'Grizzly Bear'
+
+
+# The "name" key is now removed from the dictionary.
+# Attempting .pop() a second time will throw a KeyError.
+>>> bear.pop("name")
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+KeyError: 'name'
+
+
+# Using a default argument with .pop() will 
+# prevent a KeyError from a missing key.
+>>> bear.pop("name", "Unknown")
+'Unknown'
 ```
 
- The dictionary  constructor `dict(<key_1>=<value_1>, <key_2>=<value_2>)`, but there are many more ways of creating and initializing dictionaries including the use of a _dict comprehension_ or passing additional constructor parameters as illustrated in the [Python docs][mapping-types-dict].
+You can also use the `del` statement to remove a single or multiple entries.
+A `KeError` is raised if the entry to be removed is not found in the dictionary:
+
+```python
+>>> wombat = {'name': 'Wombat',
+              'speed': 23,
+              'land_animal': True,
+              'color': 'Brindle',
+              'talent': 'Singing',
+              'size': 'small'}
+
+# Remove a single entry from the dictionary.
+>>> del wombat["color"]
+>>> wombat
+{'name': 'Wombat', 'speed': 23, 'land_animal': True, 'talent': 'Singing', 'size': 'small'}
+
+
+# Remove multiple entries from the dictionary.
+>>> del wombat["talent"], wombat["size"]
+>>> wombat
+{'name': 'Wombat', 'speed': 23, 'land_animal': True}
 
 
+# Attempting a deletion of a non-existent key raises a KeyError
+>>> del wombat["number_of_legs"]
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+KeyError: 'number_of_legs'
+```
 
-Inserting a new `key`:`value` pair can be done with `dict[key] = value` and the value can be retrieved by using `retrieved_value = dict[key]`.
+## Looping Through/Iterating over a Dictionary
 
-## Methods
+Looping through a dictionary using `for item in dict` or `while item` will iterate over the _keys_ by default.
+You can access _values_ within the same loop by using _square brackets_:
 
-`dicts` implement various methods to allow easy initialization, updating and viewing.
+```python
+>>> for key in bear:
+>>>     print((key, bear[key])) #this prints a tuple of (key, value)
+('name', 'Black Bear')
+('speed', 40)
+('land_animal', True)
+```
 
-Some useful `dict` methods:
+You can also use the `.items()` method, which returns (`key`, `value`) tuples:
 
-- Retrieve a value "safely" from a dictionary by using the `.get(key, [default])` method. `.get(key, [default])` returns the value for the key **or** the _default value_ if the key is not found, instead of raising a `KeyError`. This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for key will be present.
-- Retrieve a value "safely" or insert a default _value_ if the key is not found using the `.setdefault(key, [default])` method. `setdefault(key, [default])` will insert the default value in the dictionary **only** if the key is not found, then it will retrieve either the **newly inserted** default value if the key was not found or the **unchanged** existing value if the key was found.
-- Return various _iterable_ views of your `dict` with `.keys()`, `.values()`, `.items()` (_an iterable of (key, value) `tuples`_).
+```python
+# dict.items() forms (key, value tuples) that can be 
+# unpacked and iterated over.
+>>> for key, value in whale.items():
+>>>     print(key, ":", value)
+name : Blue Whale
+speed : 25
+land_animal : False
+blowholes : 1
+```
+
+Likewise, `.keys()` will return the `keys` and `.values()` will return the `values`.
 
 For a detailed explanation of dictionaries in Python, the [official documentation][dicts-docs] is an excellent starting place, or you can also check out the [W3-Schools][how-to-dicts] tutorial.
 
-## Extending Dictionaries: The collections module
 
-The [`collections`][collections-docs] module adds more functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`). A popular `dict`-oriented member of this module is the [`Counter`][counter-dicts], which automatically counts items and returns them a `dict` with the items as keys and their counts as values. There is also the [`OrderedDict`][ordered-dicts-docs], which has methods specialized for re-arranging the order of a dictionary. Finally, there is the [`defaultdict`][default-dicts], a subclass of the built-in `dict` module that, based on a factory method, sets a default value if a key is not found when trying to retrieve or assign the value.
+## Extending Dictionary Functionality: The Collections Module
 
-[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
-[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
-[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
-[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
-[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+The [`collections`][collections-docs] module adds specialized functionality to Python's standard collection-based datatypes (`dictionary`, `set`, `list`, `tuple`).
+Three of the most useful dictionary-based classes are:
+
+- [`Counter`][counter-dicts] automatically counts items and returns them in a `dict` with the items as keys and their counts as values.
+- [`OrderedDict`][ordered-dicts-docs], has methods specialized for arranging the order of dictionary entries.
+- [`defaultdict`][default-dicts] uses a factory method to set a default value if a `key` is not found when trying to retrieve or assign to a dictionary entry.
+
+[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain.
 [collections-docs]: https://docs.python.org/3/library/collections.html
 [counter-dicts]: https://docs.python.org/3/library/collections.html#collections.Counter
-[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict
 [default-dicts]: https://docs.python.org/2/library/collections.html#collections.defaultdict
+[dict-comprehensions]: https://www.learnbyexample.org/python-dictionary-comprehension/
+[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
+[fromkeys]: https://www.w3schools.com/python/ref_dictionary_fromkeys.asp
+[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
+[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+[ordered-dicts-docs]: https://docs.python.org/3/library/collections.html#collections.OrderedDict
+[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
diff --git a/concepts/dicts/introduction.md b/concepts/dicts/introduction.md
index 9a887ccf..5c8a7724 100644
--- a/concepts/dicts/introduction.md
+++ b/concepts/dicts/introduction.md
@@ -1,15 +1,24 @@
 # Introduction
 
+A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array].
+Dictionaries are Python's only built-in [mapping type][mapping-types-dict].
 
-A dictionary (`dict`) is a [mapping type][mapping-types-dict] data structure that associates [hashable][term-hashable] `keys` to `values` -- known in other programming languages as a resizable [hash table or hashmap][hashtable-wikipedia].
-  `Keys` can include `numbers`, `str`, `tuples` (of _immutable_ values), or `frozensets`, but must be hashable and unique across the dictionary.
-  `values` can be of any or multiple data type(s) or structures, including other dictionaries, built-in types, custom types, or even objects like functions or classes.
-  Dictionaries enable the retrieval of a `value` in (on average) constant O(1) time, given the `key`.
 
-  Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more space in memory, but has significantly more rapid retrieval.
- Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and/or updated frequently.
+`Keys` must be hashable and unique across the dictionary.
+Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values).
+They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s.
+As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted.
 
+`values` can be of any data type or structure.
+ Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures.
 
+Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_).
+Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval.
+
+Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently.
+
+
+[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain.
 [hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
 [mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
 [term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
diff --git a/concepts/sets/about.md b/concepts/sets/about.md
index dd567f07..5f272402 100644
--- a/concepts/sets/about.md
+++ b/concepts/sets/about.md
@@ -12,7 +12,7 @@ _Unlike_ sequence types (_`string`, `list` & `tuple`_), `sets` are **neither ord
 They're also used for fast membership testing, finding supersets & subsets of items, and performing "set math" (_calculating union, intersection, difference & symmetric difference between groups of items._).
 
 Checking membership in a `set` has only O(1) time complexity versus checking for membership in a `list` or `string`, which has worst-case O(n) time complexity.
-Operations such as `<set>.union()`, `<set>.intersection()`, or `<set>.diference()` have an average O(n) time complexity.
+Operations such as `<set>.union()`, `<set>.intersection()`, or `<set>.difference()` have an average O(n) time complexity.
 
 ## Construction
 
@@ -52,7 +52,7 @@ set()
 # The list is unpacked and each distinct element is added.
 >>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13])
 >>> multiple_elements_from_set
-{2, 3, 5, 7, 11}
+{2, 3, 5, 7, 11, 13}
 ```
 
 Results when using a set constructor with a string or dictionary may be surprising:
diff --git a/concepts/string-formatting/about.md b/concepts/string-formatting/about.md
index 07306a8d..f3b2756b 100644
--- a/concepts/string-formatting/about.md
+++ b/concepts/string-formatting/about.md
@@ -2,18 +2,18 @@
 
 ## String Formatting in Python
 
+String formatting is the process of converting values to strings and inserting them into a string template.
 The [Zen of Python][zen-of-python] asserts there should be "one _obvious_ way to do something in Python".
-But when it comes to string formatting, things are a little .... _less zen_.
+But when it comes to string formatting, things are a little ... _less zen_.
 It can be surprising to find out that there are **four** main ways to perform string formatting in Python - each for a different scenario.
 Some of this is due to Python's long history and some of it is due to considerations like internationalization or input sanitation.
 We will start with the most recent additions to the string formatting toolbox and work our way backward to "old style" or "printf() style" string formatting.
 
+## literal string interpolation: The `f-string`
 
-## literal string interpolation:  The `f-string`
-
- Introduced in [Python 3.6][pep-0498], [`f-strings`][f-string] (_short for "formatted-strings"_) or [literal string interpolation][string interpolation] are a way of quickly and efficiently evaluating and formatting expressions and strings to a `str` type using the `f` (or `F`) prefix before the brackets (_like so `f'{object}'`_).
- They can be used with all enclosing string types as: single quote `'`, double quote `"` and with multi-lines and escaping triple quotes `'''` or `"""`.
- Any variables, expressions, or other types placed inside the `{}` are first evaluated, then converted to a `str`, then concatenated with any `str` outside the curly braces.
+Introduced in [Python 3.6][pep-0498], [`f-strings`][f-string] (_short for "formatted-strings"_) or [literal string interpolation][string interpolation] are a way of quickly and efficiently evaluating and formatting expressions and strings to a `str` type using the `f` (or `F`) prefix before the brackets (_like so `f'{object}'`_).
+They can be used with all enclosing string types as: single-line `'` or `"` and with multi-lines `'''` or `"""`.
+Any variables, expressions, or other types placed inside the `{}` are first evaluated, then converted to a `str`, then concatenated with any `str` outside the curly braces.
 
 In this example, we insert two variable values in the sentence: one `str` and one `float`:
 
@@ -23,8 +23,8 @@ In this example, we insert two variable values in the sentence: one `str` and on
 ...
 # The f-string, using the two values.
 # The .2f format code truncates so the value displays as 0.12.
->>> print(f'An {name} is approximately {value:.2f}.')
-'An eighth is approximately 0.12.' 
+>>> f'An {name} is approximately {value:.2f}.'
+'An eighth is approximately 0.12.'
 ```
 
 The expressions evaluated can be almost anything.
@@ -37,16 +37,16 @@ Some examples:
 >>> waves = {'water': 1, 'light': 3, 'sound': 5}
 
 # Using the name waves in an f-string.
->>> print(f'"A dict can be represented with f-string: {waves}."')
+>>> f'"A dict can be represented with f-string: {waves}."'
 '"A dict can be represented with f-string: {\'water\': 1, \'light\': 3, \'sound\': 5}."'
 
 # Here, we pull a value from the dictionary by using the key
->>> print(f'Tenfold the value of "light" is {waves["light"]*10}.')
+>>> f'Tenfold the value of "light" is {waves["light"] * 10}.'
 'Tenfold the value of "light" is 30.'
 ```
 
 Replacement fields (_the `{}` in the f-string_) support output control mechanisms such as width, alignment, precision.
- This is the same [format specification mini-language][format-mini-language] that is used by the `str.format()` method.
+This specification is started in the [format specification mini-language][format-mini-language].
 
 A more complex example of an `f-string` that includes output control:
 
@@ -61,24 +61,21 @@ A more complex example of an `f-string` that includes output control:
 
 # This example includes a function, str, a nested f-string, an arithmetic expression, 
 # precision formatting, bracket escaping and object formatting.
->>> message = f'"Have a {"NICE".lower()} day, I will {verb} you after {f"{30e8*111_000:6.{precision}e}"} light-years."{{{the_end}}}'
-...
->>> print(message)
+>>> f'"Have a {"NICE".lower()} day, I will {verb} you after {f"{30e8 * 111_000:6.{precision}e}"} light-years."{{{the_end}}}'
 '"Have a nice day, I will meet you after 3.330e+14 light-years."{[\'end\', \'of\', \'transmission\']}'
-
 ```
 
 There are a few limitations to be aware of.
-`f-string` expressions cannot be empty, they cannot contain comments, and for Python versions earlier than Python 3.7, they cannot contain `await` or `async for` clauses:
+`f-string` expressions cannot be empty, they cannot contain comments.
 
 ```python
->>> print(f"An empty expression will error: {}")
+>>> f"An empty expression will error: {}"
 SyntaxError: f-string: empty expression not allowed
 
 >>> word = 'word'
->>> print(f"""A comment in a triple quoted f-string will error: {
+>>> f"""A comment in a triple quoted f-string will error: {
     word # I chose a nice variable
-}""")
+}"""
 SyntaxError: f-string expression part cannot include '#'
 ```
 
@@ -92,17 +89,16 @@ Also keep in mind that using expressions inside the `f-string` brackets `{}` is
 ## The `str.format()` Method
 
 The [`str.format()`][str-format] method replaces placeholders within the string with values fed as arguments to the function.
- The placeholders are identified with named (`{price}`), numbered (`{0}` or indexed) or even empty (_positional_) placeholders `{}`.
+The placeholders are identified with named (`{price}`), numbered (`{0}` or indexed) or even empty (_positional_) placeholders `{}`.
 For example:
 
 ```python
 # A named placeholder and a positional placeholder.
->>> print('My text: {placeholder_1} and {}.'.format(12, placeholder_1='named placeholder'))
-...
+>>> 'My text: {placeholder_1} and {}.'.format(12, placeholder_1='named placeholder')
 'My text: named placeholder and 12.'
 ```
 
-As with `f-strings`, Pythons  `str.format()` supports a whole range of [mini language format specifier][format-mini-language] that can be used to align text, convert, etc.
+As with `f-strings`, Pythons `str.format()` supports a whole range of [mini language format specifier][format-mini-language] that can be used to align text, convert, etc.
 
 The complete formatting specifier pattern is `{[<name>][!<conversion>][:<format_specifier>]}`:
 
@@ -115,25 +111,24 @@ Example of conversions for a diacritical letter:
 
 ```python
 # Fills in the object at index zero, converted to a string.
->>> print('An e with an umlaut: {0!s}'.format('ë'))
-An e with an umlaut: ë
-...
+>>> 'An e with an umlaut: {0!s}'.format('ë')
+'An e with an umlaut: ë'
+
 
 # Fills in the object at index zero, converted to a repr.
->>> print('An e with an umlaut object representation: {0!r}'.format('ë'))
-An e with an umlaut object representation: 'ë'
+>>> 'An e with an umlaut object representation: {0!r}'.format('ë')
+"An e with an umlaut object representation: 'ë'"
 
 ...
 
 # Fills in the object at index zero, converted to ascii
->>> print('An e with an umlaut converted into ascii: {0!a}'.format('ë'))
-An e with an umlaut converted into ascii: '\xeb'
+>>> 'An e with an umlaut converted into ascii: {0!a}'.format('ë')
+"An e with an umlaut converted into ascii: '\xeb'"
 
-...
 
 # Fills in the object in the first position.
 # Then fills in the object in the second position formatted as a repr
->>> print('She said her name is not {} but {!r}.'.format('Chloe', 'Zoë'))
+>>> 'She said her name is not {} but {!r}.'.format('Chloe', 'Zoë')
 "She said her name is not Chloe but 'Zoë'."
 ```
 
@@ -142,31 +137,28 @@ Example of using format specifiers:
 ```python
 # Formats the object at index 0 as a decimal with zero places, 
 # then as a right-aligned binary number in an 8 character wide field.
->>> print("The number {0:d} has a representation in binary: '{0: >8b}'.".format(42))
-The number 42 has a representation in binary: '  101010'.
+>>> "The number {0:d} has a representation in binary: '{0: >8b}'.".format(42)
+"The number 42 has a representation in binary: '  101010'."
 ```
 
 More examples are shown at the end of [this documentation][summary-string-format].
 
-
 ## `%` Formatting, or `printf()` Style Formatting
 
 Use of the `%` operator for formatting is the oldest method of string formatting in Python.
 It comes from the C language and allows the use of positional arguments to build a `str`.
 
 This method has been superseded by both `f-strings` and `str.format()`, which is why the nickname for `%` formatting is _'Old Style'_.
-It can be still found in python 2 and/or legacy code.
+It can be still found in Python 2 and/or legacy code.
 While using this method will work in Python 3.x, `%` formatting is usually avoided because it can be error-prone, is less efficient, has fewer options available, and any placeholder-argument mismatch can raise an exception.
- Using the `%` operator is similar to [`printf()`][printf-style-docs], so it is also sometimes called _printf formatting_.
-
+Using the `%` operator is similar to [`printf()`][printf-style-docs], so it is also sometimes called _printf formatting_.
 
 ```python
 # Assigning a variable.
->> name = "Anna-conda"
+>>> name = "Anna-conda"
 
 # Building a string using %
->> print("The snake's name is %s." % name)
-...
+>>> "The snake's name is %s." % name
 "The snake's name is Anna-conda."
 ```
 
@@ -179,33 +171,30 @@ If you want to add multiple variables to a string, you need to supply a [tuple][
 >>> fruit = "grapes"
 
 # Building a string using %
->>> print("Surprisingly, %ss favorite snack was %s." %(name, fruit))
-Surprisingly, Billy the Kids favorite snack was grapes.
+>>> "Surprisingly, %ss favorite snack was %s." %(name, fruit)
+"Surprisingly, Billy the Kids favorite snack was grapes."
 ```
 
-
 ## Template Strings
 
 [`string.Template()`][string.Template()] is a class from the `string` module (_as opposed to the built-in `str` type_), which is part of the Python standard library, but has to be imported for use.
 Template strings support `$`-based substitution and are much simpler and less capable than the other options mentioned here, but can be very useful for when complicated internationalization is needed, or outside inputs need to be sanitized.
 
-
 ```python
->> from string import Template
+>>> from string import Template
 
->>> snake_name = "Anna-Conda"
+>>> name = "Anna-Conda"
 
 # Creating a Template() with placeholder text
->> template_string = Template("The snake called `$snake_name` has escaped!")
+>>> template_string = Template("The snake called `$snake_name` has escaped!")
 
 # Calling .substitute() to replace the placeholder with a value.
->> template_string.substitute(snake_name=name)
+>>> template_string.substitute(snake_name=name)
 'The snake called `Anna-Conda` has escaped!'
 ```
 
 More information about `Template` string can be found in the Python [documentation][template-string].
 
-
 ## How Do You Choose which Formatting Method to Use?
 
 With all these options and mini-languages, how do you decide what to reach for when formatting Python strings?
diff --git a/config.json b/config.json
index 038123f6..f386ef39 100644
--- a/config.json
+++ b/config.json
@@ -144,6 +144,14 @@
         "prerequisites": ["loops", "lists", "tuples"],
         "status": "beta"
       },
+      {
+        "slug": "mecha-munch-management",
+        "name": "Mecha Munch Management",
+        "uuid": "5ac0c40c-4038-47b8-945b-8480e4a3f44c",
+        "concepts": ["dict-methods"],
+        "prerequisites": ["dicts"],
+        "status": "wip"
+      },
       {
         "slug": "locomotive-engineer",
         "name": "Locomotive Engineer",
@@ -970,6 +978,21 @@
         ],
         "difficulty": 3
       },
+      {
+        "slug": "resistor-color-expert",
+        "name": "Resistor Color Expert",
+        "uuid": "8a738365-0efa-444f-9466-a757ddaddcdb",
+        "practices": ["list-methods"],
+        "prerequisites": [
+          "basics",
+          "bools",
+          "lists",
+          "numbers",
+          "strings",
+          "comparisons"
+        ],
+        "difficulty": 3
+      },
       {
         "slug": "matrix",
         "name": "Matrix",
@@ -2235,6 +2258,11 @@
       "slug": "binary-data",
       "name": "Binary Data"
     },
+    {
+      "uuid": "78dbf248-a1e5-48cb-ba53-def4d92bf2a8",
+      "slug": "binary-octal-hexadecimal",
+      "name": "Binary, Octal, and Hexadecimal"
+    },
     {
       "uuid": "f8e96e60-f746-4c7a-8dc9-df6b77eefdfa",
       "slug": "bitflags",
diff --git a/docs/ABOUT.md b/docs/ABOUT.md
index 05c56799..f0d52389 100644
--- a/docs/ABOUT.md
+++ b/docs/ABOUT.md
@@ -22,7 +22,7 @@ The [zen of Python (PEP 20)][the zen of python] and [What is Pythonic?][what is
 
 Tests and tooling for this track currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_).
 It is highly recommended that students upgrade to at least `Python 3.8`, as some features used by this track may not be supported in earlier versions.
-That being said, most of the exercises will work with `Python 3.6+`, and many are compatible with `Python 2.7+`.
+That being said, most of the exercises will work with `Python 3.6+`.
 But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases].
 We will try to note when a feature is only available in a certain version.
 
diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md
index 8bf42a04..c8eb68ca 100644
--- a/docs/INSTALLATION.md
+++ b/docs/INSTALLATION.md
@@ -6,7 +6,7 @@ Real Python also offers a [nice guide][helpful guide] to installation on various
 Finally, these posts by Brett Cannon [A quick-and-dirty guide][quick-and-dirty] and [Why you should use `python -m pip`][python-m-pip], give very helpful advice on how to manage Python installations and packages.
 
 **Note for MacOS users:**  prior to MacOS Monterey (12.3), `Python 2.7` came pre-installed with the operating system.
-Using `Python 2.7` with Exercism or most other programs is not recommended.
+Using `Python 2.7` with Exercism or most other programs is not supported.
 You should instead install [Python 3][Python-three downloads] via one of the methods detailed below.
 As of MacOS Monterey (12.3), no version of Python will be pre-installed via MacOS.
 
@@ -20,7 +20,7 @@ Some quick links into the documentation by operating system:
 
 Exercism tests and tooling currently support `3.7` - `3.10.6` (_tests_) and [`Python 3.11`][311-new-features] (_tooling_).
 Exceptions to this support are noted where they occur.
-Most of the exercises will work with `Python 3.6+`, and many are compatible with `Python 2.7+`.
+Most of the exercises will work with `Python 3.6+`.
 But we don't guarantee support for versions not listed under [Active Python Releases][active-python-releases].
 
 
diff --git a/exercises/concept/black-jack/.docs/introduction.md b/exercises/concept/black-jack/.docs/introduction.md
index b79091f4..20722935 100644
--- a/exercises/concept/black-jack/.docs/introduction.md
+++ b/exercises/concept/black-jack/.docs/introduction.md
@@ -80,7 +80,7 @@ False
 Unlike numbers, strings (`str`) are compared [_lexicographically_][lexographic order], using their individual Unicode code points (_the result of passing each code point in the `str` to the built-in function [`ord()`][ord], which returns an `int`_).
 If all code points in both strings match and are _**in the same order**_, the two strings are considered equal.
 This comparison is done in a 'pair-wise' fashion - first-to-first, second-to-second, etc.
-Unlike in Python 2.x, in Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
+In Python 3.x, `str` and `bytes` cannot be directly coerced/compared.
 
 ```python
 >>> 'Python' > 'Rust'
diff --git a/exercises/concept/cater-waiter/.docs/introduction.md b/exercises/concept/cater-waiter/.docs/introduction.md
index fa29f056..905504a6 100644
--- a/exercises/concept/cater-waiter/.docs/introduction.md
+++ b/exercises/concept/cater-waiter/.docs/introduction.md
@@ -50,7 +50,7 @@ set()
 # The list is unpacked and each distinct element is added.
 >>> multiple_elements_from_list = set([2, 3, 2, 3, 3, 3, 5, 7, 11, 7, 11, 13, 13])
 >>> multiple_elements_from_set
-{2, 3, 5, 7, 11}
+{2, 3, 5, 7, 11, 13}
 ```
 
 Sets can hold heterogeneous datatypes, but all `set` elements must be _hashable_:
diff --git a/exercises/concept/inventory-management/.docs/instructions.md b/exercises/concept/inventory-management/.docs/instructions.md
index e1c1806c..718b91d3 100644
--- a/exercises/concept/inventory-management/.docs/instructions.md
+++ b/exercises/concept/inventory-management/.docs/instructions.md
@@ -4,13 +4,17 @@ In this exercise, you will be managing an inventory system.
 
 The inventory should be organized by the item name and it should keep track of the number of items available.
 
-You will have to handle adding items to an inventory. Each time an item appears in a given list, increase the item's quantity by `1` in the inventory. Then, you will have to handle deleting items from an inventory.
+You will have to handle adding items to an inventory.
+Each time an item appears in a given list, the item's quantity should be increased by `1` in the inventory.
+You will also have to handle deleting items from an inventory by decreasing quantities by `1` when requested.
+
+Finally, you will need to implement a function that will return all the key-value pairs in a given inventory as a `list` of `tuples`.
 
-To finish, you will have to implement a function which returns all the key-value pairs in an inventory as a list of `tuples`.
 
 ## 1. Create an inventory based on a list
 
-Implement the `create_inventory()` function that creates an "inventory" from a list of items. It should return a `dict` containing each item name paired with their respective quantity.
+Implement the `create_inventory(<input list>)` function that creates an "inventory" from an input list of items.
+It should return a `dict` containing each item name paired with their respective quantity.
 
 ```python
 >>> create_inventory(["coal", "wood", "wood", "diamond", "diamond", "diamond"])
@@ -19,7 +23,7 @@ Implement the `create_inventory()` function that creates an "inventory" from a l
 
 ## 2. Add items from a list to an existing dictionary
 
-Implement the `add_items()` function that adds a list of items to an inventory:
+Implement the `add_items(<inventory dict>, <item list>)` function that adds a list of items to the passed-in inventory:
 
 ```python
 >>> add_items({"coal":1}, ["wood", "iron", "coal", "wood"])
@@ -28,23 +32,26 @@ Implement the `add_items()` function that adds a list of items to an inventory:
 
 ## 3. Decrement items from the inventory
 
-Implement the `decrement_items(<items>)` function that takes a `list` of items. The function should remove one from the available count in the inventory for each time an item appears on the `list`:
+Implement the `decrement_items(<inventory dict>, <items list>)` function that takes a `list` of items.
+Your function should remove `1` from an item count for each time that item appears on the `list`:
 
 ```python
 >>> decrement_items({"coal":3, "diamond":1, "iron":5}, ["diamond", "coal", "iron", "iron"])
 {"coal":2, "diamond":0, "iron":3}
 ```
 
-Item counts in the inventory should not fall below 0. If the number of times an item appears on the list exceeds the count available, the quantity listed for that item should remain at 0 and additional requests for removing counts should be ignored.
+Item counts in the inventory should not be allowed to fall below 0.
+ If the number of times an item appears on the input `list` exceeds the count available, the quantity listed for that item should remain at 0.
+ Additional requests for removing counts should be ignored once the count falls to zero.
 
 ```python
 >>> decrement_items({"coal":2, "wood":1, "diamond":2}, ["coal", "coal", "wood", "wood", "diamond"])
 {"coal":0, "wood":0, "diamond":1}
 ```
 
-## 4. Remove an item entirely from the inventory
+## 4. Remove an entry entirely from the inventory
 
-Implement the `remove_item(<inventory>, <item>)` function that removes an item and its count entirely from an inventory:
+Implement the `remove_item(<inventory dict>, <item>)` function that removes an item and its count entirely from an inventory:
 
 ```python
 >>> remove_item({"coal":2, "wood":1, "diamond":2}, "coal")
@@ -58,9 +65,10 @@ If the item is not found in the inventory, the function should return the origin
 {"coal":2, "wood":1, "diamond":2}
 ```
 
-## 5. Return the inventory content
+## 5. Return the entire content of the inventory
 
-Implement the `list_inventory()` function that takes an inventory and returns a list of `(item, quantity)` tuples. The list should only include the available items (with a quantity greater than zero):
+Implement the `list_inventory(<inventory dict>)` function that takes an inventory and returns a list of `(item, quantity)` tuples.
+The list should only include the _available_ items (_with a quantity greater than zero_):
 
 ```python
 >>> list_inventory({"coal":7, "wood":11, "diamond":2, "iron":7, "silver":0})
diff --git a/exercises/concept/inventory-management/.docs/introduction.md b/exercises/concept/inventory-management/.docs/introduction.md
index 1b4a15dc..2b9ef011 100644
--- a/exercises/concept/inventory-management/.docs/introduction.md
+++ b/exercises/concept/inventory-management/.docs/introduction.md
@@ -1,50 +1,79 @@
 # Introduction
 
-A _**dictionary**_ is Python's primary mapping type that associates a _hashable key_ with a value. The lookup by key is more efficient than searching through an array, but does require more memory.
+A dictionary (`dict`) in Python is a data structure that associates [hashable][term-hashable] _keys_ to _values_ and is known in other programming languages as a resizable [hash table][hashtable-wikipedia], hashmap, or [associative array][associative-array].
+Dictionaries are Python's only built-in [mapping type][mapping-types-dict].
 
-## Dict construction
 
-Dictionaries can be created in various ways. Two simple options are the use the `dict()` class constructor or the dict literal declaration with key-value pairs.
+`Keys` must be hashable and unique across the dictionary.
+Key types can include `numbers`, `str`, or `tuples` (of _immutable_ values).
+They cannot contain _mutable_ data structures such as `lists`, `dict`s, or `set`s.
+As of Python 3.7, `dict` key order is guaranteed to be the order in which entries are inserted.
 
-### Use the `dict()` constructor
+`values` can be of any data type or structure.
+ Values can also nest _arbitrarily_, so they can include lists-of-lists, sub-dictionaries, and other custom or compound data structures.
+
+Given a `key`, dictionaries can retrieve a `value` in (on average) constant time (_independent of the number of entries_).
+Compared to searching for a value within a `list` or `array` (_without knowing the `index` position_), a `dict` uses significantly more memory, but has very rapid retrieval.
+Dictionaries are especially useful in scenarios where the collection of items is large and must be accessed and updated frequently.
+
+
+## Dictionary Construction
+
+Dictionaries can be created in many ways.
+The two most straightforward are using the `dict()`constructor or declaring a `dict` _literal_.
+
+### The `dict()` Class Constructor
+
+`dict()` (_the constructor for the dictionary class_) can be used with any iterable of `key`, `value` pairs or with a series of `<name>=<value>` _arguments_:
 
 ```python
+#Passing a list of key,value tuples.
+>>> wombat = dict([('name', 'Wombat'),('speed', 23),('land_animal', True)])
+{'name': 'Wombat', 'speed': 23, 'land_animal': True}
+
+
+#Using key=value arguments.
 >>> bear = dict(name="Black Bear", speed=40, land_animal=True)
 {'name': 'Black Bear', 'speed': 40, 'land_animal': True}
 ```
 
-### Declare a _dict_ literal
+### Dictionary Literals
+
+A `dict` can also be directly entered as a _dictionary literal_, using curly brackets (`{}`) enclosing `key : value` pairs:
 
 ```python
 >>> whale = {"name": "Blue Whale", "speed": 35, "land_animal": False}
 {'name': 'Blue Whale', 'speed': 35, 'land_animal': False}
 ```
 
-With the dict literal declaration keep in mind that _keys_ are of _data types_ `str` and the colon `:` is used instead of an equal sign `=`.
+## Accessing Values in a Dictionary
 
-## Accessing values
-
-You can access an item in a dictionary using the _key_ of the value.
-
-### Using _square brackets_ after the dict object
+You can access an entry in a dictionary using a _key_ in square (`[]`) brackets.
+If a `key` does not exist n the `dict`, a `KeyError` is thrown:
 
 ```python
 >>> bear["speed"]
 40
+
+>>> bear["color"]
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+KeyError: 'color'
 ```
 
-### Using `.get()`
+Accessing an entry via the `.get(<key>, <default value>)` method can avoid the `KeyError`:
 
 ```python
->>> whale.get("name")
-'Blue Whale'
+>>> bear.get("color", 'not found')
+'not found'
 ```
 
-## Changing values
+## Changing or Adding Dictionary Values
 
-You can easily change a value of an item using its _key_.
+You can change an entry `value` by assigning to its _key_:
 
 ```python
+#Assigning the value "Grizzly Bear" to the name key.
 >>> bear["name"] = "Grizzly Bear"
 {'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True}
 
@@ -52,29 +81,71 @@ You can easily change a value of an item using its _key_.
 {'name': 'Blue Whale', 'speed': 25, 'land_animal': False}
 ```
 
-## Deleting values using keys
+New `key`:`value` pairs can be _added_ in the same fashion:
+
+```python
+# Adding an new "color" key with a new "tawney" value.
+>>> bear["color"] = 'tawney'
+{'name': 'Grizzly Bear', 'speed': 40, 'land_animal': True, 'color': 'tawney'}
 
-You can delete an item from a dictionary using `dict.pop(<key>)`. This will remove the (`key`, `value`) pair from the dictionary and return the `value` for use. `dict.pop(<key>)` accepts second argument, `default` that is returned if the `key` is not found  (`dict.pop(<key>, <default>)`). Otherwise, a `KeyError` will be raised for any `key` that is missing.
+>>> whale["blowholes"] = 1
+{'name': 'Blue Whale', 'speed': 25, 'land_animal': False, 'blowholes': 1}
+```
+
+## Removing (Pop-ing) Dictionary Entries
+
+You can use the `.pop(<key>)` method to delete a dictionary entry.
+`.pop()` removes the (`key`, `value`) pair and returns the `value` for use.
+Like `.get()`, `.pop(<key>)` accepts second argument (_`dict.pop(<key>, <default value>)`_) that will be returned if the `key` is not found.
+This prevents a `KeyError` being raised:
 
 ```python
+#Using .pop() removes both the key and value, returning the value.
 >>> bear.pop("name")
 'Grizzly Bear'
->>> bear.pop("name", "Unknown")
-'Unknown'
+
+
+#The "name" key is now removed from the dictionary.
+#Attempting .pop() a second time will throw a KeyError.
 >>> bear.pop("name")
 Traceback (most recent call last):
   File "<stdin>", line 1, in <module>
 KeyError: 'name'
+
+
+#Using a default argument with .pop() will prevent a KeyError from a missing key.
+>>> bear.pop("name", "Unknown")
+'Unknown'
 ```
 
-## Looping through a dictionary
+## Looping through/Iterating over a Dictionary
 
-Looping through a dictionary using `for item in dict` will iterate over the _keys_, but you can access the _values_ by using _square brackets_.
+Looping through a dictionary using `for item in dict` or `while item` will iterate over only the _keys _ by default.
+You can access the _values_ within the same loop by using _square brackets_:
 
 ```python
 >>> for key in bear:
->>>     (key, bear[key])
+>>>     print((key, bear[key])) #this forms a tuple of (key, value) and prints it.
 ('name', 'Black Bear')
 ('speed', 40)
 ('land_animal', True)
 ```
+
+You can also use the `.items()` method, which returns (`key`, `value`) tuples automatically:
+
+```python
+#dict.items() forms (key, value tuples) that can be unpacked and iterated over.
+>>> for key, value in whale.items():
+>>>     print(key, ":", value)
+name : Blue Whale
+speed : 25
+land_animal : False
+blowholes : 1
+```
+
+Likewise, the `.keys()` method will return `keys` and the `.values()` method will return the `values`.
+
+[associative-array]: https://en.wikipedia.org/wiki/Associative_array#:~:text=In%20computer%20science%2C%20an%20associative,a%20function%20with%20finite%20domain.
+[hashtable-wikipedia]: https://en.wikipedia.org/wiki/Hash_table
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+[term-hashable]: https://docs.python.org/3/glossary.html#term-hashable
diff --git a/exercises/concept/mecha-munch-management/.docs/hints.md b/exercises/concept/mecha-munch-management/.docs/hints.md
new file mode 100644
index 00000000..9edd70ab
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.docs/hints.md
@@ -0,0 +1,56 @@
+# Hints
+
+## General
+
+Remember, this is an [MVP][mvp].
+That means you don't need to get too fancy with error handling or different "edge case" scenarios.
+It's OK to be simple and direct with the functions you are writing.
+
+The dictionary section of the [official tutorial][dicts-docs] and the mapping type [official library reference][mapping-types-dict] are excellent places to look for more help with all these methods.
+
+
+## 1. Add Item(s) to the Users Shopping Cart
+
+- You will need to iterate through each item in `items_to_add`.
+- You can avoid a `KeyError` when a key is missing by using a `dict` [method][set-default] that takes a _default value_ as one of its arguments.
+- It is also possible to accomplish the same thing manually in the `loop` by using some checking and error handling, but the `dict` method is easier.
+
+## 2. Read in Items Listed in the Users Notes App
+
+- Remember, Python's got a method for _everything_. This one is a _classmethod_ that's an easy way to [populate a `dict`][fromkeys] with keys.
+- This `dict` method returns a _new dictionary_, populated with default values. If no value is given, the default value will become `None`
+
+## 3. Update Recipe "Ideas" Section
+
+- Don't overthink this one!  This can be solved in **one** `dict` method call.
+- The key word here is .... [_update_][update].
+
+## 4. Sort the Items in the User Cart
+
+- What method would you call to get an [iterable view of items][items] in the dictionary?
+- If you had a `list` or a `tuple`, what [`built-in`][builtins] function might you use to sort them?
+- The built-in function you want is the one that returns a _copy_, and doesn't mutate the original.
+
+## 5. Send User Shopping Cart to Store for Fulfillment
+
+- Having a fresh, empty dictionary here as the `fulfillment_cart` might be handy for adding in items.
+- `Looping` through the members of the cart might be the most direct way of accessing things here.
+- What method would you call to get an [iterable view of just the keys][keys] of the dictionary?
+- Remember that you can get the `value` of a given key by using `<dict name>[<key_name>]` syntax.
+- If you had a `list` or a `tuple`, what [`built-in`][builtins] function might you use to sort them?
+- Remember that the `built-in` function can take an optional `reversed=true` argument.
+
+## 6. Update the Store Inventory to Reflect what a User Has Ordered.
+
+- There is a method that will give you an iterable view of (`key`, `value`) pairs from the dictionary.
+- You can access an item in a _nested tuple_ using _bracket notation_:  `<tuple>[<nested_tuple>][<index_of_value>]`
+- Don't forget to check if an inventory count falls to zero, you'll need to add in the "Out of Stock" message.
+
+[builtins]: https://docs.python.org/3/library/functions.html
+[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
+[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
+[items]: https://docs.python.org/3/library/stdtypes.html#dict.items
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
+[mvp]: https://en.wikipedia.org/wiki/Minimum_viable_product
+[set-default]: https://docs.python.org/3/library/stdtypes.html#dict.setdefault
+[update]: https://docs.python.org/3/library/stdtypes.html#dict.update
diff --git a/exercises/concept/mecha-munch-management/.docs/instructions.md b/exercises/concept/mecha-munch-management/.docs/instructions.md
new file mode 100644
index 00000000..f5fa6f79
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.docs/instructions.md
@@ -0,0 +1,129 @@
+# Instructions
+
+Mecha Munch™, a grocery shopping automation company has just hired you to work on their ordering app.
+Your team is tasked with building an MVP (_[minimum viable product][mvp]_) that manages all the basic shopping cart activities, allowing users to add, remove, and sort their grocery orders.
+Thankfully, a different team is handling all the money and check-out functions!
+
+## 1. Add Item(s) to the Users Shopping Cart
+
+The MVP should allow the user to add items to their shopping cart.
+This could be a single item or multiple items at once.
+Since this is an MVP, item quantity is indicated by _repeats_.
+If a user wants to add 2 Oranges, 'Oranges' will appear twice in the input iterable.
+If the user already has the item in their cart, the cart quantity should be increased by 1.
+If the item is _new_ to the cart, it should be added with a quantity of 1.
+
+Create the function `add_items(<current_cart>, <items_to_add>)` that takes a cart dictionary and any list-like iterable of items to add as arguments.
+It should return a new/updated shopping cart dictionary for the user.
+
+```python
+>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, 
+              ('Apple', 'Apple', 'Orange', 'Apple', 'Banana'))
+{'Banana': 4, 'Apple': 5, 'Orange': 2}
+
+>>> add_items({'Banana': 3, 'Apple': 2, 'Orange': 1}, 
+              ['Banana', 'Orange', 'Blueberries', 'Banana'])
+{'Banana': 5, 'Apple': 2, 'Orange': 2, 'Blueberries': 1}
+```
+
+## 2. Read in Items Listed in the Users Notes App
+
+Uh-oh.
+Looks like the product team is engaging in [feature creep][feature creep].
+They want to add extra functionality to the MVP.
+The application now has to create a shopping cart by reading items off a users notes app.
+Convenient for the users, but slightly more work for the team.
+
+Create the function `read_notes(<notes>)` that can take any list-like iterable as an argument.
+The function should parse the items and create a user shopping cart/dictionary.
+Each item should be added with a quantity of 1.
+The new user cart should then be returned.
+
+```python
+>>> read_notes(('Banana','Apple', 'Orange'))
+{'Banana': 1, 'Apple': 1, 'Orange': 1}
+
+>>> read_notes(['Blueberries', 'Pear', 'Orange', 'Banana', 'Apple'])
+{'Blueberries' : 1, 'Pear' : 1, 'Orange' : 1, 'Banana' : 1, 'Apple' : 1}
+```
+
+## 3. Update Recipe "Ideas" Section
+
+The app has an "ideas" section that's filled with finished recipes from various cuisines.
+The user can select any one of these recipes and have all its ingredients added to their shopping cart automatically.
+The project manager has asked you create a way to edit these "ideas" recipes, since the content team keeps changing around ingredients and quantities.
+
+Create the function `update_recipes(<ideas>, <recipe_updates>)` that takes an "ideas" dictionary and an iterable of recipe updates as arguments.
+The function should return the new/updated "ideas" dictionary.
+
+```python
+>>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
+                    'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}},
+(('Banana Bread', {'Banana': 4,  'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),))
+...
+
+{'Banana Bread' : {'Banana': 4,  'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3},
+ 'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}}
+
+>>> update_recipes({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
+                    'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1},
+                    'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}},
+[('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}),
+('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}),
+('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3})])
+...
+
+{'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
+ 'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2},
+ 'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1},
+ 'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}}
+```
+
+## 4. Sort the Items in the User Cart
+
+Once a user has started a cart, the app allows them to sort their items alphabetically.
+This makes things easier to find, and helps when there are data-entry errors like having 'potatoes' and 'Potato' in the database.
+
+Create the function `sort_entries(<cart>)` that takes a shopping cart/dictionary as an argument and returns a new, alphabetically sorted one.
+
+```python
+>>> sort_entries({'Banana': 3, 'Apple': 2, 'Orange': 1})
+{'Apple': 2, 'Banana':3, 'Orange': 1}
+```
+
+## 5. Send User Shopping Cart to Store for Fulfillment
+
+The app needs to send a given users cart to the store for fulfillment.
+However, the shoppers in the store need to know which store isle the item can be found in and if the item needs refrigeration.
+So (_rather arbitrarily_) the "fulfillment cart" needs to be sorted in reverse alphabetical order with item quantities combined with location and refrigeration information.
+
+Create the function `send_to_store(<cart>, <isle_mapping>)` that takes a user shopping cart and a dictionary that has store isle number and a `True`/`False` for refrigeration needed for each item.
+The function should `return` a combined "fulfillment cart" that has (quantity, isle, and refrigeration) for each item the customer is ordering.
+Items should appear in _reverse_ alphabetical order.
+
+```python
+>>> send_to_store({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2}, 
+                  {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]})
+{'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}
+```
+
+## 6. Update the Store Inventory to Reflect what a User Has Ordered.
+
+The app can't just place customer orders endlessly.
+Eventually, the store is going to run out of various products.
+So your app MVP needs to update the store inventory every time a user sends their order to the store.
+Otherwise, customers will order products that aren't actually available.
+
+Create the function `update_store_inventory(<fulfillment_cart>, <store_inventory>)` that takes a "fulfillment cart" and a store inventory.
+The function should reduce the store inventory amounts by the number "ordered" in the "fulfillment cart" and then return the updated store inventory.
+Where a store item count falls to 0, the count should be replaced by the message 'Out of Stock'.
+
+```python
+>>> update_store_inventory({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]}, 
+{'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False], 'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]})
+
+{'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False], 'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]}
+```
+
+[feature creep]: https://en.wikipedia.org/wiki/Feature_creep
+[mvp]: https://en.wikipedia.org/wiki/Minimum_viable_product
diff --git a/exercises/concept/mecha-munch-management/.docs/introduction.md b/exercises/concept/mecha-munch-management/.docs/introduction.md
new file mode 100644
index 00000000..f1c5744e
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.docs/introduction.md
@@ -0,0 +1,221 @@
+# Dictionary Methods in Python
+
+The `dict` class in Python provides many useful [methods][dict-methods] for working with dictionaries.
+Some were introduced in the concept for `dicts`.
+Here we cover a few more - along with some techniques for iterating through and manipulating dictionaries.
+
+### Use `setdefault()` for Error-Free Insertion
+
+The dictionary concept previously covered that `.get(key, <default value>)` returns an existing `value` or the `default value` if a `key` is not found in a dictionary, thereby avoiding a `KeyError`.
+This works well in situations where you would rather not have extra error handling but cannot trust that a looked-for `key` will be present.
+
+For a similarly "safe" (_without KeyError_) insertion operation, there is the `.setdefault(key, <default value>)` method.
+`setdefault(key, <default value>)` will return the `value` if the `key` is found in the dictionary.
+If the key is **not** found, it will _insert_ the (`key`, `default value`) pair and return the `default value` for use.
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
+
+# Looking for the value associated with key "Rock Brown".
+# The key does not exist, so it is added with the default value, and the value is returned.
+>>> palette.setdefault('Rock Brown', '#694605')
+'#694605'
+
+# The (key, default value) pair has now been added to the dictionary.
+>>> palette_I
+{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Rock Brown': '#694605'}
+```
+
+## Use `fromkeys()` to Populate a Dictionary from an Iterable
+
+To quickly populate a dictionary with various `keys` and default values, the _class method_ [`fromkeys(iterable, <default value>)`][fromkeys] will iterate through an iterable of `keys` and create a new `dict`.
+All `values` will be set to the `default value` provided:
+
+```python
+>>> new_dict = dict.fromkeys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'], 'fill in hex color here')
+
+{'Grassy Green': 'fill in hex color here',
+ 'Purple Mountains Majesty': 'fill in hex color here',
+ 'Misty Mountain Pink': 'fill in hex color here'}
+```
+
+## Iterating Over Entries in a Dictionary Via Views
+
+The `.keys()`, `.values()`, and `.items()` methods return [_iterable views_][dict-views] of a dictionary.
+
+These views can be used to easily loop over entries without altering them.
+Views are also _dynamic_ -- when underlying dictionary data changes, the associated `view object` will reflect the change:
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+                 'Purple Mountains Majesty': '#8076a3',
+                  'Misty Mountain Pink': '#f9c5bd'}
+
+# Using .keys() returns a list of keys.
+>>> palette_I.keys()
+dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink'])
+
+# Using .values() returns a list of values.
+>>> palette_I.values()
+dict_values(['#9bc400', '#8076a3', '#f9c5bd'])
+
+# Using .items() returns a list of (key, value) tuples.
+>>> palette_I.items()
+dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', '#8076a3'), ('Misty Mountain Pink', '#f9c5bd')])
+
+# Views are dynamic.  Changing values in the dict changes all of the associated views.
+>>> palette_I['Purple Mountains Majesty'] = (128, 118, 163)
+>>> palette_I['Deep Red'] = '#932432'
+
+>>> palette_I.values()
+dict_values(['#9bc400', (128, 118, 163), '#f9c5bd', '#932432'])
+
+>>> palette_I.keys()
+dict_keys(['Grassy Green', 'Purple Mountains Majesty', 'Misty Mountain Pink', 'Deep Red'])
+
+>>> palette_I.items()
+dict_items([('Grassy Green', '#9bc400'), ('Purple Mountains Majesty', (128, 118, 163)), ('Misty Mountain Pink', '#f9c5bd'), ('Deep Red', '#932432')])
+```
+
+## More on `.keys()`, `.values()`, and `.items()`
+
+In Python 3.7+, `dicts` preserve the order in which entries are inserted allowing First-in, First-out (_`FIFO`_),  iteration when using `.keys()`, `.values()`, or `.items()`.
+
+In Python 3.8+, views are also _reversible_.
+This allows keys, values, or (`key`, `value`) pairs to be iterated over in Last-in, First-out (`LIFO`) order by using `reversed(<dict>.keys())`, `reversed(<dict>.values())`, or `reversed(<dict>.items())`:
+
+```python
+>>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
+
+# Iterating in insertion order 
+>>> for item in palette_II.items():
+...     print(item)
+...
+('Factory Stone Purple', '#7c677f')
+('Green Treeline', '#478559')
+('Purple baseline', '#161748')
+
+
+# Iterating in the reverse direction.
+>>> for item in reversed(palette_II.items()):
+...    print (item)
+...
+('Purple baseline', '#161748')
+('Green Treeline', '#478559')
+('Factory Stone Purple', '#7c677f')
+```
+
+## Sorting a Dictionary
+
+Dictionaries do not have a built-in sorting method.
+However, it is possible to sort a `dict` _view_ using the built-in function `sorted()` with `.items()`.
+The sorted view can then be used to create a new dictionary.
+Like iteration, the default sort is over dictionary `keys`.
+
+```python
+# Default ordering for a dictionary is last in, first out (LIFO).
+>>> color_palette = {'Grassy Green': '#9bc400', 
+                    'Purple Mountains Majesty': '#8076a3', 
+                    'Misty Mountain Pink': '#f9c5bd', 
+                    'Factory Stone Purple': '#7c677f', 
+                    'Green Treeline': '#478559', 
+                    'Purple baseline': '#161748'}
+ 
+ 
+# The default sort order for a dictionary uses the keys.
+>>> sorted_palette = dict(sorted(color_palette.items()))
+>>> sorted_palette
+{'Factory Stone Purple': '#7c677f',
+ 'Grassy Green': '#9bc400',
+ 'Green Treeline': '#478559',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Purple baseline': '#161748'}
+```
+
+## Combining Dictionaries with `.update()`
+
+`<dict_one>.update(<dict_two>)` can be used to _combine_ two dictionaries.
+This method will take the (`key`,`value`) pairs of `<dict_two>` and write them into `<dict_one>`:
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400',
+                 'Purple Mountains Majesty': '#8076a3',
+                  'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_II = {'Factory Stone Purple': '#7c677f',
+                  'Green Treeline': '#478559',
+                  'Purple Baseline': '#161748'}
+
+>>> palette_I.update(palette_II)
+
+# Note that new items from palette_II are added.
+>>> palette_I
+{'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple Baseline': '#161748'}
+```
+
+Where keys in the two dictionaries _overlap_, the `value` in `dict_one` will be _overwritten_ by the corresponding `value` from `dict_two`:
+
+```python
+>>> palette_I =   {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd', 
+                   'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
+>>> palette_III = {'Grassy Green': (155, 196, 0), 'Purple Mountains Majesty': (128, 118, 163),
+                   'Misty Mountain Pink': (249, 197, 189)}
+>>> palette_I.update(palette_III)
+
+# Overlapping values in palette_I are replaced with values from palette_III
+>>> palette_I
+{'Grassy Green': (155, 196, 0),
+  'Purple Mountains Majesty': (128, 118, 163), 
+  'Misty Mountain Pink': (249, 197, 189), 
+  'Factory Stone Purple': '#7c677f', 
+  'Green Treeline': '#478559', 'Purple baseline': '#161748'}
+```
+
+## Merge or Update Dictionaries Via the Union (`|`) Operators
+
+Python 3.9 introduces a different means of merging `dicts`: the `union` operators.
+`dict_one | dict_two` will create a **new dictionary**, made up of the (`key`, `value`) pairs of `dict_one` and `dict_two`.
+When both dictionaries share keys, `dict_two` values take precedence.
+
+```python
+>>> palette_I = {'Grassy Green': '#9bc400', 'Purple Mountains Majesty': '#8076a3', 'Misty Mountain Pink': '#f9c5bd'}
+>>> palette_II = {'Factory Stone Purple': '#7c677f', 'Green Treeline': '#478559', 'Purple baseline': '#161748'}
+>>> new_dict = palette_I | palette_II
+>>> new_dict
+...
+{'Grassy Green': '#9bc400',
+ 'Purple Mountains Majesty': '#8076a3',
+ 'Misty Mountain Pink': '#f9c5bd',
+ 'Factory Stone Purple': '#7c677f',
+ 'Green Treeline': '#478559',
+ 'Purple baseline': '#161748'}
+```
+
+`dict_one |= other` behaves similar to `<dict_one>.update(<dict_two>)`, but in this case, `other` can be either a `dict` or an iterable of (`key`, `value`) pairs:
+
+```python
+>>> palette_III = {'Grassy Green': (155, 196, 0),
+                   'Purple Mountains Majesty': (128, 118, 163),
+                   'Misty Mountain Pink': (249, 197, 189)}
+>>> new_dict |= palette_III
+>>> new_dict
+...
+{'Grassy Green': (155, 196, 0),
+'Purple Mountains Majesty': (128, 118, 163),
+'Misty Mountain Pink': (249, 197, 189),
+'Factory Stone Purple': '#7c677f',
+'Green Treeline': '#478559',
+'Purple baseline': '#161748'}
+```
+
+For a detailed explanation of dictionaries and methods for working with them, the [official tutorial][dicts-docs] and the [official library reference][mapping-types-dict] are excellent starting places.
+
+[Real Python][how-to-dicts] and [Finxter][fi-dict-guide] also have very thorough articles on Python dictionaries.
+
+[dict-methods]: https://docs.python.org/3/library/stdtypes.html#dict
+[dict-views]: https://docs.python.org/3/library/stdtypes.html#dict-views
+[dicts-docs]: https://docs.python.org/3/tutorial/datastructures.html#dictionaries
+[fi-dict-guide]: https://blog.finxter.com/python-dictionary
+[fromkeys]: https://docs.python.org/3/library/stdtypes.html#dict.fromkeys
+[how-to-dicts]: https://www.w3schools.com/python/python_dictionaries.asp
+[mapping-types-dict]: https://docs.python.org/3/library/stdtypes.html#mapping-types-dict
diff --git a/exercises/concept/mecha-munch-management/.meta/config.json b/exercises/concept/mecha-munch-management/.meta/config.json
new file mode 100644
index 00000000..f09d0f29
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.meta/config.json
@@ -0,0 +1,21 @@
+{
+  "authors": [
+    "meatball133",
+    "BethanyG"
+  ],
+  "contributors": [
+  ],
+  "files": {
+    "solution": [
+      "dict_methods.py"
+    ],
+    "test": [
+      "dict_methods_test.py"
+    ],
+    "exemplar": [
+      ".meta/exemplar.py"
+    ]
+  },
+  "icon": "gross-store",
+  "blurb": "Learn about dictionary methods by building a shopping cart MVP for the Mecha Munch grocery app."
+}
diff --git a/exercises/concept/mecha-munch-management/.meta/design.md b/exercises/concept/mecha-munch-management/.meta/design.md
new file mode 100644
index 00000000..4c3c52a0
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.meta/design.md
@@ -0,0 +1,59 @@
+## Learning objectives
+
+Cover useful `dict` methods and a few techniques for operating on/manipulating `dicts`.
+
+- `dict.setdefault()` for automatically adding keys when needed.
+- `dict.fromkeys(iterable, <default value>)` for creating a new `dict` from any number of iterables.
+- `dict.keys()`, `dict.values()`, and `dict.items()` for convenient iterators.
+- `reversed(dict.keys())`, `reversed(dict.values())`, or `reversed(dict.items())` for reversed views.
+- `sorted()` with `dict.items()`. for re-ordering entries in a `dict`.
+- `dict_one.update(<dict_two>)` for updating one `dict` with overlapping values from another `dict`.
+- `dict | other_dict` and `dict |= other_dict` for merging or updating two `dict`s via operators.
+- `dict.popitem()` for removing and returning a key, value pair.
+
+- Working more with  the `dict` views `items()` , `keys()` or `values()`.  (e.g, by sorting information using `sorted()` or by swapping `keys` and `values`, etc.)
+- Knowing that Dictionaries can be _nested_, _-- e.g._ ' a dictionary of dictionaries'.
+- Considerations when `updating()` or using `union` with dictionaries.
+
+## Out of scope
+
+Please take a look at the `dicts` concept exercise [design.md file](https://github.com/exercism/python/edit/main/exercises/concept/inventory-management/.meta/design.md) for `dict` features  taught thus far.
+While those methods can be used for solutions to this exercise, it  isn't necessary to cover them again in detail.  Additionally, the following is out of scope:
+
+- Dictionary comprehensions
+- Built-in functions as they relate to this data structure (*e.g.* `len()`, or `enumerate()`
+- Considerations of Mutability
+- `copy()` vs `deepcopy()`
+- Memory and performance characteristics.
+- Related `collections` module with `Counter()` and `defaultdict()`
+
+## Concepts
+
+- `dicts`
+- `dict-methods`
+
+## Prerequisites
+
+These are the concepts/concept exercises the student needs to complete/understand before solving this concept exercise.
+
+- `basics`
+- `bools`
+- `conditionals`
+- `comparisons`
+- `dicts`
+- `lists`
+- `loops`
+- `numbers`
+- `strings`
+- `tuples`
+
+
+## Resources to refer to
+
+- [Python docs:  Tutorial - Dictionaries](https://docs.python.org/3/tutorial/datastructures.html#dictionaries)
+- [Python docs:  Mapping Type `dict`](https://docs.python.org/3/library/stdtypes.html#mapping-types-dict)
+- [Real Python:  Dicts](https://realpython.com/python-dicts/)
+- [Digital Ocean:  Understanding dictionaries in python 3](https://www.digitalocean.com/community/tutorials/understanding-dictionaries-in-python-3)
+- [Stack Overflow: exchanging keys with values in a `dict` in Python](https://stackoverflow.com/questions/1031851/how-do-i-exchange-keys-with-values-in-a-dictionary)
+- [kite:  how to sort a dictionary by key in python](https://www.kite.com/python/answers/how-to-sort-a-dictionary-by-key-in-python)
+- [medium:  16 Python Dictionary Tips](https://medium.com/python-in-plain-english/16-intermediate-level-python-dictionary-tips-tricks-and-shortcuts-1376859e1adc)  _**note:** this is a good resource for ideas and writing this exericse, but is a subscription-based service, so not the best for linking to_
\ No newline at end of file
diff --git a/exercises/concept/mecha-munch-management/.meta/exemplar.py b/exercises/concept/mecha-munch-management/.meta/exemplar.py
new file mode 100644
index 00000000..0390944d
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/.meta/exemplar.py
@@ -0,0 +1,79 @@
+"""Functions to manage a users shopping cart items."""
+
+
+def add_item(current_cart, items_to_add):
+    """Add items to shopping cart.
+
+    :param current_cart: dict - the current shopping cart.
+    :param items_to_add: iterable - items to add to the cart.
+    :return: dict - the updated user cart dictionary.
+    """
+
+    for item in items_to_add:
+        current_cart.setdefault(item, 0)
+        current_cart[item] += 1
+
+    return current_cart
+
+
+def read_notes(notes):
+    """Create user cart from an iterable notes entry.
+
+    :param notes: iterable of items to add to cart.
+    :return: dict - a user shopping cart dictionary.
+    """
+
+    return dict.fromkeys(notes, 1)
+
+
+def update_recipes(ideas, recipe_updates):
+    """Update the recipe ideas dictionary.
+
+    :param ideas: dict - The "recipe ideas" dict.
+    :param recipe_updates: dict - dictionary with updates for the ideas section.
+    :return: dict - updated "recipe ideas" dict.
+    """
+
+    ideas.update(recipe_updates)
+    return ideas
+
+
+def sort_entries(cart):
+    """Sort a users shopping cart in alphabetically order.
+
+    :param cart: dict - a users shopping cart dictionary.
+    :return: dict - users shopping cart sorted in alphabetical order.
+    """
+
+    return dict(sorted(cart.items()))
+
+
+def send_to_store(cart, isle_mapping):
+    """Combine users order to isle and refrigeration information.
+
+    :param cart: dict - users shopping cart dictionary.
+    :param isle_mapping: dict - isle and refrigeration information dictionary.
+    :return: dict - fulfillment dictionary ready to send to store.
+    """
+    fulfillment_cart = {}
+
+    for key in cart.keys():
+        fulfillment_cart[key] = [cart[key]] + isle_mapping[key]
+
+    return dict(sorted(fulfillment_cart.items(), reverse=True))
+
+
+def update_store_inventory(fulfillment_cart, store_inventory):
+    """Update store inventory levels with user order.
+
+    :param fulfillment cart: dict - fulfillment cart to send to store.
+    :param store_inventory: dict - store available inventory
+    :return: dict - store_inventory updated.
+    """
+
+    for key, values in fulfillment_cart.items():
+        store_inventory[key][0] = store_inventory[key][0] - values[0]
+        if store_inventory[key][0] == 0:
+            store_inventory[key][0] = 'Out of Stock'
+
+    return store_inventory
diff --git a/exercises/concept/mecha-munch-management/dict_methods.py b/exercises/concept/mecha-munch-management/dict_methods.py
new file mode 100644
index 00000000..d443c8bc
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/dict_methods.py
@@ -0,0 +1,65 @@
+"""Functions to manage a users shopping cart items."""
+
+
+def add_item(current_cart, items_to_add):
+    """Add items to shopping cart.
+
+    :param current_cart: dict - the current shopping cart.
+    :param items_to_add: iterable - items to add to the cart.
+    :return: dict - the updated user cart dictionary.
+    """
+
+    pass
+
+
+def read_notes(notes):
+    """Create user cart from an iterable notes entry.
+
+    :param notes: iterable of items to add to cart.
+    :return: dict - a user shopping cart dictionary.
+    """
+
+    pass
+
+
+def update_recipes(ideas, recipe_updates):
+    """Update the recipe ideas dictionary.
+
+    :param ideas: dict - The "recipe ideas" dict.
+    :param recipe_updates: dict - dictionary with updates for the ideas section.
+    :return: dict - updated "recipe ideas" dict.
+    """
+
+    pass
+
+
+def sort_entries(cart):
+    """Sort a users shopping cart in alphabetically order.
+
+    :param cart: dict - a users shopping cart dictionary.
+    :return: dict - users shopping cart sorted in alphabetical order.
+    """
+
+    pass
+
+
+def send_to_store(cart, isle_mapping):
+    """Combine users order to isle and refrigeration information.
+
+    :param cart: dict - users shopping cart dictionary.
+    :param isle_mapping: dict - isle and refrigeration information dictionary.
+    :return: dict - fulfillment dictionary ready to send to store.
+    """
+
+    pass
+
+
+def update_store_inventory(fulfillment_cart, store_inventory):
+    """Update store inventory levels with user order.
+
+    :param fulfillment cart: dict - fulfillment cart to send to store.
+    :param store_inventory: dict - store available inventory
+    :return: dict - store_inventory updated.
+    """
+
+    pass
diff --git a/exercises/practice/accumulate/.docs/instructions.md b/exercises/practice/accumulate/.docs/instructions.md
index 435e0b32..c25a03fa 100644
--- a/exercises/practice/accumulate/.docs/instructions.md
+++ b/exercises/practice/accumulate/.docs/instructions.md
@@ -1,9 +1,6 @@
 # Instructions
 
-Implement the `accumulate` operation, which, given a collection and an
-operation to perform on each element of the collection, returns a new
-collection containing the result of applying that operation to each element of
-the input collection.
+Implement the `accumulate` operation, which, given a collection and an operation to perform on each element of the collection, returns a new collection containing the result of applying that operation to each element of the input collection.
 
 Given the collection of numbers:
 
@@ -21,6 +18,5 @@ Check out the test suite to see the expected function signature.
 
 ## Restrictions
 
-Keep your hands off that collect/map/fmap/whatchamacallit functionality
-provided by your standard library!
+Keep your hands off that collect/map/fmap/whatchamacallit functionality provided by your standard library!
 Solve this one yourself using other basic tools instead.
diff --git a/exercises/practice/acronym/.meta/template.j2 b/exercises/practice/acronym/.meta/template.j2
index 1373d724..70c5d431 100644
--- a/exercises/practice/acronym/.meta/template.j2
+++ b/exercises/practice/acronym/.meta/template.j2
@@ -13,4 +13,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases %}
     {{ test_case(case) }}
     {% endfor %}
-{{ macros.footer() }}
diff --git a/exercises/practice/all-your-base/.meta/template.j2 b/exercises/practice/all-your-base/.meta/template.j2
index 20684b54..b19efb5b 100644
--- a/exercises/practice/all-your-base/.meta/template.j2
+++ b/exercises/practice/all-your-base/.meta/template.j2
@@ -29,4 +29,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor -%}
 
-{{ macros.footer() }}
diff --git a/exercises/practice/allergies/.meta/template.j2 b/exercises/practice/allergies/.meta/template.j2
index f39a9d54..a15dc176 100644
--- a/exercises/practice/allergies/.meta/template.j2
+++ b/exercises/practice/allergies/.meta/template.j2
@@ -27,5 +27,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
 
     {% endfor %}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/alphametics/.meta/template.j2 b/exercises/practice/alphametics/.meta/template.j2
index 4841b41e..73291b8a 100644
--- a/exercises/practice/alphametics/.meta/template.j2
+++ b/exercises/practice/alphametics/.meta/template.j2
@@ -30,4 +30,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         {% endif %}
     {% endfor %}
 
-{{ macros.footer()}}
diff --git a/exercises/practice/armstrong-numbers/.meta/template.j2 b/exercises/practice/armstrong-numbers/.meta/template.j2
index f6ac6e76..d9a1cceb 100644
--- a/exercises/practice/armstrong-numbers/.meta/template.j2
+++ b/exercises/practice/armstrong-numbers/.meta/template.j2
@@ -9,6 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             {{ case["expected"] }}
         )
 
-    {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
+    {% endfor %}
\ No newline at end of file
diff --git a/exercises/practice/atbash-cipher/.meta/template.j2 b/exercises/practice/atbash-cipher/.meta/template.j2
index d3eb3d87..1ad9d575 100644
--- a/exercises/practice/atbash-cipher/.meta/template.j2
+++ b/exercises/practice/atbash-cipher/.meta/template.j2
@@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endfor %}
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/beer-song/.meta/template.j2 b/exercises/practice/beer-song/.meta/template.j2
index 37e45402..7fd4a26d 100644
--- a/exercises/practice/beer-song/.meta/template.j2
+++ b/exercises/practice/beer-song/.meta/template.j2
@@ -18,6 +18,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endfor %}
     {% endfor %}
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/binary-search-tree/.meta/template.j2 b/exercises/practice/binary-search-tree/.meta/template.j2
index 518d5ced..e2606ddf 100644
--- a/exercises/practice/binary-search-tree/.meta/template.j2
+++ b/exercises/practice/binary-search-tree/.meta/template.j2
@@ -63,6 +63,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             pass
         else:
             raise AssertionError
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/binary-search/.docs/instructions.md b/exercises/practice/binary-search/.docs/instructions.md
index d7f1c899..aa1946cf 100644
--- a/exercises/practice/binary-search/.docs/instructions.md
+++ b/exercises/practice/binary-search/.docs/instructions.md
@@ -11,7 +11,7 @@ Binary search only works when a list has been sorted.
 
 The algorithm looks like this:
 
-- Find the middle element of a sorted list and compare it with the item we're looking for.
+- Find the middle element of a *sorted* list and compare it with the item we're looking for.
 - If the middle element is our item, then we're done!
 - If the middle element is greater than our item, we can eliminate that element and all the elements **after** it.
 - If the middle element is less than our item, we can eliminate that element and all the elements **before** it.
diff --git a/exercises/practice/binary/.docs/instructions.md b/exercises/practice/binary/.docs/instructions.md
index 3cfde215..046fd3e0 100644
--- a/exercises/practice/binary/.docs/instructions.md
+++ b/exercises/practice/binary/.docs/instructions.md
@@ -2,9 +2,9 @@
 
 Convert a binary number, represented as a string (e.g. '101010'), to its decimal equivalent using first principles.
 
-Implement binary to decimal conversion. Given a binary input
-string, your program should produce a decimal output. The
-program should handle invalid inputs.
+Implement binary to decimal conversion.
+Given a binary input string, your program should produce a decimal output.
+The program should handle invalid inputs.
 
 ## Note
 
@@ -15,8 +15,7 @@ program should handle invalid inputs.
 
 Decimal is a base-10 system.
 
-A number 23 in base 10 notation can be understood
-as a linear combination of powers of 10:
+A number 23 in base 10 notation can be understood as a linear combination of powers of 10:
 
 - The rightmost digit gets multiplied by 10^0 = 1
 - The next number gets multiplied by 10^1 = 10
diff --git a/exercises/practice/bob/.meta/template.j2 b/exercises/practice/bob/.meta/template.j2
index ce22d184..07df6e8e 100644
--- a/exercises/practice/bob/.meta/template.j2
+++ b/exercises/practice/bob/.meta/template.j2
@@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         {%- set expected = case["expected"] %}
         self.assertEqual({{case["property"]}}("{{argument}}"), "{{expected}}")
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/book-store/.docs/instructions.md b/exercises/practice/book-store/.docs/instructions.md
index 341ad01f..906eb587 100644
--- a/exercises/practice/book-store/.docs/instructions.md
+++ b/exercises/practice/book-store/.docs/instructions.md
@@ -12,9 +12,9 @@ If you buy 4 different books, you get a 20% discount.
 
 If you buy all 5, you get a 25% discount.
 
-Note: that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8.
+Note that if you buy four books, of which 3 are different titles, you get a 10% discount on the 3 that form part of a set, but the fourth book still costs $8.
 
-Your mission is to write a piece of code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible.
+Your mission is to write code to calculate the price of any conceivable shopping basket (containing only books of the same series), giving as big a discount as possible.
 
 For example, how much does this basket of books cost?
 
@@ -26,36 +26,36 @@ For example, how much does this basket of books cost?
 
 One way of grouping these 8 books is:
 
-- 1 group of 5 --> 25% discount (1st,2nd,3rd,4th,5th)
-- +1 group of 3 --> 10% discount (1st,2nd,3rd)
+- 1 group of 5 (1st, 2nd,3rd, 4th, 5th)
+- 1 group of 3 (1st, 2nd, 3rd)
 
 This would give a total of:
 
 - 5 books at a 25% discount
-- +3 books at a 10% discount
+- 3 books at a 10% discount
 
 Resulting in:
 
-- 5 × (8 - 2.00) = 5 × 6.00 = $30.00
-- +3 × (8 - 0.80) = 3 × 7.20 = $21.60
+- 5 × (100% - 25%) * $8 = 5 × $6.00 = $30.00, plus
+- 3 × (100% - 10%) * $8 = 3 × $7.20 = $21.60
 
-For a total of $51.60
+Which equals $51.60.
 
 However, a different way to group these 8 books is:
 
-- 1 group of 4 books --> 20% discount  (1st,2nd,3rd,4th)
-- +1 group of 4 books --> 20% discount  (1st,2nd,3rd,5th)
+- 1 group of 4 books (1st, 2nd, 3rd, 4th)
+- 1 group of 4 books (1st, 2nd, 3rd, 5th)
 
 This would give a total of:
 
 - 4 books at a 20% discount
-- +4 books at a 20% discount
+- 4 books at a 20% discount
 
 Resulting in:
 
-- 4 × (8 - 1.60) = 4 × 6.40 = $25.60
-- +4 × (8 - 1.60) = 4 × 6.40 = $25.60
+- 4 × (100% - 20%) * $8 = 4 × $6.40 = $25.60, plus
+- 4 × (100% - 20%) * $8 = 4 × $6.40 = $25.60
 
-For a total of $51.20
+Which equals $51.20.
 
 And $51.20 is the price with the biggest discount.
diff --git a/exercises/practice/book-store/.meta/template.j2 b/exercises/practice/book-store/.meta/template.j2
index 6d3a79e8..735ecb64 100644
--- a/exercises/practice/book-store/.meta/template.j2
+++ b/exercises/practice/book-store/.meta/template.j2
@@ -20,5 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/bottle-song/.meta/template.j2 b/exercises/practice/bottle-song/.meta/template.j2
index 37e45402..7fd4a26d 100644
--- a/exercises/practice/bottle-song/.meta/template.j2
+++ b/exercises/practice/bottle-song/.meta/template.j2
@@ -18,6 +18,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endfor %}
     {% endfor %}
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/bowling/.docs/instructions.md b/exercises/practice/bowling/.docs/instructions.md
index 48a2fedc..ddce7ee4 100644
--- a/exercises/practice/bowling/.docs/instructions.md
+++ b/exercises/practice/bowling/.docs/instructions.md
@@ -36,7 +36,7 @@ Frame 3 is (9 + 0) = 9
 This means the current running total is 48.
 
 The tenth frame in the game is a special case.
-If someone throws a strike or a spare then they get a fill ball.
+If someone throws a spare or a strike then they get one or two fill balls respectively.
 Fill balls exist to calculate the total of the 10th frame.
 Scoring a strike or spare on the fill ball does not give the player more fill balls.
 The total value of the 10th frame is the total number of pins knocked down.
diff --git a/exercises/practice/circular-buffer/.meta/example.py b/exercises/practice/circular-buffer/.meta/example.py
index 8438b983..538cc7bc 100644
--- a/exercises/practice/circular-buffer/.meta/example.py
+++ b/exercises/practice/circular-buffer/.meta/example.py
@@ -25,7 +25,7 @@ class CircularBuffer:
         self.read_point = 0
         self.write_point = 0
 
-    # (protected) helper method to support python 2/3
+    # (protected) helper method
     def _update_buffer(self, data):
         try:
             self.buffer[self.write_point] = data
diff --git a/exercises/practice/clock/.meta/template.j2 b/exercises/practice/clock/.meta/template.j2
index f786b2f1..c8cee7e0 100644
--- a/exercises/practice/clock/.meta/template.j2
+++ b/exercises/practice/clock/.meta/template.j2
@@ -29,4 +29,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
 
     {% endfor %}
     {%- endfor %}
-{{ macros.footer() }}
diff --git a/exercises/practice/crypto-square/.meta/template.j2 b/exercises/practice/crypto-square/.meta/template.j2
index 5568ca09..c33812e6 100644
--- a/exercises/practice/crypto-square/.meta/template.j2
+++ b/exercises/practice/crypto-square/.meta/template.j2
@@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual(cipher_text(value), expected)
 
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/custom-set/.meta/template.j2 b/exercises/practice/custom-set/.meta/template.j2
index 13412265..d70a45c2 100644
--- a/exercises/practice/custom-set/.meta/template.j2
+++ b/exercises/practice/custom-set/.meta/template.j2
@@ -44,5 +44,4 @@ class {{ class_name }}Test(unittest.TestCase):
         {% endif %}
     {% endfor %}
     {% endfor %}
-
-{{ macros.footer() }}
+    
\ No newline at end of file
diff --git a/exercises/practice/darts/.docs/instructions.md b/exercises/practice/darts/.docs/instructions.md
index 7af7428a..70f0e53d 100644
--- a/exercises/practice/darts/.docs/instructions.md
+++ b/exercises/practice/darts/.docs/instructions.md
@@ -12,7 +12,7 @@ In our particular instance of the game, the target rewards 4 different amounts o
 - If the dart lands in the inner circle of the target, player earns 10 points.
 
 The outer circle has a radius of 10 units (this is equivalent to the total radius for the entire target), the middle circle a radius of 5 units, and the inner circle a radius of 1.
-Of course, they are all centered at the same point (that is, the circles are [concentric][] defined by the coordinates (0, 0).
+Of course, they are all centered at the same point — that is, the circles are [concentric][] defined by the coordinates (0, 0).
 
 Write a function that given a point in the target (defined by its [Cartesian coordinates][cartesian-coordinates] `x` and `y`, where `x` and `y` are [real][real-numbers]), returns the correct amount earned by a dart landing at that point.
 
diff --git a/exercises/practice/darts/.meta/template.j2 b/exercises/practice/darts/.meta/template.j2
index 09c0361b..35a5c37d 100644
--- a/exercises/practice/darts/.meta/template.j2
+++ b/exercises/practice/darts/.meta/template.j2
@@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     def test_{{ case["description"] | to_snake }}(self):
         self.assertEqual({{ case["property"] }}({{ case["input"]["x"] }}, {{ case["input"]["y"] }}), {{ case["expected"] }})
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/diamond/.meta/template.j2 b/exercises/practice/diamond/.meta/template.j2
index e9357cc7..5651fa6b 100644
--- a/exercises/practice/diamond/.meta/template.j2
+++ b/exercises/practice/diamond/.meta/template.j2
@@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         result = {{expected}}
         self.assertEqual({{case["property"]}}("{{letter}}"), result)
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/difference-of-squares/.meta/template.j2 b/exercises/practice/difference-of-squares/.meta/template.j2
index 62b8d8d2..ffac71e2 100644
--- a/exercises/practice/difference-of-squares/.meta/template.j2
+++ b/exercises/practice/difference-of-squares/.meta/template.j2
@@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             
         {% endfor %}
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/dominoes/.meta/template.j2 b/exercises/practice/dominoes/.meta/template.j2
index e75094e9..3ec60875 100644
--- a/exercises/practice/dominoes/.meta/template.j2
+++ b/exercises/practice/dominoes/.meta/template.j2
@@ -64,6 +64,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     def refute_correct_chain(self, input_dominoes, output_chain):
         msg = 'There should be no valid chain for {}'.format(input_dominoes)
         self.assertIsNone(output_chain, msg)
-
-{{ macros.footer() }}
-
diff --git a/exercises/practice/error-handling/.docs/instructions.md b/exercises/practice/error-handling/.docs/instructions.md
index 7bc1a085..25dd4d29 100644
--- a/exercises/practice/error-handling/.docs/instructions.md
+++ b/exercises/practice/error-handling/.docs/instructions.md
@@ -2,9 +2,7 @@
 
 Implement various kinds of error handling and resource management.
 
-An important point of programming is how to handle errors and close
-resources even if errors occur.
+An important point of programming is how to handle errors and close resources even if errors occur.
 
-This exercise requires you to handle various errors. Because error handling
-is rather programming language specific you'll have to refer to the tests
-for your track to see what's exactly required.
+This exercise requires you to handle various errors.
+Because error handling is rather programming language specific you'll have to refer to the tests for your track to see what's exactly required.
diff --git a/exercises/practice/etl/.meta/template.j2 b/exercises/practice/etl/.meta/template.j2
index 8e616d88..5669b6ed 100644
--- a/exercises/practice/etl/.meta/template.j2
+++ b/exercises/practice/etl/.meta/template.j2
@@ -15,6 +15,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases %}
     {{ test_case(case) }}
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/flatten-array/.meta/template.j2 b/exercises/practice/flatten-array/.meta/template.j2
index d1d93ad7..425e92fe 100644
--- a/exercises/practice/flatten-array/.meta/template.j2
+++ b/exercises/practice/flatten-array/.meta/template.j2
@@ -9,5 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}(inputs), expected)
 
     {% endfor %}
-
-{{ macros.footer() }}
+    
\ No newline at end of file
diff --git a/exercises/practice/food-chain/.meta/template.j2 b/exercises/practice/food-chain/.meta/template.j2
index e1851f7a..619ada74 100644
--- a/exercises/practice/food-chain/.meta/template.j2
+++ b/exercises/practice/food-chain/.meta/template.j2
@@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             self.assertEqual({{ case["property"]}}({{start_verse}}, {{end_verse}}), {{expected}})
 
         {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/gigasecond/.docs/introduction.md b/exercises/practice/gigasecond/.docs/introduction.md
index 74afaa99..18a3dc20 100644
--- a/exercises/practice/gigasecond/.docs/introduction.md
+++ b/exercises/practice/gigasecond/.docs/introduction.md
@@ -13,7 +13,7 @@ Then we can use metric system prefixes for writing large numbers of seconds in m
 - Perhaps you and your family would travel to somewhere exotic for two megaseconds (that's two million seconds).
 - And if you and your spouse were married for _a thousand million_ seconds, you would celebrate your one gigasecond anniversary.
 
-```exercism/note
+~~~~exercism/note
 If we ever colonize Mars or some other planet, measuring time is going to get even messier.
 If someone says "year" do they mean a year on Earth or a year on Mars?
 
@@ -21,4 +21,4 @@ The idea for this exercise came from the science fiction novel ["A Deepness in t
 In it the author uses the metric system as the basis for time measurements.
 
 [vinge-novel]: https://www.tor.com/2017/08/03/science-fiction-with-something-for-everyone-a-deepness-in-the-sky-by-vernor-vinge/
-```
+~~~~
diff --git a/exercises/practice/gigasecond/.meta/template.j2 b/exercises/practice/gigasecond/.meta/template.j2
index 31d032a4..87b0a148 100644
--- a/exercises/practice/gigasecond/.meta/template.j2
+++ b/exercises/practice/gigasecond/.meta/template.j2
@@ -9,6 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     def test_{{ case["description"] | to_snake }}(self):
         self.assertEqual({{ case["property"] }}({{ input }}), {{ expected }})
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/grep/.meta/template.j2 b/exercises/practice/grep/.meta/template.j2
index 6091fd66..593bbc07 100644
--- a/exercises/practice/grep/.meta/template.j2
+++ b/exercises/practice/grep/.meta/template.j2
@@ -48,5 +48,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% endfor %}
 
 {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/hexadecimal/.docs/instructions.md b/exercises/practice/hexadecimal/.docs/instructions.md
index 3e5afa77..a3c648e3 100644
--- a/exercises/practice/hexadecimal/.docs/instructions.md
+++ b/exercises/practice/hexadecimal/.docs/instructions.md
@@ -2,7 +2,6 @@
 
 Convert a hexadecimal number, represented as a string (e.g. "10af8c"), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion).
 
-On the web we use hexadecimal to represent colors, e.g. green: 008000,
-teal: 008080, navy: 000080).
+On the web we use hexadecimal to represent colors, e.g. green: 008000, teal: 008080, navy: 000080).
 
 The program should handle invalid hexadecimal strings.
diff --git a/exercises/practice/house/.meta/template.j2 b/exercises/practice/house/.meta/template.j2
index 9683c07a..b855685d 100644
--- a/exercises/practice/house/.meta/template.j2
+++ b/exercises/practice/house/.meta/template.j2
@@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             {{ case["expected"] }}
         )
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/isbn-verifier/.meta/template.j2 b/exercises/practice/isbn-verifier/.meta/template.j2
index ce973c49..47392cfb 100644
--- a/exercises/practice/isbn-verifier/.meta/template.j2
+++ b/exercises/practice/isbn-verifier/.meta/template.j2
@@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     def test_{{ case["description"] | to_snake }}(self):
         self.assertIs({{ case["property"] | to_snake }}("{{ case["input"]["isbn"] }}"), {{ case["expected"] }})
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/killer-sudoku-helper/.docs/instructions.md b/exercises/practice/killer-sudoku-helper/.docs/instructions.md
index 93469a09..2347ab26 100644
--- a/exercises/practice/killer-sudoku-helper/.docs/instructions.md
+++ b/exercises/practice/killer-sudoku-helper/.docs/instructions.md
@@ -56,8 +56,8 @@ The screenshots above have been generated using [F-Puzzles.com](https://www.f-pu
 
 [sudoku-rules]: https://masteringsudoku.com/sudoku-rules-beginners/
 [killer-guide]: https://masteringsudoku.com/killer-sudoku/
-[one-solution-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example1.png
-[four-solutions-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example2.png
-[not-possible-img]: https://media.githubusercontent.com/media/exercism/v3-files/main/julia/killer-sudoku-helper/example3.png
+[one-solution-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example1.png
+[four-solutions-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example2.png
+[not-possible-img]: https://exercism-v3-icons.s3.eu-west-2.amazonaws.com/images/exercises/killer-sudoku-helper/example3.png
 [clover-puzzle]: https://app.crackingthecryptic.com/sudoku/HqTBn3Pr6R
 [goodliffe-video]: https://youtu.be/c_NjEbFEeW0?t=1180
diff --git a/exercises/practice/knapsack/.meta/template.j2 b/exercises/practice/knapsack/.meta/template.j2
index 12760c7a..3c59a9e8 100644
--- a/exercises/practice/knapsack/.meta/template.j2
+++ b/exercises/practice/knapsack/.meta/template.j2
@@ -13,5 +13,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             self.assertEqual({{ case["property"] |to_snake }}({{weight}}, {{items}}), {{expected}})
         {% endif%}
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/leap/.meta/template.j2 b/exercises/practice/leap/.meta/template.j2
index 968306fc..66516c2b 100644
--- a/exercises/practice/leap/.meta/template.j2
+++ b/exercises/practice/leap/.meta/template.j2
@@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             {{- case ["expected"] }}
         )
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/ledger/.meta/config.json b/exercises/practice/ledger/.meta/config.json
index eeb5e2cc..5b9998ba 100644
--- a/exercises/practice/ledger/.meta/config.json
+++ b/exercises/practice/ledger/.meta/config.json
@@ -3,7 +3,9 @@
     "cmccandless"
   ],
   "contributors": [
+    "BethanyG",
     "Dog",
+    "IsaaG",
     "tqa236"
   ],
   "files": {
diff --git a/exercises/practice/ledger/.meta/template.j2 b/exercises/practice/ledger/.meta/template.j2
new file mode 100644
index 00000000..8936a2ca
--- /dev/null
+++ b/exercises/practice/ledger/.meta/template.j2
@@ -0,0 +1,22 @@
+{%- import "generator_macros.j2" as macros with context -%}
+{{ macros.header(imports=["format_entries", "create_entry"]) }}
+
+class {{ exercise | camel_case }}Test(unittest.TestCase):
+    maxDiff = 5000
+    {%- for case in cases %}
+
+    def test_{{ case["description"] | to_snake }}(self):
+        currency = '{{- case["input"]["currency"] }}'
+        locale = '{{- case["input"]["locale"] }}'
+        entries = [
+            {%- for entry in case["input"]["entries"] %}
+            create_entry('{{ entry["date"] }}', '{{ entry["description"] }}', {{ entry["amountInCents"] }}),
+            {%- endfor %}
+        ]
+        expected = '\n'.join([
+            {%- for line in case["expected"] %}
+            '{{- line -}}',
+            {%- endfor %}
+        ])
+        self.assertEqual(format_entries(currency, locale, entries), expected)
+    {%- endfor %}
diff --git a/exercises/practice/ledger/ledger.py b/exercises/practice/ledger/ledger.py
index 493109b8..c0dcf94f 100644
--- a/exercises/practice/ledger/ledger.py
+++ b/exercises/practice/ledger/ledger.py
@@ -296,3 +296,4 @@ def format_entries(currency, locale, entries):
                     change_str = ' ' + change_str
                 table += change_str
         return table
+
diff --git a/exercises/practice/linked-list/.docs/instructions.md b/exercises/practice/linked-list/.docs/instructions.md
index a47942d7..edf4055b 100644
--- a/exercises/practice/linked-list/.docs/instructions.md
+++ b/exercises/practice/linked-list/.docs/instructions.md
@@ -13,7 +13,7 @@ Sometimes a station gets closed down, and in that case the station needs to be r
 
 The size of a route is measured not by how far the train travels, but by how many stations it stops at.
 
-```exercism/note
+~~~~exercism/note
 The linked list is a fundamental data structure in computer science, often used in the implementation of other data structures.
 As the name suggests, it is a list of nodes that are linked together.
 It is a list of "nodes", where each node links to its neighbor or neighbors.
@@ -23,4 +23,4 @@ In a **doubly linked list** each node links to both the node that comes before,
 If you want to dig deeper into linked lists, check out [this article][intro-linked-list] that explains it using nice drawings.
 
 [intro-linked-list]: https://medium.com/basecs/whats-a-linked-list-anyway-part-1-d8b7e6508b9d
-```
+~~~~
diff --git a/exercises/practice/list-ops/.meta/template.j2 b/exercises/practice/list-ops/.meta/template.j2
index cc28280f..3ce6a303 100644
--- a/exercises/practice/list-ops/.meta/template.j2
+++ b/exercises/practice/list-ops/.meta/template.j2
@@ -52,6 +52,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/matching-brackets/.meta/template.j2 b/exercises/practice/matching-brackets/.meta/template.j2
index bc8f0830..0d43598b 100644
--- a/exercises/practice/matching-brackets/.meta/template.j2
+++ b/exercises/practice/matching-brackets/.meta/template.j2
@@ -8,7 +8,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         {%- set expected = case["expected"] %}
         self.assertEqual({{ case["property"] | to_snake }}({{ "{!r}".format(value) }}), {{ expected }})
     {% endfor %}
-
-{{ macros.footer() }}
-
-
diff --git a/exercises/practice/nucleotide-count/.docs/instructions.md b/exercises/practice/nucleotide-count/.docs/instructions.md
index 57667134..548d9ba5 100644
--- a/exercises/practice/nucleotide-count/.docs/instructions.md
+++ b/exercises/practice/nucleotide-count/.docs/instructions.md
@@ -1,10 +1,12 @@
 # Instructions
 
-Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed. All known life depends on DNA!
+Each of us inherits from our biological parents a set of chemical instructions known as DNA that influence how our bodies are constructed.
+All known life depends on DNA!
 
 > Note: You do not need to understand anything about nucleotides or DNA to complete this exercise.
 
-DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine. A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important!
+DNA is a long chain of other chemicals and the most important are the four nucleotides, adenine, cytosine, guanine and thymine.
+A single DNA chain can contain billions of these four nucleotides and the order in which they occur is important!
 We call the order of these nucleotides in a bit of DNA a "DNA sequence".
 
 We represent a DNA sequence as an ordered collection of these four nucleotides and a common way to do that is with a string of characters such as "ATTACG" for a DNA sequence of 6 nucleotides.
diff --git a/exercises/practice/octal/.docs/instructions.md b/exercises/practice/octal/.docs/instructions.md
index 81f10838..65ce135c 100644
--- a/exercises/practice/octal/.docs/instructions.md
+++ b/exercises/practice/octal/.docs/instructions.md
@@ -1,11 +1,9 @@
 # Instructions
 
-Convert an octal number, represented as a string (e.g. '1735263'), to its
-decimal equivalent using first principles (i.e. no, you may not use built-in or
-external libraries to accomplish the conversion).
+Convert an octal number, represented as a string (e.g. '1735263'), to its decimal equivalent using first principles (i.e. no, you may not use built-in or external libraries to accomplish the conversion).
 
-Implement octal to decimal conversion.  Given an octal input
-string, your program should produce a decimal output.
+Implement octal to decimal conversion.
+Given an octal input string, your program should produce a decimal output.
 
 ## Note
 
diff --git a/exercises/practice/pangram/.docs/instructions.md b/exercises/practice/pangram/.docs/instructions.md
index d5698bc2..817c872d 100644
--- a/exercises/practice/pangram/.docs/instructions.md
+++ b/exercises/practice/pangram/.docs/instructions.md
@@ -5,4 +5,4 @@ Your task is to figure out if a sentence is a pangram.
 A pangram is a sentence using every letter of the alphabet at least once.
 It is case insensitive, so it doesn't matter if a letter is lower-case (e.g. `k`) or upper-case (e.g. `K`).
 
-For this exercise we only use the basic letters used in the English alphabet: `a` to `z`.
+For this exercise, a sentence is a pangram if it contains each of the 26 letters in the English alphabet.
diff --git a/exercises/practice/pangram/.docs/introduction.md b/exercises/practice/pangram/.docs/introduction.md
index d38fa341..32b6f1fc 100644
--- a/exercises/practice/pangram/.docs/introduction.md
+++ b/exercises/practice/pangram/.docs/introduction.md
@@ -7,10 +7,10 @@ To give a comprehensive sense of the font, the random sentences should use **all
 They're running a competition to get suggestions for sentences that they can use.
 You're in charge of checking the submissions to see if they are valid.
 
-```exercism/note
+~~~~exercism/note
 Pangram comes from Greek, παν γράμμα, pan gramma, which means "every letter".
 
 The best known English pangram is:
 
 > The quick brown fox jumps over the lazy dog.
-```
+~~~~
diff --git a/exercises/practice/pangram/.meta/template.j2 b/exercises/practice/pangram/.meta/template.j2
index 7e93a443..a365b456 100644
--- a/exercises/practice/pangram/.meta/template.j2
+++ b/exercises/practice/pangram/.meta/template.j2
@@ -22,4 +22,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-{{ macros.footer() }}
diff --git a/exercises/practice/parallel-letter-frequency/.docs/instructions.md b/exercises/practice/parallel-letter-frequency/.docs/instructions.md
index a5b936c5..85abcf86 100644
--- a/exercises/practice/parallel-letter-frequency/.docs/instructions.md
+++ b/exercises/practice/parallel-letter-frequency/.docs/instructions.md
@@ -2,7 +2,6 @@
 
 Count the frequency of letters in texts using parallel computation.
 
-Parallelism is about doing things in parallel that can also be done
-sequentially. A common example is counting the frequency of letters.
-Create a function that returns the total frequency of each letter in a
-list of texts and that employs parallelism.
+Parallelism is about doing things in parallel that can also be done sequentially.
+A common example is counting the frequency of letters.
+Create a function that returns the total frequency of each letter in a list of texts and that employs parallelism.
diff --git a/exercises/practice/pig-latin/.meta/template.j2 b/exercises/practice/pig-latin/.meta/template.j2
index f516f6f1..fd284824 100644
--- a/exercises/practice/pig-latin/.meta/template.j2
+++ b/exercises/practice/pig-latin/.meta/template.j2
@@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             
         {% endfor %}
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/poker/.meta/template.j2 b/exercises/practice/poker/.meta/template.j2
index 099626a1..86e2e879 100644
--- a/exercises/practice/poker/.meta/template.j2
+++ b/exercises/practice/poker/.meta/template.j2
@@ -8,5 +8,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual(
             {{ case["property"] | to_snake }}({{ input["hands"] }}), {{ case["expected"] }})
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/protein-translation/.meta/template.j2 b/exercises/practice/protein-translation/.meta/template.j2
index 5cc931bc..a8391c37 100644
--- a/exercises/practice/protein-translation/.meta/template.j2
+++ b/exercises/practice/protein-translation/.meta/template.j2
@@ -9,5 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}(value), expected)
 
     {% endfor %}
-
-{{ macros.footer() }}
+    
\ No newline at end of file
diff --git a/exercises/practice/pythagorean-triplet/.meta/template.j2 b/exercises/practice/pythagorean-triplet/.meta/template.j2
index b3836a54..20d750c7 100644
--- a/exercises/practice/pythagorean-triplet/.meta/template.j2
+++ b/exercises/practice/pythagorean-triplet/.meta/template.j2
@@ -2,14 +2,9 @@
 
 {{ macros.header() }}
 
-# Python 2/3 compatibility
-if not hasattr(unittest.TestCase, 'assertCountEqual'):
-    unittest.TestCase.assertCountEqual = unittest.TestCase.assertItemsEqual
 
 class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
     def test_{{ case["description"] | to_snake }}(self):
         self.assertCountEqual({{ case["property"] | to_snake }}({{ case["input"]["n"] }}), {{ case["expected"] }})
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/queen-attack/.docs/instructions.md b/exercises/practice/queen-attack/.docs/instructions.md
index dce0fc29..ad7ea954 100644
--- a/exercises/practice/queen-attack/.docs/instructions.md
+++ b/exercises/practice/queen-attack/.docs/instructions.md
@@ -21,5 +21,5 @@ So if you are told the white queen is at `c5` (zero-indexed at column 2, row 3)
   a b c d e f g h
 ```
 
-You are also be able to answer whether the queens can attack each other.
+You are also able to answer whether the queens can attack each other.
 In this case, that answer would be yes, they can, because both pieces share a diagonal.
diff --git a/exercises/practice/rail-fence-cipher/.meta/template.j2 b/exercises/practice/rail-fence-cipher/.meta/template.j2
index 2c11e606..2698af33 100644
--- a/exercises/practice/rail-fence-cipher/.meta/template.j2
+++ b/exercises/practice/rail-fence-cipher/.meta/template.j2
@@ -12,5 +12,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             
         {% endfor %}
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/raindrops/.meta/template.j2 b/exercises/practice/raindrops/.meta/template.j2
index d1b0d2f8..1e025b8b 100644
--- a/exercises/practice/raindrops/.meta/template.j2
+++ b/exercises/practice/raindrops/.meta/template.j2
@@ -16,5 +16,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             "{{ case["expected"] }}"
         )
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/rectangles/.meta/template.j2 b/exercises/practice/rectangles/.meta/template.j2
index 0d82cb84..c9b1f623 100644
--- a/exercises/practice/rectangles/.meta/template.j2
+++ b/exercises/practice/rectangles/.meta/template.j2
@@ -8,5 +8,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}({{ input_strings }}), {{ case["expected"] }})
 
     {% endfor %}
-
-{{ macros.footer() }}
+    
\ No newline at end of file
diff --git a/exercises/practice/resistor-color-expert/.docs/instructions.md b/exercises/practice/resistor-color-expert/.docs/instructions.md
new file mode 100644
index 00000000..f0880164
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.docs/instructions.md
@@ -0,0 +1,76 @@
+# Instructions
+
+In this exercise, you are going to create a helpful program so that you don't have to remember the values of the bands.
+The program will take 1, 4, or 5 colors as input, and outputs the correct value, in ohms.
+The color bands are encoded as follows:
+
+- Black: 0
+- Brown: 1
+- Red: 2
+- Orange: 3
+- Yellow: 4
+- Green: 5
+- Blue: 6
+- Violet: 7
+- Grey: 8
+- White: 9
+
+In `resistor-color trio` you decoded the first three colors.
+For instance: orange-orange-brown translated to the main value `330`.
+In this exercise you will need to add _tolerance_ to the mix.
+Tolerance is the maximum amount that a value can be above or below the main value.
+For example, if the last band is green, the maximum tolerance will be ±0.5%.
+
+The tolerance band will have one of these values:
+
+- Grey - 0.05%
+- Violet - 0.1%
+- Blue - 0.25%
+- Green - 0.5%
+- Brown - 1%
+- Red - 2%
+- Gold - 5%
+- Silver - 10%
+
+The four-band resistor is built up like this:
+
+| Band_1  | Band_2  | Band_3     | band_4    |
+| ------- | ------- | ---------- | --------- |
+| Value_1 | Value_2 | Multiplier | Tolerance |
+
+Meaning
+
+- orange-orange-brown-green would be 330 ohms with a ±0.5% tolerance.
+- orange-orange-red would-grey would be 3300 ohms with ±0.005% tolerance.
+The difference between a four and five-band resistor is that the five-band resistor has an extra band to indicate a more precise value.
+
+| Band_1  | Band_2  | Band_3  | Band_4     | band_5    |
+| ------- | ------- | ------- | ---------- | --------- |
+| Value_1 | Value_2 | Value_3 | Multiplier | Tolerance |
+
+Meaning
+
+- orange-orange-orange-black-green would be 330 ohms with a ±0.5% tolerance.
+There are also one band resistors.
+This type of resistor only has the color black and has a value of 0.
+
+This exercise is about translating the resistor band colors into a label:
+
+"... ohms ...%"
+
+So an input of "orange", "orange", "black, green" should return:
+
+"33 ohms ±0.5%"
+
+When there are more than a thousand ohms, we say "kiloohms".
+ That's similar to saying "kilometer" for 1000 meters, and "kilograms" for 1000 grams.
+
+So an input of "orange", "orange", "orange", grey should return:
+
+"33 kiloohms ±0.05%"
+
+When there are more than a million ohms, we say "megaohms".
+
+So an input of "orange", "orange", "orange", "red" should return:
+
+"33 megaohms ±2%"
diff --git a/exercises/practice/resistor-color-expert/.docs/introduction.md b/exercises/practice/resistor-color-expert/.docs/introduction.md
new file mode 100644
index 00000000..fd9e05ef
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.docs/introduction.md
@@ -0,0 +1,10 @@
+# Introduction
+
+If you want to build something using a Raspberry Pi, you'll probably use _resistors_.
+Like the previous `Resistor Color Duo` and `Resistor Color Trio` exercises, you will be translating resistor color bands to human-readable labels.
+
+- Each resistor has a resistance value.
+- Resistors are small - so small in fact that if you printed the resistance value on them, it would be hard to read.
+  To get around this problem, manufacturers print color-coded bands onto the resistors to denote their resistance values.
+- Each band acts as a digit of a number.
+  For example, if they printed a brown band (value 1) followed by a green band (value 5), it would translate to the number 15.
diff --git a/exercises/practice/resistor-color-expert/.meta/config.json b/exercises/practice/resistor-color-expert/.meta/config.json
new file mode 100644
index 00000000..edf50b4a
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.meta/config.json
@@ -0,0 +1,20 @@
+{
+  "authors": [
+    "meatball133",
+    "bethanyg"
+  ],
+  "files": {
+    "solution": [
+      "resistor_color_expert.py"
+    ],
+    "test": [
+      "resistor_color_expert_test.py"
+    ],
+    "example": [
+      ".meta/example.py"
+    ]
+  },
+  "blurb": "Convert color codes as used on resistors with different bands to a human-readable label.",
+  "source": "Based on earlier resistor color exercises made by Erik Schierboom and Maud de Vries",
+  "source_url": "https://github.com/exercism/problem-specifications/issues/1464"
+}
diff --git a/exercises/practice/resistor-color-expert/.meta/example.py b/exercises/practice/resistor-color-expert/.meta/example.py
new file mode 100644
index 00000000..0fd6e42f
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.meta/example.py
@@ -0,0 +1,49 @@
+COLORS = [
+    'black',
+    'brown',
+    'red',
+    'orange',
+    'yellow',
+    'green',
+    'blue',
+    'violet',
+    'grey',
+    'white'
+]
+
+COLORS_TOLERANCE = {
+    'brown': 1,
+    'red': 2,
+    'green': 0.5,
+    'blue': 0.25,
+    'violet': 0.1,
+    'grey': 0.05,
+    'gold': 5,
+    'silver': 10
+}
+
+
+def resistor_label(colors):
+    if len(colors) == 1:
+        return f'0 ohms'
+    elif len(colors) == 4:
+        value = 10 * COLORS.index(colors[0]) + COLORS.index(colors[1])
+        value *= 10 ** COLORS.index(colors[2])
+        value, unit = color_code(value)
+        value = int(value) if value.is_integer() else value
+        return f'{value} {unit} ±{COLORS_TOLERANCE[colors[3]]}%'
+    else:
+        value = 100 * COLORS.index(colors[0]) + 10 * COLORS.index(colors[1]) + COLORS.index(colors[2])
+        value *= 10 ** COLORS.index(colors[3])
+        value, unit = color_code(value)
+        value = int(value) if value.is_integer() else value
+        return f'{value} {unit} ±{COLORS_TOLERANCE[colors[4]]}%'
+
+
+def color_code(color):
+    if color < 1000:
+        return color / 1, 'ohms'
+    elif color < 1000000:
+        return color / 1000, 'kiloohms'
+    else:
+        return color / 1000000, 'megaohms'
\ No newline at end of file
diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert.py b/exercises/practice/resistor-color-expert/resistor_color_expert.py
new file mode 100644
index 00000000..f36881c5
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/resistor_color_expert.py
@@ -0,0 +1,2 @@
+def resistor_label(colors):
+    pass
diff --git a/exercises/practice/resistor-color/.meta/template.j2 b/exercises/practice/resistor-color/.meta/template.j2
index e186e356..643a3b69 100644
--- a/exercises/practice/resistor-color/.meta/template.j2
+++ b/exercises/practice/resistor-color/.meta/template.j2
@@ -14,5 +14,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] | to_snake }}(), expected)
     {%- endif -%}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/rest-api/.meta/template.j2 b/exercises/practice/rest-api/.meta/template.j2
index 9bd4e751..aa7afe8f 100644
--- a/exercises/practice/rest-api/.meta/template.j2
+++ b/exercises/practice/rest-api/.meta/template.j2
@@ -20,5 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         expected = {{ case["expected"] }}
         self.assertDictEqual(json.loads(response), expected)
     {% endfor %}{% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/reverse-string/.meta/template.j2 b/exercises/practice/reverse-string/.meta/template.j2
index f7e1e41f..f76cc28e 100644
--- a/exercises/practice/reverse-string/.meta/template.j2
+++ b/exercises/practice/reverse-string/.meta/template.j2
@@ -11,5 +11,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             "{{- case ["expected"] }}"
         )
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/rna-transcription/.meta/template.j2 b/exercises/practice/rna-transcription/.meta/template.j2
index 6be1210a..ebd16751 100644
--- a/exercises/practice/rna-transcription/.meta/template.j2
+++ b/exercises/practice/rna-transcription/.meta/template.j2
@@ -9,5 +9,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             '{{ case["expected"] }}'
         )
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/robot-name/.approaches/config.json b/exercises/practice/robot-name/.approaches/config.json
new file mode 100644
index 00000000..c71235ec
--- /dev/null
+++ b/exercises/practice/robot-name/.approaches/config.json
@@ -0,0 +1,21 @@
+{
+  "introduction": {
+    "authors": ["safwansamsudeen"]
+  },
+  "approaches": [
+    {
+      "uuid": "94d82d63-0a20-44df-ad9b-14aba7c076ae",
+      "slug": "mass-name-generation",
+      "title": "Mass Name Generation",
+      "blurb": "Select from all possible names",
+      "authors": ["safwansamsudeen"]
+    },
+    {
+      "uuid": "20ca6d56-ab48-46be-a04e-98736109be0a",
+      "slug": "name-on-the-fly",
+      "title": "Find name on the fly",
+      "blurb": "Generate name and check that it hasn't been used",
+      "authors": ["safwansamsudeen"]
+    }
+  ]
+}
diff --git a/exercises/practice/robot-name/.approaches/introduction.md b/exercises/practice/robot-name/.approaches/introduction.md
new file mode 100644
index 00000000..9dc810ed
--- /dev/null
+++ b/exercises/practice/robot-name/.approaches/introduction.md
@@ -0,0 +1,59 @@
+# Introduction
+Robot Name in Python is an interesting exercise for practising randomness.
+
+## General Guidance
+Two ways immedietely come to mind: generate all the possible names and then return them sequentially, or generate a random name and ensure that it's not been previously used.
+
+Randomness can be a little, well, random, so **it's very easy to have an incorrect solution and still pass the tests**. It's strongly recommended to submit your solution for Code Review.
+
+## Approach: mass name generation
+We'd first have to generate all the possible names, shuffle them, and then use `next` (the simplest way) or maintain a `current_index` and get the name.
+Here's a possible way to do it:
+
+```python
+from itertools import product
+from random import shuffle
+from string import ascii_uppercase as letters
+
+letter_pairs = (''.join(p) for p in product(letters, letters))
+numbers = (str(i).zfill(3) for i in range(1000))
+names = [l + n for l, n in product(letter_pairs, numbers)]
+shuffle(names)
+NAMES = iter(names)
+class Robot(object):
+    def __init__(self):
+        self.reset()
+    def reset(self):
+        self.name = next(NAMES)
+```
+Note that selecting randomly from the list of all names would be incorrect, as there's a possibility of the name being repeated.
+For more detail and explanation of the code, [read here][approach-mass-name-generation].
+
+## Approach: name on the fly
+Another approach is to generate the name on the fly and add it to a cache or a store, and checking if the generated name hasn't been used previously.
+
+
+A possible way to implement this:
+```python
+from string import ascii_uppercase, digits
+from random import choices
+
+
+cache = set()
+
+
+class Robot:
+    def __get_name(self): 
+        return ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))
+
+    def reset(self):
+        while (name := self.__get_name()) in cache:
+            pass
+        cache.add(name)
+        self.name = name
+
+    def __init__(self): 
+        self.reset()
+```
+
+For more detail and different ways to implement this, [read here][approach-name-on-the-fly].
\ No newline at end of file
diff --git a/exercises/practice/robot-name/.approaches/mass-name-generation/content.md b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md
new file mode 100644
index 00000000..6e5fad83
--- /dev/null
+++ b/exercises/practice/robot-name/.approaches/mass-name-generation/content.md
@@ -0,0 +1,49 @@
+# Mass Name Generation
+We'd first have to generate all the possible names, shuffle them, and then use `next` (the simplest way) or maintain a `current_index` and get the name.
+Note that selecting randomly from the list of all names would be incorrect, as there's a possibility of the name being repeated.
+
+Here's a possible way to do it:
+
+```python
+from itertools import product
+from random import shuffle
+from string import ascii_uppercase
+
+letter_pairs = (''.join(p) for p in product(ascii_uppercase, ascii_uppercase))
+numbers = (str(i).zfill(3) for i in range(1000))
+names = [l + n for l, n in product(letter_pairs, numbers)]
+
+shuffle(names)
+NAMES = iter(names)
+
+class Robot(object):
+    def __init__(self):
+        self.reset()
+    def reset(self):
+        self.name = next(NAMES)
+```
+
+The first few lines of the mass name generation uses [`itertools.product`][itertools-product].
+The resultant code is a simplification of:
+```python
+letter_pairs = (''.join((l1, l2)) for l1 in ascii_uppercase for l2 in ascii_uppercase)
+numbers = (str(i).zfill(3) for i in range(1000))
+names = [l + n for l in letter_pairs for n in numbers]
+```
+
+After the name generation, the names are shuffled - using the [default `seed`][random-seed] in the `random` module (the current timestamp). 
+When the tests reseed `random`, this has no effect as the names were shuffled before that.
+
+We then set `NAMES` to the iterable of names, and in `reset`, set the robot's name to the  `next(name)`. 
+If you'd like, read more on [`iter` and `next`][iter-and-next].
+
+Unlike the on the fly approach, this has a relatively short "generation" time, because we're merely giving the `next` name instead of generating it.
+However, this has a huge startup memory and time cost, as 676,000 strings have to be calculated and stored. 
+For an approximate calculation, 676,000 strings * 5 characters / string * 1 byte / character gives 3380000 bytes or 3.38 MB of RAM - and that's just the memory aspect of it.
+Sounds small, but it's relatively very expensive at the beginning.
+
+Thus, this approach is inefficient in cases where only a small amount of names are needed _and_ the time to set/reset the robot isn't crucial.
+
+[random-seed]: https://docs.python.org/3/library/random.html#random.seed
+[iter-and-next]: https://www.programiz.com/python-programming/methods/built-in/iter
+[itertools-product]: https://www.hackerrank.com/challenges/itertools-product/problem
\ No newline at end of file
diff --git a/exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt b/exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt
new file mode 100644
index 00000000..34123094
--- /dev/null
+++ b/exercises/practice/robot-name/.approaches/mass-name-generation/snippet.txt
@@ -0,0 +1,8 @@
+...
+names = [l + n for l, n in product(letter_pairs, numbers)]
+shuffle(names)
+NAMES = iter(names)
+
+class Robot(object):
+    def reset(self):
+        self.name = next(NAMES)
\ No newline at end of file
diff --git a/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md b/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md
new file mode 100644
index 00000000..0aa9f9a3
--- /dev/null
+++ b/exercises/practice/robot-name/.approaches/name-on-the-fly/content.md
@@ -0,0 +1,52 @@
+# Find name on the fly
+We generate the name on the fly and add it to a cache or a store, and checking if the generated name hasn't been used previously.
+
+A possible way to implement this:
+```python
+from string import ascii_uppercase, digits
+from random import choices
+
+cache = set()
+
+
+class Robot:
+    def __get_name(self): 
+        return ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))
+
+    def reset(self):
+        while (name := self.__get_name()) in cache:
+            pass
+        cache.add(name)
+        self.name = name
+
+    def __init__(self): 
+        self.reset()
+```
+We use a `set` for the cache as it has a low access time, and we don't need the preservation of order or the ability to be indexed.
+
+This way is merely one of the many to generate the name. 
+Another way might be to use `randrange` along with `zfill` for the number part, and a double `random.choice` / `random.choice` on `itertools.product` to generate the letter part.
+This is the shortest way, and best utilizes the Python standard library.
+
+As we're using a `while` loop to check for the name generation, it's convenient to store the local `name` using the [walrus operator][walrus-operator].
+It's also possible to find the name before the loop and find it again inside the loop, but that would unnecessary repetition.
+A helper method ([private][private-helper-methods] in this case) makes your code cleaner, but it's equally valid to have the code in the loop itself:
+```python
+def reset(self):
+    while (name := ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))) in cache:
+        pass
+    cache.add(name)
+    self.name = name
+```
+
+We call `reset` from `__init__` - it's syntactically valid to do it the other way round, but it's not considered good practice to call [dunder methods][dunder-methods] directly.
+
+This has almost no startup time and memory, apart from declaring an empty `set`.
+Note that the _generation_ time is the same as the mass generation approach, as a similar method is used.
+However, as the name is generated at the time of setting/resetting, the method time itself is higher.
+
+In the long run, if many names are generated, this is inefficient, since collisions will start being generated more often than unique names. 
+
+[walrus-operator]: https://realpython.com/python-walrus-operator/
+[private-helper-methods]: https://www.geeksforgeeks.org/private-methods-in-python/
+[dunder-methods]: https://dbader.org/blog/python-dunder-methods
\ No newline at end of file
diff --git a/exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt b/exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt
new file mode 100644
index 00000000..e6ab23e7
--- /dev/null
+++ b/exercises/practice/robot-name/.approaches/name-on-the-fly/snippet.txt
@@ -0,0 +1,8 @@
+cache = set()
+class Robot:
+    def reset(self):
+        while (name := ''.join(choices(ascii_uppercase, k=2) + choices(digits, k=3))) in cache:
+            pass
+        cache.add(name)
+        self.name = name
+    def __init__(self): self.reset()
\ No newline at end of file
diff --git a/exercises/practice/roman-numerals/.docs/instructions.md b/exercises/practice/roman-numerals/.docs/instructions.md
index bb7e909d..247ea089 100644
--- a/exercises/practice/roman-numerals/.docs/instructions.md
+++ b/exercises/practice/roman-numerals/.docs/instructions.md
@@ -36,6 +36,6 @@ In Roman numerals 1990 is MCMXC:
 2000=MM
 8=VIII
 
-Learn more about [Roman numberals on Wikipedia][roman-numerals].
+Learn more about [Roman numerals on Wikipedia][roman-numerals].
 
 [roman-numerals]: https://wiki.imperivm-romanvm.com/wiki/Roman_Numerals
diff --git a/exercises/practice/rotational-cipher/.meta/template.j2 b/exercises/practice/rotational-cipher/.meta/template.j2
index 5a846415..7db7871e 100644
--- a/exercises/practice/rotational-cipher/.meta/template.j2
+++ b/exercises/practice/rotational-cipher/.meta/template.j2
@@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             self.assertEqual({{ case["property"] }}("{{ text }}", {{ shiftKey }}), "{{ expected }}")
             
         {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/saddle-points/.docs/instructions.md b/exercises/practice/saddle-points/.docs/instructions.md
index d861388e..c585568b 100644
--- a/exercises/practice/saddle-points/.docs/instructions.md
+++ b/exercises/practice/saddle-points/.docs/instructions.md
@@ -5,20 +5,22 @@ Your task is to find the potential trees where you could build your tree house.
 The data company provides the data as grids that show the heights of the trees.
 The rows of the grid represent the east-west direction, and the columns represent the north-south direction.
 
-An acceptable tree will be the the largest in its row, while being the smallest in its column.
+An acceptable tree will be the largest in its row, while being the smallest in its column.
 
 A grid might not have any good trees at all.
 Or it might have one, or even several.
 
 Here is a grid that has exactly one candidate tree.
 
+```text
     1  2  3  4
   |-----------
 1 | 9  8  7  8
 2 | 5  3  2  4  <--- potential tree house at row 2, column 1, for tree with height 5
 3 | 6  6  7  1
+```
 
-- Row 2 has values 5, 3, and 1. The largest value is 5.
+- Row 2 has values 5, 3, 2, and 4. The largest value is 5.
 - Column 1 has values 9, 5, and 6. The smallest value is 5.
 
 So the point at `[2, 1]` (row: 2, column: 1) is a great spot for a tree house.
diff --git a/exercises/practice/saddle-points/.docs/introduction.md b/exercises/practice/saddle-points/.docs/introduction.md
index b582efbd..34b2c77e 100644
--- a/exercises/practice/saddle-points/.docs/introduction.md
+++ b/exercises/practice/saddle-points/.docs/introduction.md
@@ -1,9 +1,11 @@
 # Introduction
 
-You are planning on building a tree house in the woods near your house so that you can watch the sun rise and set.
+You plan to build a tree house in the woods near your house so that you can watch the sun rise and set.
 
-You've obtained data from a local survey company that shows the heights of all the trees in each rectangular section of the map.
-You need to analyze each grid on the map to find the perfect tree for your tree house.
+You've obtained data from a local survey company that show the height of every tree in each rectangular section of the map.
+You need to analyze each grid on the map to find good trees for your tree house.
 
-The best tree will be the tallest tree compared to all the other trees to the east and west, so that you have the best possible view of the sunrises and sunsets.
-You don't like climbing too much, so the perfect tree will also be the shortest among all the trees to the north and to the south.
+A good tree is both:
+
+- taller than every tree to the east and west, so that you have the best possible view of the sunrises and sunsets.
+- shorter than every tree to the north and south, to minimize the amount of tree climbing.
diff --git a/exercises/practice/scale-generator/.meta/template.j2 b/exercises/practice/scale-generator/.meta/template.j2
index b65b5f09..da566b8a 100644
--- a/exercises/practice/scale-generator/.meta/template.j2
+++ b/exercises/practice/scale-generator/.meta/template.j2
@@ -25,6 +25,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for supercase in cases %}
     {{ test_supercase(supercase) }}
     {% endfor %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/scrabble-score/.meta/template.j2 b/exercises/practice/scrabble-score/.meta/template.j2
index f122b916..ea6c9ae2 100644
--- a/exercises/practice/scrabble-score/.meta/template.j2
+++ b/exercises/practice/scrabble-score/.meta/template.j2
@@ -13,6 +13,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {% for case in cases -%}
     {{ test_case(case) }}
     {% endfor %}
-
-
-{{ macros.footer() }}
+    
\ No newline at end of file
diff --git a/exercises/practice/secret-handshake/.docs/instructions.md b/exercises/practice/secret-handshake/.docs/instructions.md
index 77136cf0..d2120b9b 100644
--- a/exercises/practice/secret-handshake/.docs/instructions.md
+++ b/exercises/practice/secret-handshake/.docs/instructions.md
@@ -43,5 +43,6 @@ jump, double blink
 
 ~~~~exercism/note
 If you aren't sure what binary is or how it works, check out [this binary tutorial][intro-to-binary].
+
 [intro-to-binary]: https://medium.com/basecs/bits-bytes-building-with-binary-13cb4289aafa
 ~~~~
diff --git a/exercises/practice/secret-handshake/.meta/template.j2 b/exercises/practice/secret-handshake/.meta/template.j2
index 459e9c98..f5791a8b 100644
--- a/exercises/practice/secret-handshake/.meta/template.j2
+++ b/exercises/practice/secret-handshake/.meta/template.j2
@@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     def test_{{ case["description"] | to_snake }}(self):
         self.assertEqual({{ case["property"] }}("{{ plugins.to_binary(case["input"]["number"]) }}"), {{ case["expected"] }})
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/sieve/.docs/instructions.md b/exercises/practice/sieve/.docs/instructions.md
index ec14620c..3adf1d55 100644
--- a/exercises/practice/sieve/.docs/instructions.md
+++ b/exercises/practice/sieve/.docs/instructions.md
@@ -18,11 +18,11 @@ Then you repeat the following steps:
 You keep repeating these steps until you've gone through every number in your list.
 At the end, all the unmarked numbers are prime.
 
-```exercism/note
+~~~~exercism/note
 [Wikipedia's Sieve of Eratosthenes article][eratosthenes] has a useful graphic that explains the algorithm.
 
 The tests don't check that you've implemented the algorithm, only that you've come up with the correct list of primes.
 A good first test is to check that you do not use division or remainder operations.
 
 [eratosthenes]: https://en.wikipedia.org/wiki/Sieve_of_Eratosthenes
-```
+~~~~
diff --git a/exercises/practice/sieve/.meta/template.j2 b/exercises/practice/sieve/.meta/template.j2
index e26e4e4c..68a9db82 100644
--- a/exercises/practice/sieve/.meta/template.j2
+++ b/exercises/practice/sieve/.meta/template.j2
@@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] }}({{ case["input"]["limit"] }}), {{ case["expected"] }})
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/simple-cipher/.meta/template.j2 b/exercises/practice/simple-cipher/.meta/template.j2
index ca20aa30..ce9ad3e4 100644
--- a/exercises/practice/simple-cipher/.meta/template.j2
+++ b/exercises/practice/simple-cipher/.meta/template.j2
@@ -59,5 +59,3 @@ class {{ cases["description"] | camel_case }}Test(unittest.TestCase):
     {% endfor %}
 
 {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/space-age/.meta/template.j2 b/exercises/practice/space-age/.meta/template.j2
index a7822ad6..15c8c550 100644
--- a/exercises/practice/space-age/.meta/template.j2
+++ b/exercises/practice/space-age/.meta/template.j2
@@ -9,5 +9,4 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual(SpaceAge({{ seconds }}).on_{{ planet | lower }}(), {{ case["expected"] }})
 
     {% endfor %}
-
-{{ macros.footer() }}
+    
\ No newline at end of file
diff --git a/exercises/practice/spiral-matrix/.meta/template.j2 b/exercises/practice/spiral-matrix/.meta/template.j2
index b501e6d4..2a73e6bc 100644
--- a/exercises/practice/spiral-matrix/.meta/template.j2
+++ b/exercises/practice/spiral-matrix/.meta/template.j2
@@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] | to_snake }}({{ case["input"]["size"] }}), {{  case["expected"] }})
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/strain/.docs/instructions.md b/exercises/practice/strain/.docs/instructions.md
index 370eb221..3469ae65 100644
--- a/exercises/practice/strain/.docs/instructions.md
+++ b/exercises/practice/strain/.docs/instructions.md
@@ -1,9 +1,7 @@
 # Instructions
 
-Implement the `keep` and `discard` operation on collections. Given a collection
-and a predicate on the collection's elements, `keep` returns a new collection
-containing those elements where the predicate is true, while `discard` returns
-a new collection containing those elements where the predicate is false.
+Implement the `keep` and `discard` operation on collections.
+Given a collection and a predicate on the collection's elements, `keep` returns a new collection containing those elements where the predicate is true, while `discard` returns a new collection containing those elements where the predicate is false.
 
 For example, given the collection of numbers:
 
@@ -23,12 +21,9 @@ While your discard operation should produce:
 
 Note that the union of keep and discard is all the elements.
 
-The functions may be called `keep` and `discard`, or they may need different
-names in order to not clash with existing functions or concepts in your
-language.
+The functions may be called `keep` and `discard`, or they may need different names in order to not clash with existing functions or concepts in your language.
 
 ## Restrictions
 
-Keep your hands off that filter/reject/whatchamacallit functionality
-provided by your standard library!  Solve this one yourself using other
-basic tools instead.
+Keep your hands off that filter/reject/whatchamacallit functionality provided by your standard library!
+Solve this one yourself using other basic tools instead.
diff --git a/exercises/practice/sublist/.approaches/config.json b/exercises/practice/sublist/.approaches/config.json
new file mode 100644
index 00000000..ce54db9c
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/config.json
@@ -0,0 +1,21 @@
+{
+  "introduction": {
+    "authors": ["safwansamsudeen"]
+  },
+  "approaches": [
+    {
+      "uuid": "db47397a-4551-49e8-8775-7e7aad79a38b",
+      "slug": "list-manipulation",
+      "title": "List manipulation",
+      "blurb": "Manipulate and check lists to solve the exercise",
+      "authors": ["safwansamsudeen"]
+    },
+    {
+      "uuid": "61366160-c859-4d16-9085-171428209b8d",
+      "slug": "using-strings",
+      "title": "Using strings",
+      "blurb": "Convert the lists to string and use string manipulation to solve the exercise",
+      "authors": ["safwansamsudeen"]
+    }
+  ]
+}
diff --git a/exercises/practice/sublist/.approaches/introduction.md b/exercises/practice/sublist/.approaches/introduction.md
new file mode 100644
index 00000000..42f991ef
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/introduction.md
@@ -0,0 +1,59 @@
+# Introduction
+There are two broad ways to solve Sublist. 
+
+## General guidance
+To write the code, you need to branch out (probably with `if`) into the four different possible conditions, and return the appropriate name of the category. 
+
+## Approach: list manipulation
+The direct approach would be to manipulate and check the given lists to solve this.
+This solution uses a helper function, which simplifies things, but the approach can be implemented without it.
+
+```python
+SUBLIST = 1
+SUPERLIST = 2
+EQUAL = 3
+UNEQUAL = 4
+
+def check_sub_sequences(list_one, list_two):
+    n1 = len(list_one)
+    n2 = len(list_two)
+    return any(list_two[i:i+n1] == list_one for i in range(n2 - n1 + 1))
+    
+def sublist(list_one, list_two):
+    if list_one == list_two:
+        return EQUAL
+    if check_sub_sequences(list_one, list_two):
+        return SUBLIST
+    if check_sub_sequences(list_two, list_one):
+        return SUPERLIST
+    return UNEQUAL
+```
+
+Read more on the [detail of this approach][approach-list-manipulation].
+
+## Approach: using strings
+Another seemingly clever approach is to convert the lists to strings and then 
+use the `in` operator to check for sub-sequences.
+**However, this does not work.**
+```python
+SUBLIST = 1
+SUPERLIST = 2
+EQUAL = 3
+UNEQUAL = 4
+
+def sublist(list_one, list_two):
+    list_one_check = (str(list_one).strip("[]") + ",")
+    list_two_check = (str(list_two).strip("[]") + ",")
+
+    if list_one_check == list_two_check:
+        return EQUAL
+    elif list_one_check in list_two_check:
+        return SUBLIST
+    elif list_two_check in list_one_check:
+        return SUPERLIST
+    return UNEQUAL
+```
+To understand more about this approach and **why it fails**, [read here][approach-using-strings].
+
+[approach-list-manipulation]: https://exercism.org/tracks/python/exercises/sublist/approaches/list-manipulation
+[approach-using-strings]: https://exercism.org/tracks/python/exercises/sublist/approaches/using-strings
diff --git a/exercises/practice/sublist/.approaches/list-manipulation/content.md b/exercises/practice/sublist/.approaches/list-manipulation/content.md
new file mode 100644
index 00000000..ac374b73
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/list-manipulation/content.md
@@ -0,0 +1,38 @@
+# List manipulation
+The direct approach would be to manipulate and check the given lists to solve this.
+This solution uses a helper function, which simplifies things, but the approach can be implemented without it.
+
+```python
+SUBLIST = 1
+SUPERLIST = 2
+EQUAL = 3
+UNEQUAL = 4
+
+def check_sub_sequences(list_one, list_two):
+    n1 = len(list_one)
+    n2 = len(list_two)
+    return any(list_two[i:i+n1] == list_one for i in range(n2 - n1 + 1))
+    
+def sublist(list_one, list_two):
+    if list_one == list_two:
+        return EQUAL
+    if check_sub_sequences(list_one, list_two):
+        return SUBLIST
+    if check_sub_sequences(list_two, list_one):
+        return SUPERLIST
+    return UNEQUAL
+```
+
+We first check for equality using the `==` operator, if so, then we return `EQUAL`. 
+A common way to do this differently would be to return `1` directly, but this is better practice as we  [remove magic values][magic values].
+
+After that we call `check_sub_sequences` passing in `list_one` and `list_two`.
+In the helper function, we check if `any` of the possible sub-sequences in `list_two` of length `n1` (the length of the first list) are equal to the first list.
+If so, then we conclude that `list_one` is a `SUBLIST` of `list_two`. 
+
+To find whether `list_one` is a `SUPERLIST` of `list_two`, we just reverse this process - pass in the lists in the opposite order.
+Thus, we check if `any` of the possible sub-sequences in `list_one` of length `n2` (the length of the second list) are equal to the second list.
+
+If none of the above conditions are true, we conclude that the two lists are unequal.
+
+[magic values]: https://stackoverflow.com/questions/47882/what-is-a-magic-number-and-why-is-it-bad
\ No newline at end of file
diff --git a/exercises/practice/sublist/.approaches/list-manipulation/snippet.txt b/exercises/practice/sublist/.approaches/list-manipulation/snippet.txt
new file mode 100644
index 00000000..290f8cdd
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/list-manipulation/snippet.txt
@@ -0,0 +1,8 @@
+def sublist(list_one, list_two):
+    if list_one == list_two:
+        return EQUAL
+    if check_sub_sequences(list_one, list_two):
+        return SUBLIST
+    if check_sub_sequences(list_two, list_one):
+        return SUPERLIST
+    return UNEQUAL
\ No newline at end of file
diff --git a/exercises/practice/sublist/.approaches/using-strings/content.md b/exercises/practice/sublist/.approaches/using-strings/content.md
new file mode 100644
index 00000000..ff960902
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/using-strings/content.md
@@ -0,0 +1,47 @@
+# Using strings
+~~~~exercism/caution
+**This approach does not work, and this document exists to explain that.** 
+Please do not use it in your code.
+~~~~
+
+Another seemingly clever solution is to convert the lists to strings and then 
+use the `in` operator to check for sub-sequences.
+Note that this approach, even if it worked, is not as performant as the 
+previous one.
+```python
+SUBLIST = 1
+SUPERLIST = 2
+EQUAL = 3
+UNEQUAL = 4
+
+def sublist(list_one, list_two):
+    list_one_check = str(list_one).strip("[]")
+    list_two_check = str(list_two).strip("[]")
+
+    if list_one_check == list_two_check:
+        return EQUAL
+    elif list_one_check in list_two_check:
+        return SUBLIST
+    elif list_two_check in list_one_check:
+        return SUPERLIST
+    return UNEQUAL
+```
+Let's parse the code to see what it does.
+In this approach, we convert the lists to strings, so `[1, 2, 3]` becomes `"[1, 2, 3]"`, remove the brackets `"1, 2, 3"`, and add a comma `"1, 2, 3,"`. 
+We check equality and then use the `in` operator to check for `SUBLIST` or `SUPERLIST`, and finally return `UNEQUAL`.
+
+We add a comma because, say, we call `sublist` with `[1, 2]` and `[1, 22]`. `"1, 2" in "1, 22"` evaluates to `True`, so 
+the **function would wrongly mark it as `SUBLIST`**. 
+
+This test can be overridden by changing the code like this:
+```python
+list_one_check = str(list_one).strip("[]") + ','
+list_two_check = str(list_two).strip("[]") + ','
+```
+Yet, the test case (which doesn't exist in the Exercism test suite) `["1", "2"]` and `["5", "'1', '2',", "7"]` would 
+fail.
+
+Students can add any arbitrary string into the representation to try to "defeat" this test - `list_one_check = str
+(list_one) + TOKEN`. The test suite currently test `TOKEN = ''`, but not others.
+
+[gen-exp]: https://www.programiz.com/python-programming/generator
\ No newline at end of file
diff --git a/exercises/practice/sublist/.approaches/using-strings/snippet.txt b/exercises/practice/sublist/.approaches/using-strings/snippet.txt
new file mode 100644
index 00000000..26fc3ec0
--- /dev/null
+++ b/exercises/practice/sublist/.approaches/using-strings/snippet.txt
@@ -0,0 +1,8 @@
+# Failing approach
+def sublist(list_one, list_two):
+    list_one_check = str(list_one).strip("[]")
+    ...
+    elif list_one_check in list_two_check:
+        return SUBLIST
+    ...
+    return UNEQUAL
\ No newline at end of file
diff --git a/exercises/practice/sublist/sublist.py b/exercises/practice/sublist/sublist.py
index 428d3aa6..35410301 100644
--- a/exercises/practice/sublist/sublist.py
+++ b/exercises/practice/sublist/sublist.py
@@ -1,8 +1,7 @@
 """
 This exercise stub and the test suite contain several enumerated constants.
 
-Since Python 2 does not have the enum module, the idiomatic way to write
-enumerated constants has traditionally been a NAME assigned to an arbitrary,
+Enumerated constants can be done with a NAME assigned to an arbitrary,
 but unique value. An integer is traditionally used because it’s memory
 efficient.
 It is a common practice to export both constants and functions that work with
diff --git a/exercises/practice/sum-of-multiples/.docs/instructions.md b/exercises/practice/sum-of-multiples/.docs/instructions.md
index e9a25636..d69f890e 100644
--- a/exercises/practice/sum-of-multiples/.docs/instructions.md
+++ b/exercises/practice/sum-of-multiples/.docs/instructions.md
@@ -7,12 +7,12 @@ The points awarded depend on two things:
 - The level (a number) that the player completed.
 - The base value of each magical item collected by the player during that level.
 
-The energy points are awarded according to the following rules:  
+The energy points are awarded according to the following rules:
 
 1. For each magical item, take the base value and find all the multiples of that value that are less than the level number.
 2. Combine the sets of numbers.
 3. Remove any duplicates.
-4. Calculate the sum of all the numbers that are left.  
+4. Calculate the sum of all the numbers that are left.
 
 Let's look at an example:
 
@@ -24,4 +24,4 @@ To calculate the energy points earned by the player, we need to find all the uni
 - Multiples of 5 less than 20: `{5, 10, 15}`
 - Combine the sets and remove duplicates: `{3, 5, 6, 9, 10, 12, 15, 18}`
 - Sum the unique multiples: `3 + 5 + 6 + 9 + 10 + 12 + 15 + 18 = 78`
-- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5
+- Therefore, the player earns **78** energy points for completing level 20 and finding the two magical items with base values of 3 and 5.
diff --git a/exercises/practice/sum-of-multiples/.meta/template.j2 b/exercises/practice/sum-of-multiples/.meta/template.j2
index 6ffa327a..39dab6ad 100644
--- a/exercises/practice/sum-of-multiples/.meta/template.j2
+++ b/exercises/practice/sum-of-multiples/.meta/template.j2
@@ -7,5 +7,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"].replace("sum", "sum_of_multiples") }}({{ case["input"]["limit"] }}, {{ case["input"]["factors"] }}), {{ case["expected"] }})
 
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/triangle/.meta/template.j2 b/exercises/practice/triangle/.meta/template.j2
index 8875bc51..6d79c8c3 100644
--- a/exercises/practice/triangle/.meta/template.j2
+++ b/exercises/practice/triangle/.meta/template.j2
@@ -9,4 +9,3 @@ class {{ case["description"] | camel_case }}Test(unittest.TestCase):
 
     {% endfor %}
 {% endfor %}
-{{ macros.footer() }}
diff --git a/exercises/practice/trinary/.docs/instructions.md b/exercises/practice/trinary/.docs/instructions.md
index 3638ddb7..d38e3b5b 100644
--- a/exercises/practice/trinary/.docs/instructions.md
+++ b/exercises/practice/trinary/.docs/instructions.md
@@ -1,15 +1,13 @@
 # Instructions
 
-Convert a trinary number, represented as a string (e.g. '102012'), to its
-decimal equivalent using first principles.
+Convert a trinary number, represented as a string (e.g. '102012'), to its decimal equivalent using first principles.
 
-The program should consider strings specifying an invalid trinary as the
-value 0.
+The program should consider strings specifying an invalid trinary as the value 0.
 
 Trinary numbers contain three symbols: 0, 1, and 2.
 
-The last place in a trinary number is the 1's place. The second to last
-is the 3's place, the third to last is the 9's place, etc.
+The last place in a trinary number is the 1's place.
+The second to last is the 3's place, the third to last is the 9's place, etc.
 
 ```shell
 # "102012"
@@ -18,5 +16,4 @@ is the 3's place, the third to last is the 9's place, etc.
   243 +     0 +    54 +     0 +     3 +     2 =  302
 ```
 
-If your language provides a method in the standard library to perform the
-conversion, pretend it doesn't exist and implement it yourself.
+If your language provides a method in the standard library to perform the conversion, pretend it doesn't exist and implement it yourself.
diff --git a/exercises/practice/two-fer/.docs/instructions.md b/exercises/practice/two-fer/.docs/instructions.md
index a9bb4a3c..37aa7529 100644
--- a/exercises/practice/two-fer/.docs/instructions.md
+++ b/exercises/practice/two-fer/.docs/instructions.md
@@ -17,9 +17,9 @@ One for you, one for me.
 
 Here are some examples:
 
-|Name    |Dialogue
-|:-------|:------------------
-|Alice   |One for Alice, one for me.
-|Bohdan  |One for Bohdan, one for me.
-|        |One for you, one for me.
-|Zaphod  |One for Zaphod, one for me.
+| Name   | Dialogue                    |
+| :----- | :-------------------------- |
+| Alice  | One for Alice, one for me.  |
+| Bohdan | One for Bohdan, one for me. |
+|        | One for you, one for me.    |
+| Zaphod | One for Zaphod, one for me. |
diff --git a/exercises/practice/two-fer/.meta/template.j2 b/exercises/practice/two-fer/.meta/template.j2
index 51634eb1..5db1d53c 100644
--- a/exercises/practice/two-fer/.meta/template.j2
+++ b/exercises/practice/two-fer/.meta/template.j2
@@ -10,5 +10,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         self.assertEqual({{ case["property"] | to_snake }}("{{ case["input"]["name"] }}"), "{{ case["expected"] }}")
         {% endif %}
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/word-count/.meta/template.j2 b/exercises/practice/word-count/.meta/template.j2
index 2c91fdf9..7e41be67 100644
--- a/exercises/practice/word-count/.meta/template.j2
+++ b/exercises/practice/word-count/.meta/template.j2
@@ -21,6 +21,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     {{ test_case(case) }}
     {% endfor %}
     {%- endif %}
-
-
-{{ macros.footer() }}
diff --git a/exercises/practice/word-search/.meta/template.j2 b/exercises/practice/word-search/.meta/template.j2
index 309a2fc1..952da9e9 100644
--- a/exercises/practice/word-search/.meta/template.j2
+++ b/exercises/practice/word-search/.meta/template.j2
@@ -20,6 +20,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
         {%- endfor %}
 
     {% endfor %}
-
-{{ macros.footer() }}
-
diff --git a/exercises/practice/yacht/.meta/template.j2 b/exercises/practice/yacht/.meta/template.j2
index 4086f512..80eb31f7 100644
--- a/exercises/practice/yacht/.meta/template.j2
+++ b/exercises/practice/yacht/.meta/template.j2
@@ -16,5 +16,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
             {{ case["expected"] }})
 
     {% endfor %}
-
-{{ macros.footer() }}
\ No newline at end of file
diff --git a/exercises/practice/zebra-puzzle/.meta/template.j2 b/exercises/practice/zebra-puzzle/.meta/template.j2
index 6b068c7e..134ccdbf 100644
--- a/exercises/practice/zebra-puzzle/.meta/template.j2
+++ b/exercises/practice/zebra-puzzle/.meta/template.j2
@@ -6,5 +6,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
     def test_{{ case["description"] | to_snake }}(self):
         self.assertEqual({{ case["property"] | to_snake }}(), "{{ case["expected"] }}")
     {% endfor %}
-
-{{ macros.footer() }}
diff --git a/exercises/practice/zipper/.meta/template.j2 b/exercises/practice/zipper/.meta/template.j2
index 9f239bc1..4e5de995 100644
--- a/exercises/practice/zipper/.meta/template.j2
+++ b/exercises/practice/zipper/.meta/template.j2
@@ -52,5 +52,3 @@ class {{ exercise | camel_case }}Test(unittest.TestCase):
 {%- for case in cases %}
     {{ test_case(case) }}
 {%- endfor %}
-
-{{ macros.footer() }}
diff --git a/pylintrc b/pylintrc
index 745e6f03..09795978 100644
--- a/pylintrc
+++ b/pylintrc
@@ -341,11 +341,6 @@ known-standard-library=
 # Force import order to recognize a module as part of a third party library.
 known-third-party=enchant, absl
 
-# Analyse import fallback blocks. This can be used to support both Python 2 and
-# 3 compatible code, which means that the block might have code that exists
-# only in one or another interpreter, leading to false positives when analysed.
-analyse-fallback-blocks=no
-
 
 [CLASSES]
 
diff --git a/reference/exercise-concepts/rna-transcription.md b/reference/exercise-concepts/rna-transcription.md
index b2b12946..004ded95 100644
--- a/reference/exercise-concepts/rna-transcription.md
+++ b/reference/exercise-concepts/rna-transcription.md
@@ -2,7 +2,7 @@
 
 ## Example implementation
 
-Modified from the existing [example.py](https://github.com/exercism/python/blob/master/exercises/rna-transcription/example.py) to remove Python 2 compatiblity noise:
+Taken from the existing [example.py](https://github.com/exercism/python/blob/main/exercises/practice/rna-transcription/.meta/example.py):
 
 ```python
 DNA_TO_RNA = str.maketrans("AGCT", "UCGA")


Test patch:
diff --git a/exercises/concept/mecha-munch-management/dict_methods_test.py b/exercises/concept/mecha-munch-management/dict_methods_test.py
new file mode 100644
index 00000000..2f5828d6
--- /dev/null
+++ b/exercises/concept/mecha-munch-management/dict_methods_test.py
@@ -0,0 +1,161 @@
+import unittest
+import pytest
+from dict_methods import (add_item,
+                          read_notes,
+                          update_recipes,
+                          sort_entries,
+                          send_to_store,
+                          update_store_inventory)
+
+
+class MechaMunchManagementTest(unittest.TestCase):
+
+    @pytest.mark.task(taskno=1)
+    def test_add_item(self):
+        input_data = [
+                      ({'Apple': 1, 'Banana': 4 }, ('Apple', 'Banana', 'Orange')),
+                      ({'Orange': 1, 'Raspberry': 1, 'Blueberries': 10}, ['Raspberry', 'Blueberries', 'Raspberry']),
+                      ({'Broccoli': 1, 'Banana': 1}, ('Broccoli', 'Kiwi', 'Kiwi', 'Kiwi', 'Melon', 'Apple', 'Banana', 'Banana'))
+                      ]
+        
+        output_data = [{'Apple': 2, 'Banana': 5, 'Orange': 1},
+                       {'Orange': 1, 'Raspberry': 3, 'Blueberries': 11},
+                       {'Broccoli': 2, 'Banana': 3, 'Kiwi': 3, 'Melon': 1, 'Apple': 1}]
+
+        for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1):
+            with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data):
+                error_msg=f'Expected: {output_data} but got a different shopping cart.'
+                self.assertEqual(add_item(input_data[0], input_data[1]), output_data, msg=error_msg)
+        
+
+    @pytest.mark.task(taskno=2)
+    def test_read_notes(self):
+        input_data = [('Apple', "Banana"), ('Orange', 'Raspberry', 'Blueberries'),
+                      ['Broccoli', 'Kiwi', 'Melon', 'Apple', 'Banana']]
+
+        output_data = [{'Apple': 1, 'Banana': 1}, {'Orange': 1, 'Raspberry': 1, 'Blueberries': 1},
+                       {'Broccoli': 1, 'Kiwi': 1, 'Melon': 1, 'Apple': 1, 'Banana': 1}]
+
+        for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1):
+            with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data):
+                error_msg=f'Expected: {output_data} but got a different shopping cart.'
+                self.assertEqual(read_notes(input_data), output_data, msg=error_msg)
+
+    @pytest.mark.task(taskno=3)
+    def test_update_recipes(self):
+        input_data = [
+                        ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
+                          'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}},
+                        (('Banana Bread', {'Banana': 4,  'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3}),)),
+
+                        ({'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1},
+                          'Blueberry Pie': {'Blueberries': 1, 'Pie Crust': 1, 'Cream Custard': 1}},
+                        (('Blueberry Pie', {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}),
+                         ('Apple Pie', {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1}))),
+
+                        ({'Banana Bread' : {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
+                          'Raspberry Pie' : {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1},
+                          'Pasta Primavera': {'Eggs': 1, 'Carrots': 1, 'Spinach': 2, 'Tomatoes': 3, 'Parmesan': 2, 'Milk': 1, 'Onion': 1}},
+                        (('Raspberry Pie', {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2}),
+                         ('Pasta Primavera', {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1}),
+                         ('Blueberry Crumble', {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3})))
+                     ]
+
+        output_data = [
+                        {'Banana Bread': {'Banana': 4,  'Walnuts': 2, 'Flour': 1, 'Eggs': 2, 'Butter': 1, 'Milk': 2, 'Eggs': 3},
+                         'Raspberry Pie': {'Raspberry': 1, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1}},
+                        {'Apple Pie': {'Apple': 1, 'Pie Crust': 1, 'Cream Custard': 1},
+                        'Blueberry Pie': {'Blueberries': 2, 'Pie Crust': 1, 'Cream Custard': 1}},
+                        {'Banana Bread': {'Banana': 1, 'Apple': 1, 'Walnuts': 1, 'Flour': 1, 'Eggs': 2, 'Butter': 1},
+                        'Raspberry Pie': {'Raspberry': 3, 'Orange': 1, 'Pie Crust': 1, 'Cream Custard': 1, 'Whipped Cream': 2},
+                        'Pasta Primavera': {'Eggs': 1, 'Mixed Veggies': 2, 'Parmesan': 2, 'Milk': 1, 'Spinach': 1, 'Bread Crumbs': 1},
+                        'Blueberry Crumble': {'Blueberries': 2, 'Whipped Creme': 2, 'Granola Topping': 2, 'Yogurt': 3}}
+                     ]
+
+        for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1):
+            with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data):
+                error_msg=f'Expected: {output_data} but got a different ideas instead.'
+                self.assertEqual(update_recipes(input_data[0], input_data[1]), output_data, msg=error_msg)
+
+    @pytest.mark.task(taskno=4)
+    def test_sort_entries(self):
+        input_data = [
+                      {'Banana': 4, 'Apple': 2, 'Orange': 1, 'Pear': 12},
+                      {'Apple': 3, 'Orange': 5, 'Banana': 1, 'Avocado': 2},
+                      {'Orange': 3, 'Banana': 2, 'Apple': 1},
+                      {'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4}
+                     ]
+
+        output_data = [
+                        {'Apple': 2, 'Banana': 4, 'Orange': 1, 'Pear': 12},
+                        {'Avocado': 2, 'Apple': 3, 'Banana': 1, 'Orange': 5},
+                        {'Apple': 1, 'Orange': 3, 'Banana': 2},
+                        {'Apple' : 2, 'Blueberries': 5, 'Broccoli': 2, 'Kiwi': 1, 'Melon': 4, 'Raspberry': 2}
+                      ]
+
+        for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1):
+            with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data):
+                error_msg=f'Expected: {output_data} but got a different sorted list instead.'
+                self.assertEqual(sort_entries(input_data), output_data, msg=error_msg)
+
+    @pytest.mark.task(taskno=5)
+    def test_send_to_store(self):
+        input_data = [
+                        ({'Banana': 3, 'Apple': 2, 'Orange': 1, 'Milk': 2},
+                         {'Banana': ['Isle 5', False], 'Apple': ['Isle 4', False], 'Orange': ['Isle 4', False], 'Milk': ['Isle 2', True]}),
+
+                        ({'Kiwi': 3, 'Juice': 5, 'Yoghurt': 2, 'Milk': 5},
+                         {'Kiwi': ['Isle 6', False], 'Juice': ['Isle 5', False], 'Yoghurt': ['Isle 2', True], 'Milk': ['Isle 2', True]}),
+
+                        ({'Apple': 2, 'Raspberry': 2, 'Blueberries': 5, 'Broccoli' : 2, 'Kiwi': 1, 'Melon': 4},
+                         {'Apple': ['Isle 1', False], 'Raspberry': ['Isle 6', False], 'Blueberries': ['Isle 6', False],
+                          'Broccoli': ['Isle 3', False], 'Kiwi': ['Isle 6', False], 'Melon': ['Isle 6', False]})
+                      ]
+
+        output_data = [
+                        {'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True], 'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]},
+                        {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True], 'Kiwi': [3, 'Isle 6', False]},
+                        {'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False],
+                         'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False], 'Broccoli': [2, 'Isle 3', False]}
+                      ]
+
+        for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1):
+            with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data):
+                error_msg=f'Expected: {output_data} but got a different fulfillment_cart instead.'
+                self.assertEqual(send_to_store(input_data[0], input_data[1]), output_data, msg=error_msg)
+
+    @pytest.mark.task(taskno=6)
+    def test_update_store_inventory(self):
+        input_data = [
+                        ({'Orange': [1, 'Isle 4', False], 'Milk': [2, 'Isle 2', True],
+                          'Banana': [3, 'Isle 5', False], 'Apple': [2, 'Isle 4', False]},
+                         {'Banana': [15, 'Isle 5', False], 'Apple': [12, 'Isle 4', False],
+                          'Orange': [1, 'Isle 4', False], 'Milk': [4, 'Isle 2', True]}),
+
+                        ({'Kiwi': [3, 'Isle 6', False]},{'Kiwi': [3, 'Isle 6', False], 'Juice': [5, 'Isle 5', False],
+                          'Yoghurt': [2, 'Isle 2', True], 'Milk': [5, 'Isle 2', True]}),
+
+                        ({'Kiwi': [1, 'Isle 6', False], 'Melon': [4, 'Isle 6', False], 'Apple': [2, 'Isle 1', False],
+                          'Raspberry': [2, 'Isle 6', False], 'Blueberries': [5, 'Isle 6', False],
+                          'Broccoli': [1, 'Isle 3', False]},
+                         {'Apple': [2, 'Isle 1', False], 'Raspberry': [5, 'Isle 6', False],
+                          'Blueberries': [10, 'Isle 6', False], 'Broccoli': [4, 'Isle 3', False],
+                          'Kiwi': [1, 'Isle 6', False], 'Melon': [8, 'Isle 6', False]})
+                      ]
+
+        output_data = [
+                        {'Banana': [12, 'Isle 5', False], 'Apple': [10, 'Isle 4', False],
+                         'Orange': ['Out of Stock', 'Isle 4', False], 'Milk': [2, 'Isle 2', True]},
+
+                        {'Juice': [5, 'Isle 5', False], 'Yoghurt': [2, 'Isle 2', True],
+                         'Milk': [5, 'Isle 2', True], 'Kiwi': ["Out of Stock", 'Isle 6', False]},
+
+                        {'Kiwi': ['Out of Stock', 'Isle 6', False], 'Melon': [4, 'Isle 6', False],
+                         'Apple': ['Out of Stock', 'Isle 1', False], 'Raspberry': [3, 'Isle 6', False],
+                         'Blueberries': [5, 'Isle 6', False], 'Broccoli': [3, 'Isle 3', False]}
+                      ]
+
+        for variant, (input_data, output_data) in enumerate(zip(input_data, output_data), start=1):
+            with self.subTest(f'variation #{variant}', input_data=input_data, output_data=output_data):
+                error_msg=f'Expected: {output_data} but got a different store inventory instead.'
+                self.assertEqual(update_store_inventory(input_data[0], input_data[1]), output_data, msg=error_msg)
diff --git a/exercises/practice/acronym/acronym_test.py b/exercises/practice/acronym/acronym_test.py
index c5fbbfa3..19e4e486 100644
--- a/exercises/practice/acronym/acronym_test.py
+++ b/exercises/practice/acronym/acronym_test.py
@@ -39,7 +39,3 @@ class AcronymTest(unittest.TestCase):
 
     def test_underscore_emphasis(self):
         self.assertEqual(abbreviate("The Road _Not_ Taken"), "TRNT")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/all-your-base/all_your_base_test.py b/exercises/practice/all-your-base/all_your_base_test.py
index 04cf27db..0191754a 100644
--- a/exercises/practice/all-your-base/all_your_base_test.py
+++ b/exercises/practice/all-your-base/all_your_base_test.py
@@ -101,11 +101,3 @@ class AllYourBaseTest(unittest.TestCase):
             rebase(-2, [1], -7)
         self.assertEqual(type(err.exception), ValueError)
         self.assertEqual(err.exception.args[0], "input base must be >= 2")
-
-    # Utility functions
-    def assertRaisesWithMessage(self, exception):
-        return self.assertRaisesRegex(exception, r".+")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/allergies/allergies_test.py b/exercises/practice/allergies/allergies_test.py
index 5b8d32c0..4525db8e 100644
--- a/exercises/practice/allergies/allergies_test.py
+++ b/exercises/practice/allergies/allergies_test.py
@@ -183,7 +183,3 @@ class AllergiesTest(unittest.TestCase):
 
     def test_no_allergen_score_parts_without_highest_valid_score(self):
         self.assertEqual(Allergies(257).lst, ["eggs"])
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/alphametics/alphametics_test.py b/exercises/practice/alphametics/alphametics_test.py
index c1f57339..8853e1e7 100644
--- a/exercises/practice/alphametics/alphametics_test.py
+++ b/exercises/practice/alphametics/alphametics_test.py
@@ -106,7 +106,3 @@ class AlphameticsTest(unittest.TestCase):
                 "T": 9,
             },
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py
index c3f6089e..6a4bd96e 100644
--- a/exercises/practice/armstrong-numbers/armstrong_numbers_test.py
+++ b/exercises/practice/armstrong-numbers/armstrong_numbers_test.py
@@ -34,7 +34,3 @@ class ArmstrongNumbersTest(unittest.TestCase):
 
     def test_seven_digit_number_that_is_not_an_armstrong_number(self):
         self.assertIs(is_armstrong_number(9926314), False)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/atbash-cipher/atbash_cipher_test.py b/exercises/practice/atbash-cipher/atbash_cipher_test.py
index 98c1072a..fcc1a25e 100644
--- a/exercises/practice/atbash-cipher/atbash_cipher_test.py
+++ b/exercises/practice/atbash-cipher/atbash_cipher_test.py
@@ -61,7 +61,3 @@ class AtbashCipherTest(unittest.TestCase):
         self.assertEqual(
             decode("zmlyhgzxovrhlugvmzhgvkkrmthglmv"), "anobstacleisoftenasteppingstone"
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/beer-song/beer_song_test.py b/exercises/practice/beer-song/beer_song_test.py
index 4952a374..2aca5bae 100644
--- a/exercises/practice/beer-song/beer_song_test.py
+++ b/exercises/practice/beer-song/beer_song_test.py
@@ -369,7 +369,3 @@ class BeerSongTest(unittest.TestCase):
             "Go to the store and buy some more, 99 bottles of beer on the wall.",
         ]
         self.assertEqual(recite(start=99, take=100), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/binary-search-tree/binary_search_tree_test.py b/exercises/practice/binary-search-tree/binary_search_tree_test.py
index 23c1fea7..9a4a7193 100644
--- a/exercises/practice/binary-search-tree/binary_search_tree_test.py
+++ b/exercises/practice/binary-search-tree/binary_search_tree_test.py
@@ -82,7 +82,3 @@ class BinarySearchTreeTest(unittest.TestCase):
             pass
         else:
             raise AssertionError
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/bob/bob_test.py b/exercises/practice/bob/bob_test.py
index 40081b04..c033d34d 100644
--- a/exercises/practice/bob/bob_test.py
+++ b/exercises/practice/bob/bob_test.py
@@ -98,7 +98,3 @@ class BobTest(unittest.TestCase):
         self.assertEqual(
             response("This is a statement ending with whitespace      "), "Whatever."
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/book-store/book_store_test.py b/exercises/practice/book-store/book_store_test.py
index 50f38bc5..06feb75a 100644
--- a/exercises/practice/book-store/book_store_test.py
+++ b/exercises/practice/book-store/book_store_test.py
@@ -95,7 +95,3 @@ class BookStoreTest(unittest.TestCase):
     def test_shuffled_book_order(self):
         basket = [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2, 3]
         self.assertEqual(total(basket), 8120)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/bottle-song/bottle_song_test.py b/exercises/practice/bottle-song/bottle_song_test.py
index 89877dc4..3c3108f9 100644
--- a/exercises/practice/bottle-song/bottle_song_test.py
+++ b/exercises/practice/bottle-song/bottle_song_test.py
@@ -130,7 +130,3 @@ class BottleSongTest(unittest.TestCase):
             "There'll be no green bottles hanging on the wall.",
         ]
         self.assertEqual(recite(start=10, take=10), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/clock/clock_test.py b/exercises/practice/clock/clock_test.py
index fe063366..b2c6fe9d 100644
--- a/exercises/practice/clock/clock_test.py
+++ b/exercises/practice/clock/clock_test.py
@@ -177,7 +177,3 @@ class ClockTest(unittest.TestCase):
 
     def test_full_clock_and_zeroed_clock(self):
         self.assertEqual(Clock(24, 0), Clock(0, 0))
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/crypto-square/crypto_square_test.py b/exercises/practice/crypto-square/crypto_square_test.py
index 3715400c..553728f3 100644
--- a/exercises/practice/crypto-square/crypto_square_test.py
+++ b/exercises/practice/crypto-square/crypto_square_test.py
@@ -51,7 +51,3 @@ class CryptoSquareTest(unittest.TestCase):
         value = "If man was meant to stay on the ground, god would have given us roots."
         expected = "imtgdvs fearwer mayoogo anouuio ntnnlvt wttddes aohghn  sseoau "
         self.assertEqual(cipher_text(value), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/custom-set/custom_set_test.py b/exercises/practice/custom-set/custom_set_test.py
index e5e3ffd3..30c945ac 100644
--- a/exercises/practice/custom-set/custom_set_test.py
+++ b/exercises/practice/custom-set/custom_set_test.py
@@ -212,7 +212,3 @@ class CustomSetTest(unittest.TestCase):
         set2 = CustomSet([2, 3])
         expected = CustomSet([3, 2, 1])
         self.assertEqual(set1 + set2, expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/darts/darts_test.py b/exercises/practice/darts/darts_test.py
index e830199c..94a9c5ed 100644
--- a/exercises/practice/darts/darts_test.py
+++ b/exercises/practice/darts/darts_test.py
@@ -46,7 +46,3 @@ class DartsTest(unittest.TestCase):
 
     def test_asymmetric_position_between_the_inner_and_middle_circles(self):
         self.assertEqual(score(0.5, -4), 5)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/diamond/diamond_test.py b/exercises/practice/diamond/diamond_test.py
index f0e60c86..9ed44ee1 100644
--- a/exercises/practice/diamond/diamond_test.py
+++ b/exercises/practice/diamond/diamond_test.py
@@ -87,7 +87,3 @@ class DiamondTest(unittest.TestCase):
             "                         A                         ",
         ]
         self.assertEqual(rows("Z"), result)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/difference-of-squares/difference_of_squares_test.py b/exercises/practice/difference-of-squares/difference_of_squares_test.py
index 8c2069e4..fa39f03e 100644
--- a/exercises/practice/difference-of-squares/difference_of_squares_test.py
+++ b/exercises/practice/difference-of-squares/difference_of_squares_test.py
@@ -36,7 +36,3 @@ class DifferenceOfSquaresTest(unittest.TestCase):
 
     def test_difference_of_squares_100(self):
         self.assertEqual(difference_of_squares(100), 25164150)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/dominoes/dominoes_test.py b/exercises/practice/dominoes/dominoes_test.py
index a62f130c..a9b8c90d 100644
--- a/exercises/practice/dominoes/dominoes_test.py
+++ b/exercises/practice/dominoes/dominoes_test.py
@@ -128,7 +128,3 @@ class DominoesTest(unittest.TestCase):
     def refute_correct_chain(self, input_dominoes, output_chain):
         msg = "There should be no valid chain for {}".format(input_dominoes)
         self.assertIsNone(output_chain, msg)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/etl/etl_test.py b/exercises/practice/etl/etl_test.py
index 693ecb9e..fbd57fd2 100644
--- a/exercises/practice/etl/etl_test.py
+++ b/exercises/practice/etl/etl_test.py
@@ -62,7 +62,3 @@ class EtlTest(unittest.TestCase):
             "z": 10,
         }
         self.assertEqual(transform(legacy_data), data)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/flatten-array/flatten_array_test.py b/exercises/practice/flatten-array/flatten_array_test.py
index 1552f0ed..3038d1a0 100644
--- a/exercises/practice/flatten-array/flatten_array_test.py
+++ b/exercises/practice/flatten-array/flatten_array_test.py
@@ -66,7 +66,3 @@ class FlattenArrayTest(unittest.TestCase):
         inputs = [None, [[[None]]], None, None, [[None, None], None], None]
         expected = []
         self.assertEqual(flatten(inputs), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/food-chain/food_chain_test.py b/exercises/practice/food-chain/food_chain_test.py
index d7795352..f87f4cc9 100644
--- a/exercises/practice/food-chain/food_chain_test.py
+++ b/exercises/practice/food-chain/food_chain_test.py
@@ -180,7 +180,3 @@ class FoodChainTest(unittest.TestCase):
                 "She's dead, of course!",
             ],
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/gigasecond/gigasecond_test.py b/exercises/practice/gigasecond/gigasecond_test.py
index 76ff16a6..e7b5f083 100644
--- a/exercises/practice/gigasecond/gigasecond_test.py
+++ b/exercises/practice/gigasecond/gigasecond_test.py
@@ -33,7 +33,3 @@ class GigasecondTest(unittest.TestCase):
         self.assertEqual(
             add(datetime(2015, 1, 24, 23, 59, 59)), datetime(2046, 10, 3, 1, 46, 39)
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/grep/grep_test.py b/exercises/practice/grep/grep_test.py
index 80407154..87c76ac8 100644
--- a/exercises/practice/grep/grep_test.py
+++ b/exercises/practice/grep/grep_test.py
@@ -290,7 +290,3 @@ class GrepTest(unittest.TestCase):
             "paradise-lost.txt:Of Oreb, or of Sinai, didst inspire\n"
             "paradise-lost.txt:That Shepherd, who first taught the chosen Seed\n",
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/house/house_test.py b/exercises/practice/house/house_test.py
index 0facef61..82476758 100644
--- a/exercises/practice/house/house_test.py
+++ b/exercises/practice/house/house_test.py
@@ -126,7 +126,3 @@ class HouseTest(unittest.TestCase):
                 "This is the horse and the hound and the horn that belonged to the farmer sowing his corn that kept the rooster that crowed in the morn that woke the priest all shaven and shorn that married the man all tattered and torn that kissed the maiden all forlorn that milked the cow with the crumpled horn that tossed the dog that worried the cat that killed the rat that ate the malt that lay in the house that Jack built.",
             ],
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/isbn-verifier/isbn_verifier_test.py b/exercises/practice/isbn-verifier/isbn_verifier_test.py
index f2f97770..22fd2892 100644
--- a/exercises/practice/isbn-verifier/isbn_verifier_test.py
+++ b/exercises/practice/isbn-verifier/isbn_verifier_test.py
@@ -64,7 +64,3 @@ class IsbnVerifierTest(unittest.TestCase):
 
     def test_input_is_too_long_but_contains_a_valid_isbn(self):
         self.assertIs(is_valid("98245726788"), False)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/knapsack/knapsack_test.py b/exercises/practice/knapsack/knapsack_test.py
index 0a792f17..937df1bb 100644
--- a/exercises/practice/knapsack/knapsack_test.py
+++ b/exercises/practice/knapsack/knapsack_test.py
@@ -100,7 +100,3 @@ class KnapsackTest(unittest.TestCase):
             ),
             1458,
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/leap/leap_test.py b/exercises/practice/leap/leap_test.py
index e51aeb42..6ba0f949 100644
--- a/exercises/practice/leap/leap_test.py
+++ b/exercises/practice/leap/leap_test.py
@@ -34,7 +34,3 @@ class LeapTest(unittest.TestCase):
 
     def test_year_divisible_by_200_not_divisible_by_400_in_common_year(self):
         self.assertIs(leap_year(1800), False)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/ledger/.meta/tests.toml b/exercises/practice/ledger/.meta/tests.toml
new file mode 100644
index 00000000..e71dfbfc
--- /dev/null
+++ b/exercises/practice/ledger/.meta/tests.toml
@@ -0,0 +1,43 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[d131ecae-a30e-436c-b8f3-858039a27234]
+description = "empty ledger"
+
+[ce4618d2-9379-4eca-b207-9df1c4ec8aaa]
+description = "one entry"
+
+[8d02e9cb-e6ee-4b77-9ce4-e5aec8eb5ccb]
+description = "credit and debit"
+
+[502c4106-0371-4e7c-a7d8-9ce33f16ccb1]
+description = "multiple entries on same date ordered by description"
+
+[29dd3659-6c2d-4380-94a8-6d96086e28e1]
+description = "final order tie breaker is change"
+
+[9b9712a6-f779-4f5c-a759-af65615fcbb9]
+description = "overlong description is truncated"
+
+[67318aad-af53-4f3d-aa19-1293b4d4c924]
+description = "euros"
+
+[bdc499b6-51f5-4117-95f2-43cb6737208e]
+description = "Dutch locale"
+
+[86591cd4-1379-4208-ae54-0ee2652b4670]
+description = "Dutch locale and euros"
+
+[876bcec8-d7d7-4ba4-82bd-b836ac87c5d2]
+description = "Dutch negative number with 3 digits before decimal point"
+
+[29670d1c-56be-492a-9c5e-427e4b766309]
+description = "American negative number with 3 digits before decimal point"
diff --git a/exercises/practice/ledger/ledger_test.py b/exercises/practice/ledger/ledger_test.py
index f0de049d..c1615666 100644
--- a/exercises/practice/ledger/ledger_test.py
+++ b/exercises/practice/ledger/ledger_test.py
@@ -1,147 +1,171 @@
-# -*- coding: utf-8 -*-
 import unittest
 
-from ledger import format_entries, create_entry
+from ledger import (
+    format_entries,
+    create_entry,
+)
+
+# Tests adapted from `problem-specifications//canonical-data.json`
 
 
 class LedgerTest(unittest.TestCase):
     maxDiff = 5000
 
     def test_empty_ledger(self):
-        currency = 'USD'
-        locale = 'en_US'
+        currency = "USD"
+        locale = "en_US"
         entries = []
-        expected = 'Date       | Description               | Change       '
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_one_entry(self):
-        currency = 'USD'
-        locale = 'en_US'
+        currency = "USD"
+        locale = "en_US"
         entries = [
-            create_entry('2015-01-01', 'Buy present', -1000),
+            create_entry("2015-01-01", "Buy present", -1000),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            '01/01/2015 | Buy present               |      ($10.00)',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "01/01/2015 | Buy present               |      ($10.00)",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_credit_and_debit(self):
-        currency = 'USD'
-        locale = 'en_US'
+        currency = "USD"
+        locale = "en_US"
         entries = [
-            create_entry('2015-01-02', 'Get present', 1000),
-            create_entry('2015-01-01', 'Buy present', -1000),
+            create_entry("2015-01-02", "Get present", 1000),
+            create_entry("2015-01-01", "Buy present", -1000),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            '01/01/2015 | Buy present               |      ($10.00)',
-            '01/02/2015 | Get present               |       $10.00 ',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "01/01/2015 | Buy present               |      ($10.00)",
+                "01/02/2015 | Get present               |       $10.00 ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_multiple_entries_on_same_date_ordered_by_description(self):
-        currency = 'USD'
-        locale = 'en_US'
+        currency = "USD"
+        locale = "en_US"
         entries = [
-            create_entry('2015-01-02', 'Get present', 1000),
-            create_entry('2015-01-01', 'Buy present', -1000),
+            create_entry("2015-01-02", "Get present", 1000),
+            create_entry("2015-01-01", "Buy present", -1000),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            '01/01/2015 | Buy present               |      ($10.00)',
-            '01/02/2015 | Get present               |       $10.00 ',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "01/01/2015 | Buy present               |      ($10.00)",
+                "01/02/2015 | Get present               |       $10.00 ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_final_order_tie_breaker_is_change(self):
-        currency = 'USD'
-        locale = 'en_US'
+        currency = "USD"
+        locale = "en_US"
         entries = [
-            create_entry('2015-01-01', 'Something', 0),
-            create_entry('2015-01-01', 'Something', -1),
-            create_entry('2015-01-01', 'Something', 1),
+            create_entry("2015-01-01", "Something", 0),
+            create_entry("2015-01-01", "Something", -1),
+            create_entry("2015-01-01", "Something", 1),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            '01/01/2015 | Something                 |       ($0.01)',
-            '01/01/2015 | Something                 |        $0.00 ',
-            '01/01/2015 | Something                 |        $0.01 ',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "01/01/2015 | Something                 |       ($0.01)",
+                "01/01/2015 | Something                 |        $0.00 ",
+                "01/01/2015 | Something                 |        $0.01 ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
-    def test_overlong_description(self):
-        currency = 'USD'
-        locale = 'en_US'
+    def test_overlong_description_is_truncated(self):
+        currency = "USD"
+        locale = "en_US"
         entries = [
-            create_entry('2015-01-01', 'Freude schoner Gotterfunken', -123456),
+            create_entry("2015-01-01", "Freude schoner Gotterfunken", -123456),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            '01/01/2015 | Freude schoner Gotterf... |   ($1,234.56)',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "01/01/2015 | Freude schoner Gotterf... |   ($1,234.56)",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_euros(self):
-        currency = 'EUR'
-        locale = 'en_US'
+        currency = "EUR"
+        locale = "en_US"
         entries = [
-            create_entry('2015-01-01', 'Buy present', -1000),
+            create_entry("2015-01-01", "Buy present", -1000),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            u'01/01/2015 | Buy present               |      (€10.00)',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "01/01/2015 | Buy present               |      (€10.00)",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_dutch_locale(self):
-        currency = 'USD'
-        locale = 'nl_NL'
+        currency = "USD"
+        locale = "nl_NL"
         entries = [
-            create_entry('2015-03-12', 'Buy present', 123456),
+            create_entry("2015-03-12", "Buy present", 123456),
         ]
-        expected = '\n'.join([
-            'Datum      | Omschrijving              | Verandering  ',
-            '12-03-2015 | Buy present               |   $ 1.234,56 ',
-        ])
+        expected = "\n".join(
+            [
+                "Datum      | Omschrijving              | Verandering  ",
+                "12-03-2015 | Buy present               |   $ 1.234,56 ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_dutch_locale_and_euros(self):
-        currency = 'EUR'
-        locale = 'nl_NL'
+        currency = "EUR"
+        locale = "nl_NL"
         entries = [
-            create_entry('2015-03-12', 'Buy present', 123456),
+            create_entry("2015-03-12", "Buy present", 123456),
         ]
-        expected = '\n'.join([
-            'Datum      | Omschrijving              | Verandering  ',
-            u'12-03-2015 | Buy present               |   € 1.234,56 ',
-        ])
+        expected = "\n".join(
+            [
+                "Datum      | Omschrijving              | Verandering  ",
+                "12-03-2015 | Buy present               |   € 1.234,56 ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_dutch_negative_number_with_3_digits_before_decimal_point(self):
-        currency = 'USD'
-        locale = 'nl_NL'
+        currency = "USD"
+        locale = "nl_NL"
         entries = [
-            create_entry('2015-03-12', 'Buy present', -12345),
+            create_entry("2015-03-12", "Buy present", -12345),
         ]
-        expected = '\n'.join([
-            'Datum      | Omschrijving              | Verandering  ',
-            '12-03-2015 | Buy present               |    $ -123,45 ',
-        ])
+        expected = "\n".join(
+            [
+                "Datum      | Omschrijving              | Verandering  ",
+                "12-03-2015 | Buy present               |    $ -123,45 ",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
 
     def test_american_negative_number_with_3_digits_before_decimal_point(self):
-        currency = 'USD'
-        locale = 'en_US'
+        currency = "USD"
+        locale = "en_US"
         entries = [
-            create_entry('2015-03-12', 'Buy present', -12345),
+            create_entry("2015-03-12", "Buy present", -12345),
         ]
-        expected = '\n'.join([
-            'Date       | Description               | Change       ',
-            '03/12/2015 | Buy present               |     ($123.45)',
-        ])
+        expected = "\n".join(
+            [
+                "Date       | Description               | Change       ",
+                "03/12/2015 | Buy present               |     ($123.45)",
+            ]
+        )
         self.assertEqual(format_entries(currency, locale, entries), expected)
-
-
-if __name__ == '__main__':
-    unittest.main()
diff --git a/exercises/practice/list-ops/list_ops_test.py b/exercises/practice/list-ops/list_ops_test.py
index a2a30129..cb015acc 100644
--- a/exercises/practice/list-ops/list_ops_test.py
+++ b/exercises/practice/list-ops/list_ops_test.py
@@ -98,7 +98,3 @@ class ListOpsTest(unittest.TestCase):
 
     def test_reverse_reverse_mixed_types(self):
         self.assertEqual(reverse(["xyz", 4.0, "cat", 1]), [1, "cat", 4.0, "xyz"])
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/matching-brackets/matching_brackets_test.py b/exercises/practice/matching-brackets/matching_brackets_test.py
index fd23bd78..fb270ae8 100644
--- a/exercises/practice/matching-brackets/matching_brackets_test.py
+++ b/exercises/practice/matching-brackets/matching_brackets_test.py
@@ -72,7 +72,3 @@ class MatchingBracketsTest(unittest.TestCase):
             ),
             True,
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/pangram/pangram_test.py b/exercises/practice/pangram/pangram_test.py
index 1031cb4e..3f8c9aa7 100644
--- a/exercises/practice/pangram/pangram_test.py
+++ b/exercises/practice/pangram/pangram_test.py
@@ -50,7 +50,3 @@ class PangramTest(unittest.TestCase):
 
     def test_sentence_without_upper_bound(self):
         self.assertIs(is_pangram("abcdefghijklmnopqrstuvwxy"), False)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/pig-latin/pig_latin_test.py b/exercises/practice/pig-latin/pig_latin_test.py
index cf666ddf..cf46ae3c 100644
--- a/exercises/practice/pig-latin/pig_latin_test.py
+++ b/exercises/practice/pig-latin/pig_latin_test.py
@@ -73,7 +73,3 @@ class PigLatinTest(unittest.TestCase):
 
     def test_a_whole_phrase(self):
         self.assertEqual(translate("quick fast run"), "ickquay astfay unray")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/poker/poker_test.py b/exercises/practice/poker/poker_test.py
index f436e481..d0b2c52d 100644
--- a/exercises/practice/poker/poker_test.py
+++ b/exercises/practice/poker/poker_test.py
@@ -209,7 +209,3 @@ class PokerTest(unittest.TestCase):
         self.assertEqual(
             best_hands(["2H 3H 4H 5H 6H", "4D AD 3D 2D 5D"]), ["2H 3H 4H 5H 6H"]
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/protein-translation/protein_translation_test.py b/exercises/practice/protein-translation/protein_translation_test.py
index 046c0a43..4ce2d115 100644
--- a/exercises/practice/protein-translation/protein_translation_test.py
+++ b/exercises/practice/protein-translation/protein_translation_test.py
@@ -132,7 +132,3 @@ class ProteinTranslationTest(unittest.TestCase):
         value = "UGGUGUUAUUAAUGGUUU"
         expected = ["Tryptophan", "Cysteine", "Tyrosine"]
         self.assertEqual(proteins(value), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py
index 70d501c4..3536908a 100644
--- a/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py
+++ b/exercises/practice/pythagorean-triplet/pythagorean_triplet_test.py
@@ -6,10 +6,6 @@ from pythagorean_triplet import (
 
 # Tests adapted from `problem-specifications//canonical-data.json`
 
-# Python 2/3 compatibility
-if not hasattr(unittest.TestCase, "assertCountEqual"):
-    unittest.TestCase.assertCountEqual = unittest.TestCase.assertItemsEqual
-
 
 class PythagoreanTripletTest(unittest.TestCase):
     def test_triplets_whose_sum_is_12(self):
@@ -53,7 +49,3 @@ class PythagoreanTripletTest(unittest.TestCase):
                 [7500, 10000, 12500],
             ],
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py
index 65743180..873de583 100644
--- a/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py
+++ b/exercises/practice/rail-fence-cipher/rail_fence_cipher_test.py
@@ -33,7 +33,3 @@ class RailFenceCipherTest(unittest.TestCase):
             decode("133714114238148966225439541018335470986172518171757571896261", 6),
             "112358132134558914423337761098715972584418167651094617711286",
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/raindrops/raindrops_test.py b/exercises/practice/raindrops/raindrops_test.py
index 247f4daa..7a5969c9 100644
--- a/exercises/practice/raindrops/raindrops_test.py
+++ b/exercises/practice/raindrops/raindrops_test.py
@@ -63,7 +63,3 @@ class RaindropsTest(unittest.TestCase):
 
     def test_the_sound_for_3125_is_plang_as_it_has_a_factor_5(self):
         self.assertEqual(convert(3125), "Plang")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rectangles/rectangles_test.py b/exercises/practice/rectangles/rectangles_test.py
index 59c622de..05938fc5 100644
--- a/exercises/practice/rectangles/rectangles_test.py
+++ b/exercises/practice/rectangles/rectangles_test.py
@@ -98,7 +98,3 @@ class RectanglesTest(unittest.TestCase):
             ),
             5,
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/resistor-color-expert/.meta/tests.toml b/exercises/practice/resistor-color-expert/.meta/tests.toml
new file mode 100644
index 00000000..4f57723a
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/.meta/tests.toml
@@ -0,0 +1,40 @@
+# This is an auto-generated file.
+#
+# Regenerating this file via `configlet sync` will:
+# - Recreate every `description` key/value pair
+# - Recreate every `reimplements` key/value pair, where they exist in problem-specifications
+# - Remove any `include = true` key/value pair (an omitted `include` key implies inclusion)
+# - Preserve any other key/value pair
+#
+# As user-added comments (using the # character) will be removed when this file
+# is regenerated, comments can be added via a `comment` key.
+
+[8c4f9fb6-d477-4250-bc57-b325d2be226f]
+description = "Orange, orange, black, and red"
+
+[d1d4a769-9210-43cc-9a14-6af6ce4c0b00]
+description = "Blue, grey, brown, and violet"
+
+[6af91bc3-8275-4c38-920a-185d30feb5f3]
+description = "Red, black, red, and green"
+
+[9c4630bf-0dda-4212-baca-2f5111530b4d]
+description = "Green, brown, orange, and grey"
+
+[5880ddf1-0dc6-4bd0-b9de-5626117cd2c7]
+description = "One black band"
+
+[a5cfda34-3c02-4bda-b183-726791fb43b2]
+description = "Orange, orange, yellow, black, and brown"
+
+[4f0ad96c-cdab-4c84-95dd-7074e889e001]
+description = "Red, green, yellow, yellow, and brown"
+
+[48c66841-208c-46a7-8992-1e84a2eda9e2]
+description = "Blue, grey, white, brown, and brown"
+
+[4f3aeb0c-fd9d-4cbd-b204-554e61978f73]
+description = "Violet, orange, red, and grey"
+
+[1ae3a17f-8bc0-449c-9831-fddfd2d91c5d]
+description = "Brown, red, orange, green, and blue"
diff --git a/exercises/practice/resistor-color-expert/resistor_color_expert_test.py b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py
new file mode 100644
index 00000000..bcf2052c
--- /dev/null
+++ b/exercises/practice/resistor-color-expert/resistor_color_expert_test.py
@@ -0,0 +1,51 @@
+import unittest
+
+from resistor_color_expert import (
+    resistor_label,
+)
+
+# Tests adapted from `problem-specifications//canonical-data.json`
+
+
+class ResistorColorExpertTest(unittest.TestCase):
+    def test_orange_orange_black_and_red(self):
+        self.assertEqual(resistor_label(["orange", "orange", "black", "red"]), "33 ohms ±2%")
+
+    def test_blue_grey_brown_and_violet(self):
+        self.assertEqual(resistor_label(["blue", "grey", "brown", "violet"]), "680 ohms ±0.1%")
+
+    def test_red_black_red_and_green(self):
+        self.assertEqual(resistor_label(["red", "black", "red", "green"]), "2 kiloohms ±0.5%")
+
+    def test_green_brown_orange_and_grey(self):
+        self.assertEqual(
+            resistor_label(["green", "brown", "orange", "grey"]), "51 kiloohms ±0.05%"
+        )
+
+    def test_one_black_band(self):
+        self.assertEqual(resistor_label(["black"]), "0 ohms")
+
+    def test_orange_orange_yellow_black_and_brown(self):
+        self.assertEqual(
+            resistor_label(["orange", "orange", "yellow", "black", "brown"]), "334 ohms ±1%"
+        )
+
+    def test_red_green_yellow_yellow_and_brown(self):
+        self.assertEqual(
+            resistor_label(["red", "green", "yellow", "yellow", "brown"]), "2.54 megaohms ±1%"
+        )
+
+    def test_blue_grey_white_red_and_brown(self):
+        self.assertEqual(
+            resistor_label(["blue", "grey", "white", "brown", "brown"]), "6.89 kiloohms ±1%"
+        )
+
+    def test_violet_orange_red_and_grey(self):
+        self.assertEqual(
+            resistor_label(["violet", "orange", "red", "grey"]), "7.3 kiloohms ±0.05%"
+        )
+
+    def test_brown_red_orange_green_and_blue(self):
+        self.assertEqual(
+            resistor_label(["brown", "red", "orange", "green", "blue"]), "12.3 megaohms ±0.25%"
+        )
diff --git a/exercises/practice/resistor-color/resistor_color_test.py b/exercises/practice/resistor-color/resistor_color_test.py
index 62ab4862..631bc951 100644
--- a/exercises/practice/resistor-color/resistor_color_test.py
+++ b/exercises/practice/resistor-color/resistor_color_test.py
@@ -32,7 +32,3 @@ class ResistorColorTest(unittest.TestCase):
             "white",
         ]
         self.assertEqual(colors(), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rest-api/rest_api_test.py b/exercises/practice/rest-api/rest_api_test.py
index 8092aec2..4a5c525f 100644
--- a/exercises/practice/rest-api/rest_api_test.py
+++ b/exercises/practice/rest-api/rest_api_test.py
@@ -159,7 +159,3 @@ class RestApiTest(unittest.TestCase):
             ]
         }
         self.assertDictEqual(json.loads(response), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/reverse-string/reverse_string_test.py b/exercises/practice/reverse-string/reverse_string_test.py
index 78212b41..a1a28dc1 100644
--- a/exercises/practice/reverse-string/reverse_string_test.py
+++ b/exercises/practice/reverse-string/reverse_string_test.py
@@ -25,7 +25,3 @@ class ReverseStringTest(unittest.TestCase):
 
     def test_an_even_sized_word(self):
         self.assertEqual(reverse("drawer"), "reward")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rna-transcription/rna_transcription_test.py b/exercises/practice/rna-transcription/rna_transcription_test.py
index aa062329..e6c88062 100644
--- a/exercises/practice/rna-transcription/rna_transcription_test.py
+++ b/exercises/practice/rna-transcription/rna_transcription_test.py
@@ -25,7 +25,3 @@ class RnaTranscriptionTest(unittest.TestCase):
 
     def test_rna_complement(self):
         self.assertEqual(to_rna("ACGTGGTCTTAA"), "UGCACCAGAAUU")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/rotational-cipher/rotational_cipher_test.py b/exercises/practice/rotational-cipher/rotational_cipher_test.py
index 88b64d6e..b6e760a4 100644
--- a/exercises/practice/rotational-cipher/rotational_cipher_test.py
+++ b/exercises/practice/rotational-cipher/rotational_cipher_test.py
@@ -40,7 +40,3 @@ class RotationalCipherTest(unittest.TestCase):
             rotate("The quick brown fox jumps over the lazy dog.", 13),
             "Gur dhvpx oebja sbk whzcf bire gur ynml qbt.",
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/scale-generator/scale_generator_test.py b/exercises/practice/scale-generator/scale_generator_test.py
index 7edc118c..5757fad3 100644
--- a/exercises/practice/scale-generator/scale_generator_test.py
+++ b/exercises/practice/scale-generator/scale_generator_test.py
@@ -78,7 +78,3 @@ class ScaleGeneratorTest(unittest.TestCase):
     def test_enigmatic(self):
         expected = ["G", "G#", "B", "C#", "D#", "F", "F#", "G"]
         self.assertEqual(Scale("G").interval("mAMMMmm"), expected)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/scrabble-score/scrabble_score_test.py b/exercises/practice/scrabble-score/scrabble_score_test.py
index e8d7eff7..2ea8fd32 100644
--- a/exercises/practice/scrabble-score/scrabble_score_test.py
+++ b/exercises/practice/scrabble-score/scrabble_score_test.py
@@ -40,7 +40,3 @@ class ScrabbleScoreTest(unittest.TestCase):
 
     def test_entire_alphabet_available(self):
         self.assertEqual(score("abcdefghijklmnopqrstuvwxyz"), 87)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/secret-handshake/secret_handshake_test.py b/exercises/practice/secret-handshake/secret_handshake_test.py
index 959c8669..b9e534d5 100644
--- a/exercises/practice/secret-handshake/secret_handshake_test.py
+++ b/exercises/practice/secret-handshake/secret_handshake_test.py
@@ -44,7 +44,3 @@ class SecretHandshakeTest(unittest.TestCase):
 
     def test_do_nothing_for_zero(self):
         self.assertEqual(commands("00000"), [])
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/sieve/sieve_test.py b/exercises/practice/sieve/sieve_test.py
index d9fa6726..91bb00e7 100644
--- a/exercises/practice/sieve/sieve_test.py
+++ b/exercises/practice/sieve/sieve_test.py
@@ -194,7 +194,3 @@ class SieveTest(unittest.TestCase):
                 997,
             ],
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/simple-cipher/simple_cipher_test.py b/exercises/practice/simple-cipher/simple_cipher_test.py
index 07c0c3d5..8b911d73 100644
--- a/exercises/practice/simple-cipher/simple_cipher_test.py
+++ b/exercises/practice/simple-cipher/simple_cipher_test.py
@@ -64,7 +64,3 @@ class SubstitutionCipherTest(unittest.TestCase):
     def test_can_decode_messages_longer_than_the_key(self):
         cipher = Cipher("abc")
         self.assertEqual(cipher.decode("iboaqcnecbfcr"), "iamapandabear")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/space-age/space_age_test.py b/exercises/practice/space-age/space_age_test.py
index b3f91ca0..1d248b19 100644
--- a/exercises/practice/space-age/space_age_test.py
+++ b/exercises/practice/space-age/space_age_test.py
@@ -31,7 +31,3 @@ class SpaceAgeTest(unittest.TestCase):
 
     def test_age_on_neptune(self):
         self.assertEqual(SpaceAge(1821023456).on_neptune(), 0.35)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/spiral-matrix/spiral_matrix_test.py b/exercises/practice/spiral-matrix/spiral_matrix_test.py
index 1291342a..3e6658cf 100644
--- a/exercises/practice/spiral-matrix/spiral_matrix_test.py
+++ b/exercises/practice/spiral-matrix/spiral_matrix_test.py
@@ -37,7 +37,3 @@ class SpiralMatrixTest(unittest.TestCase):
                 [13, 12, 11, 10, 9],
             ],
         )
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py
index f7e8bfb5..29d0ac72 100644
--- a/exercises/practice/sum-of-multiples/sum_of_multiples_test.py
+++ b/exercises/practice/sum-of-multiples/sum_of_multiples_test.py
@@ -57,7 +57,3 @@ class SumOfMultiplesTest(unittest.TestCase):
         self,
     ):
         self.assertEqual(sum_of_multiples(10000, [2, 3, 5, 7, 11]), 39614537)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/triangle/triangle_test.py b/exercises/practice/triangle/triangle_test.py
index 2de48d33..f67d63fa 100644
--- a/exercises/practice/triangle/triangle_test.py
+++ b/exercises/practice/triangle/triangle_test.py
@@ -76,7 +76,3 @@ class ScaleneTriangleTest(unittest.TestCase):
 
     def test_sides_may_be_floats(self):
         self.assertIs(scalene([0.5, 0.4, 0.6]), True)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/two-fer/two_fer_test.py b/exercises/practice/two-fer/two_fer_test.py
index 540930da..ea74f116 100644
--- a/exercises/practice/two-fer/two_fer_test.py
+++ b/exercises/practice/two-fer/two_fer_test.py
@@ -16,7 +16,3 @@ class TwoFerTest(unittest.TestCase):
 
     def test_another_name_given(self):
         self.assertEqual(two_fer("Bob"), "One for Bob, one for me.")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/word-count/word_count_test.py b/exercises/practice/word-count/word_count_test.py
index b63a9eb3..329301a3 100644
--- a/exercises/practice/word-count/word_count_test.py
+++ b/exercises/practice/word-count/word_count_test.py
@@ -121,7 +121,3 @@ class WordCountTest(unittest.TestCase):
 
     def test_multiple_apostrophes_ignored(self):
         self.assertEqual(count_words("''hey''"), {"hey": 1})
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/word-search/word_search_test.py b/exercises/practice/word-search/word_search_test.py
index 6ee05041..e5a32cee 100644
--- a/exercises/practice/word-search/word_search_test.py
+++ b/exercises/practice/word-search/word_search_test.py
@@ -310,7 +310,3 @@ class WordSearchTest(unittest.TestCase):
     def test_should_not_wrap_around_vertically_to_find_a_word(self):
         puzzle = WordSearch(["s", "u", "r", "a", "b", "c", "t"])
         self.assertIsNone(puzzle.search("rust"))
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/yacht/yacht_test.py b/exercises/practice/yacht/yacht_test.py
index 5c262c88..32b92994 100644
--- a/exercises/practice/yacht/yacht_test.py
+++ b/exercises/practice/yacht/yacht_test.py
@@ -92,7 +92,3 @@ class YachtTest(unittest.TestCase):
 
     def test_yacht_as_choice(self):
         self.assertEqual(yacht.score([2, 2, 2, 2, 2], yacht.CHOICE), 10)
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py
index 034d4ccc..4b35afb7 100644
--- a/exercises/practice/zebra-puzzle/zebra_puzzle_test.py
+++ b/exercises/practice/zebra-puzzle/zebra_puzzle_test.py
@@ -14,7 +14,3 @@ class ZebraPuzzleTest(unittest.TestCase):
 
     def test_resident_who_owns_zebra(self):
         self.assertEqual(owns_zebra(), "Japanese")
-
-
-if __name__ == "__main__":
-    unittest.main()
diff --git a/exercises/practice/zipper/zipper_test.py b/exercises/practice/zipper/zipper_test.py
index 5883e80d..d770c92b 100644
--- a/exercises/practice/zipper/zipper_test.py
+++ b/exercises/practice/zipper/zipper_test.py
@@ -315,7 +315,3 @@ class ZipperTest(unittest.TestCase):
         expected = Zipper.from_tree(final).right().to_tree()
 
         self.assertEqual(result, expected)
-
-
-if __name__ == "__main__":
-    unittest.main()