Repository: exercism/python
Pull request #2729 (2021-11-04 12:01:33)

[New Concept Exercise] : Generators (Plane Tickets)
Heya,

This is the new (hopefully) concept exercise for the Python track. It is called plane tickets as the student will need to generate seat numbers (using a generator) and also assign those to a list of passengers.

It currently has 2 tasks. We might need to add more, but I wanted to see if any of you had an idea of what that task should be. 

The concept exercise currently also does not have an appropriate `icon`, what about [Atbash-Cipher](https://dg8krxphbh767.cloudfront.net/exercises/atbash-cipher.svg). I don't know if it is possible to create a custom one?

Anyway, I'd love your feedback. Thanks 💙 

github-actions[bot]: Hi & Welcome! 👋🏽&nbsp;👋

**Thank you** for contributing to `exercism/python` 💛&nbsp;💙 -- we really appreciate it! 🌟 🌈


<br>

This is an automated [🤖&nbsp;🤖 &nbsp;] comment for the  **`maintainers`** of this repository, notifying them of your contribution.  🎉
&nbsp;  Someone will review/reply to your changes shortly. (_usually within **72 hours**._)
You can safely ignore the **`maintainers`**  section below.

<br>
<details open>
  <summary>⚠️ &nbsp;<b>Please be aware</b>&nbsp;⚠️ </summary>

  &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;_This repo does not generally accept Pull Requests unless they follow our [contributing guidelines](https://github.com/exercism/python/blob/main/CONTRIBUTING.md) and are:_

  &nbsp;&nbsp;&nbsp;&nbsp; 1️⃣ &nbsp;&nbsp;&nbsp; Small, contained fixes for typos/grammar/punctuation/code syntax on [one] exercise,
  &nbsp;&nbsp;&nbsp;&nbsp; 2️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Medium changes that have been agreed/discussed via a filed issue,
  &nbsp;&nbsp;&nbsp;&nbsp; 3️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Contributions from our [help wanted][help-wanted] issue list,
  &nbsp;&nbsp;&nbsp;&nbsp; 4️⃣ &nbsp;&nbsp;&nbsp;&nbsp;Larger (_previously agreed-upon_) contributions from recent & regular (_within the last 6 months_) contributors.

  &nbsp;&nbsp;&nbsp;&nbsp;Pull Requests not in these categories **will be closed**.  😞

</details>
<br>


---

#### **`💙  It looks like you are changing/adding files in a Concept Exercise! 💙 `**

<br>
<details>
  <summary>‼️&nbsp;&nbsp;<b><em>Did You...</em></b></summary>
<br>
<table>
<td>
<br>

 - [ ]  &nbsp;Update & rebase your branch with any (recent) upstream changes.
 - [ ]  &nbsp;Spell and grammar check all prose changes.
 - [ ]  &nbsp;Run [Prettier](https://prettier.io/) on all markdown and JSON files.
     - (_Optionally_) run [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) to help format your code.
 - [ ]  &nbsp;Run [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) to check general code style standards.
 - [ ] &nbsp; Run [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc) to check extended code style standards.
 - [ ]  &nbsp;Use pytest or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed `example.py`/`exemplar.py`files
 &nbsp;against their associated test files.
 - [ ]  &nbsp;Similarly, use [pytest](https://docs.pytest.org/en/6.2.x/contents.html) or the [python-track-test-runner](https://github.com/exercism/python-test-runner) to test any changed _**test**_ files.
     - Check that tests **fail** properly, as well as succeed.
     &nbsp;(_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
 - [ ]  &nbsp;Double-check all files for proper EOL.
 - [ ]  &nbsp;[Regenerate](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#generating-practice-exercise-documents) exercise documents when you modified or created a `hints.md` file for a practice exercise.
 - [ ]  &nbsp;[Regenerate the test file](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates) if you modified or created a `JinJa2` template file for a practice exercise.
     -  Run the generated test file result against its `example.py`.
 - [ ]  &nbsp;Run [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint) if the track [config.json](https://github.com/exercism/docs/blob/main/building/tracks/config-json.md), or any other exercise `config.json` has been modified.

<br>
</td>
</table>
<br>
</details>

<details>
<summary>✅️&nbsp;&nbsp;<b><em>Have You Checked:</em></b></summary>
<br>
<table>
<td>

.
_**Are there any additional changes you need to make?**_
Please make sure all associated files are present and consistent with your changes!

[Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)

- [ ] `.docs/hints.md`
- [ ] `.docs/instructions.md`
- [ ] `.docs/introduction.md`
- [ ] `.meta/config.json`
- [ ] `.meta/design.md`
- [ ] `.meta/exemplar.py` (_exemplar solution_)
- [ ] `<exercise-slug>_test.py` (_test file_)
- [ ] `<exercise-slug>.py` (_stub file_)
- [ ] `concepts/../introduction.md`
- [ ] `concepts/../about.md`
- [ ] `concepts/../links.json`
- [ ] `concepts/../.meta/config.json`

</td>
</table>
<br>
</details>



<h2>🛠️ &nbsp;&nbsp; Maintainers &nbsp;&nbsp;</h2>
<h3> Please take note 📒  of the following sections/review items 👀   ✨ </h3>
<br>
<br>
<details>
<summary> 🌈  &nbsp;Acknowledgements and Reputation</summary>
<br>
<table>
<td>

- ❓ Does this PR need to receive a label with a [`reputation modifier`](https://github.com/exercism/docs/blob/main/using/product/reputation.md#creating-a-pull-request)?
     -  `medium` is awarded by default.
- ❓ Does this contributor need to be added to the **exercise** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md#file-metaconfigjson)?
- ❓ Does this contributor need to be added to the **concept** [`authors` or `contributors`](https://github.com/exercism/docs/blob/main/building/tracks/concepts.md#file-metaconfigjson)?
- ❓ Is there an associated issue or issues that should be linked to this PR?

</td>
</table>
</details>
<details>
<summary>💫 &nbsp;General Code Quality</summary>
<ul>
<table>
<td>
  <h4>Verify:</h4>

  -  [ ]  &nbsp;The branch was updated & rebased with any (recent) upstream changes.
  -  [ ]  &nbsp;All prose was checked for spelling and grammar.
  -  [ ]  &nbsp;Files are formatted via [yapf](https://github.com/google/yapf) ([_yapf config_](https://github.com/exercism/python/blob/main/.style.yapf)) & conform to our [coding standards](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#coding-standards)
  -  [ ]  &nbsp;Files pass [flake8](http://flake8.pycqa.org/) with [_flake8 config_](https://github.com/exercism/python/blob/main/.flake8) & [pylint](https://pylint.pycqa.org/en/v2.11.1/user_guide/index.html) with [_pylint config_](https://github.com/exercism/python/blob/main/pylintrc).
  -  [ ]  &nbsp;Changed `example.py`/`exemplar.py` files still pass their associated **test files**.
  -  [ ]  &nbsp;Changed _**test files**_ still work with associated `example.py`/`exemplar.py` files.
      - Check that tests **fail** properly, as well as succeed.
             (_**e.g.**, make some tests fail on purpose to "test the tests" & failure messages_).
  -  [ ]  &nbsp;All files have proper EOL.
  -  [ ]  &nbsp;If a `JinJa2` template was modified/created, was the test file [regenerated](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#auto-generated-test-files-and-test-templates)?
      - Does the regenerated test file successfully test the exercises `example.py` file?
  -  [ ]  &nbsp;The branch passes all CI checks & [`configlet-lint`](https://github.com/exercism/configlet#configlet-lint).

<br>
</td>
</table>
</ul>
<br>
</details>
<details>
<summary>🌿 &nbsp;Changes to Concept Exercises</summary>
<br>

  -   ❓ Are all required files still up-to-date & configured correctly for this change?_
  -   ❓ Does `<exercise>/.meta/design.md` need to be updated with new implementation/design decisions
  -   ❓ Do these changes require follow-on/supporting changes to related **concept** documents?

<ul>
<details>
<summary><em><b> ✨ &nbsp;Where applicable, check the following&nbsp;✨  </b></em></summary>
<br>
<ul>
<table>
<td>

(_as a reminder: [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)_)
  - [ ] Exercise `introduction.md`
    - [ ]  Do all code examples compile, run, and return the shown output?
    - [ ]  Are all the code examples formatted per the Python docs?
  - [ ]  Exercise `instructions.md`
  - [ ]  Exercise  `hints.md`
  - [ ]   Check that exercise `design.md` was fulfilled or edited appropriately
  - [ ]   Exercise `exemplar.py`
    - [ ]  Only uses syntax previously introduced or explained.
    - [ ]  Is correct and appropriate for the exercise and story.
  - [ ]   Exercise `<exercise_name>.py` (stub)
    - [ ]  Includes appropriate docstrings and function names.
    - [ ]  Includes `pass` for each function
    - [ ]  Includes an EOL at the end
  - [ ]   Exercise  `<exercise_name>_test.py`
    - [ ]   Tests cover all (reasonable) inputs and scenarios
    - [ ]   At least one test for each task in the exercise
    - [ ]   If using subtests or fixtures they're [formatted correctly](https://github.com/exercism/python/blob/main/CONTRIBUTING.md#Test-File_style) for the runner
    - [ ]   Classnames are `<ExerciseName>Test`
    - [ ]   Test functions are `test_<test_name>`
  - [ ]   Exercise `config.json` --> **valid UUID4**
  - [ ]   Corresponding concept `introduction.md`
  - [ ]   Corresponding concept `about.md`
  - [ ]   Concept  `config.json`
  - [ ]   All Markdown Files :  Prettier linting (_for all markdown docs_)
  - [ ]   All Code files: PyLint linting (**_except for test files_**)
  - [ ]   All files with text:  Spell check & grammar review.

</td>
</table>
</ul>
</details>
</ul>
</details>

<details>
<summary>🚀&nbsp;  Changes to Practice Exercises</summary>
<br>
<ul>
<table>
<td>

_Is the exercise is in line with [Practice Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/practice-exercises.md)?_
- [ ] `.docs/instructions.md`(**required**)
   - Was this file updated and regenerated properly?
- [ ] `.docs/introduction.md`(_optional_)
- [ ] `.docs/introduction.append.md`(_optional_)
- [ ] `.docs/instructions.append.md` (_optional_)
   - Are any additional instructions needed/provided?
   &nbsp;(_e.g. error handling or information on classes_)
- [ ] `.docs/hints.md`(_optional_)
   - Was this file regenerated properly?
- [ ] `.meta/config.json` (**required**)
- [ ] `.meta/example.py` (**required**)
   - Does this pass all the current tests as written/generated?
- [ ] `.meta/design.md` (_optional_)
- [ ] `.meta/template.j2` (_template for generating tests from canonical data_)
  - Was a test file properly regenerated from this template?
- [ ] `.meta/tests.toml`
   -  Are there additional test cases to include or exclude?
   -  Are there any Python-specific test cases needed for this exercise?
- [ ] `<exercise-slug>_test.py`
   - Does this file need to be regenerated?
   - Does this file correctly test the `example.py` file?
   - Does this file correctly report test failures and messages?
- [ ] `<exercise-slug>.py` (**required**)
   - Does this stub have enough information to get
   &nbsp;the student started coding a valid solution?

</td>
</table>
</ul>
</details>
  <details>
    <summary>🐣 &nbsp;Brand-New Concept Exercises</summary>
<br>
<ul>
<table>
<td>

_Is the exercise is in line with [Concept Exercise Anatomy](https://github.com/exercism/docs/blob/main/building/tracks/concept-exercises.md)?_
  - [ ] Exercise `introduction.md`
    - [ ]  Do all code examples compile, run, and return the shown output?
    - [ ]  Are all the code examples formatted per the Python docs?
  - [ ]  Exercise `instructions.md`
  - [ ]  Exercise  `hints.md`
  - [ ]   Check that exercise `design.md` was fulfilled or edited appropriately
  - [ ]   Exercise `exemplar.py`
    - [ ]  Only uses syntax previously introduced or explained.
    - [ ]  Is correct and appropriate for the exercise and story.
  - [ ]   Exercise `<exercise_name>.py` (stub)
    - [ ]  Includes appropriate docstrings and function names.
    - [ ]  Includes `pass` for each function
    - [ ]  Includes an EOL at the end
  - [ ]   Exercise  `<exercise_name>_test.py`
    - [ ]   Tests cover all (reasonable) inputs and scenarios
    - [ ]   At least one test for each task in the exercise
    - [ ]   If using subtests or fixtures they're formatted correctly for the runner
    - [ ]   Classnames are `<ExerciseName>Test`
    - [ ]   Test functions are `test_<test_name>`
  - [ ]   Exercise `config.json` --> valid UUID4
  - [ ]   Corresponding concept `introduction.md`
  - [ ]   Corresponding concept `about.md`
  - [ ]   Concept  `config.json`
  - [ ]   All Markdown Files :  Prettier linting (_for all markdown docs_)
  - [ ]   All Code files: Flake8 & PyLint linting
  - [ ]   All Code Examples:  proper formatting and fencing.  Verify they run in the REPL
  - [ ]   All files with text:  Spell check & grammar review.

<br>
</td>
</table>
</ul>
</details>
<br>
<h3>Our &nbsp; 💖  &nbsp; for all your review efforts! 🌟 🦄</h3>


<!-- pr-commenter-metadata: any-file-changed,change-concept-exercise -->
J08K: Resolves #2293 
kotp: > ```suggestion
> Generators are constructed much like other  functions, but require a [`yield` expression](#the-yield-expression), which we will explore  in depth a bit later.
> ```
> 

```suggestion
Generators are constructed much like other functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later.
```


BethanyG: @J08K  - it looks as though you accepted zero of my suggestions, so wondering what I should be reviewing here?  Plz let me know - thanks!
J08K: > @J08K - it looks as though you accepted zero of my suggestions, so wondering what I should be reviewing here? Plz let me know - thanks!

Really? I think I missed them then, let me have a look! I am so sorry!
BethanyG: Hi @J08K  -- Just checking in -- are you still interested in working on this?  Let me know - thanks!
BethanyG: @J08K  - Per our Slack conversation, I will be merging this so you can work on it offline.
@kotp - This was not a PR that was intended for review at this time, we're optimistically merging as-is.  Could you please approve so I can merge?  Thanks.

Patch:
diff --git a/.github/workflows/configlet.yml b/.github/workflows/configlet.yml
index 5f7632d9..47eb8754 100644
--- a/.github/workflows/configlet.yml
+++ b/.github/workflows/configlet.yml
@@ -1,16 +1,15 @@
-name: Configlet CI
+name: Configlet
 
-on: [push, pull_request, workflow_dispatch]
+on:
+  pull_request:
+  push:
+    branches:
+      - main
+  workflow_dispatch:
+
+permissions:
+  contents: read
 
 jobs:
   configlet:
-    runs-on: ubuntu-latest
-
-    steps:
-    - uses: actions/checkout@v3
-
-    - name: Fetch configlet
-      uses: exercism/github-actions/configlet-ci@main
-        
-    - name: Configlet Linter
-      run: configlet lint
+    uses: exercism/github-actions/.github/workflows/configlet.yml@main
diff --git a/.github/workflows/sync-labels.yml b/.github/workflows/sync-labels.yml
index 9b9509af..e7b99e50 100644
--- a/.github/workflows/sync-labels.yml
+++ b/.github/workflows/sync-labels.yml
@@ -2,20 +2,18 @@ name: Tools
 
 on:
   push:
-    branches: [main]
+    branches:
+      - main
     paths:
       - .github/labels.yml
       - .github/workflows/sync-labels.yml
-  schedule:
-    - cron: 0 0 1 * *
   workflow_dispatch:
+  schedule:
+    - cron: 0 0 1 * * # First day of each month
+
+permissions:
+  issues: write
 
 jobs:
   sync-labels:
-    name: Sync labels
-    runs-on: ubuntu-latest
-    steps:
-      - uses: actions/checkout@v3
-      - uses: micnncim/action-label-syncer@3abd5ab72fda571e69fffd97bd4e0033dd5f495c
-        env:
-          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+    uses: exercism/github-actions/.github/workflows/labels.yml@main
diff --git a/concepts/generators/.meta/config.json b/concepts/generators/.meta/config.json
index 9b9e8da5..2204a700 100644
--- a/concepts/generators/.meta/config.json
+++ b/concepts/generators/.meta/config.json
@@ -1,5 +1,5 @@
 {
-  "blurb": "TODO: add blurb for this concept",
-  "authors": ["bethanyg", "cmccandless"],
+  "blurb": "Learn about generators by assigning seats to passengers.",
+  "authors": ["J08K"],
   "contributors": []
 }
diff --git a/concepts/generators/about.md b/concepts/generators/about.md
index c628150d..f1b97291 100644
--- a/concepts/generators/about.md
+++ b/concepts/generators/about.md
@@ -1,2 +1,132 @@
-#TODO: Add about for this concept.
+# About
 
+## Constructing a generator
+
+Generators are constructed much like other looping or recursive functions, but require a [`yield` expression](#the-yield-expression), which we will explore in depth a bit later.
+
+
+An example is a function that returns the _squares_ from a given list of numbers.  
+As currently written, all input must be processed before any values can be returned:  
+
+
+```python
+>>> def squares(list_of_numbers):
+>>>     squares = []
+>>>     for number in list_of_numbers:
+>>>         squares.append(number ** 2)
+>>>     return squares
+```
+
+You can convert that function into a generator like this:
+
+```python
+def squares(list_of_numbers):
+    for number in list_of_number:
+        yield number ** 2
+```
+
+The rationale behind this is that you use a generator when you do not need all the values _at once_.
+
+This saves memory and processing power, since only the value you are _currently working on_ is calculated.
+
+
+## Using a generator
+
+Generators may be used in place of most `iterables` in Python. This includes  _functions_ or _objects_ that require an `iterable`/`iterator` as an argument.
+
+To use the `squares()` generator:
+
+```python
+>>> squared_numbers = squares([1, 2, 3, 4])
+
+>>> for square in squared_numbers:
+>>>     print(square)
+1
+4
+9
+16
+```
+
+Values within a generator can also be produced/accessed via the `next()` function. 
+`next()` calls the `__next__()` method of a generator object, "advancing" or evaluating the generator code up to its `yield` expression, which then "yields" or returns the value.
+
+```python
+square_generator = squares([1, 2])
+
+>>> next(square_generator)
+1
+>>> next(square_generator)
+4
+```
+
+When a `generator` is fully consumed and has no more values to return, it throws a `StopIteration` error.
+
+```python
+>>> next(square_generator)
+Traceback (most recent call last):
+  File "<stdin>", line 1, in <module>
+StopIteration
+```
+
+### Difference between iterables and generators
+
+Generators are a special sub-set of _iterators_.
+`Iterators` are the mechanism/protocol that enables looping over _iterables_.
+Generators and and the iterators returned by common Python (`iterables`)[https://wiki.python.org/moin/Iterator] act very similarly, but there are some important differences to note:
+
+
+- Generators are _one-way_; there is no "backing up" to a previous value.
+
+- Iterating over generators consume the returned values; no resetting.
+- Generators (_being lazily evaluated_) are not sortable and can not be reversed.
+
+- Generators do _not_ have `indexes`, so you can't reference a previous or future value using addition or subtraction.
+
+- Generators cannot be used with the `len()` function.
+
+- Generators can be _finite_ or _infinite_, be careful when collecting all values from an _infinite_ generator.
+
+## The yield expression
+
+The [yield expression](https://docs.python.org/3.8/reference/expressions.html#yield-expressions) is very similar to the `return` expression.
+
+_Unlike_ the `return` expression, `yield` gives up values to the caller at a _specific point_, suspending evaluation/return of any additional values until they are requested.
+
+When `yield` is evaluated, it pauses the execution of the enclosing function and returns any values of the function _at that point in time_.
+
+The function then _stays in scope_, and when `__next__()` is called, execution resumes until `yield` is encountered again.
+
+Note: _Using `yield` expressions is prohibited outside of functions._
+
+```python
+>>> def infinite_sequence():
+>>>     current_number = 0
+>>>     while True:
+>>>         yield current_number
+>>>         current_number += 1
+
+>>> lets_try = infinite_sequence()
+>>> lets_try.__next__()
+0
+>>> lets_try.__next__()
+1
+```
+
+## Why generators?
+
+Generators are useful in a lot of applications.
+
+When working with a large collection, you might not want to put all of its values into `memory`.
+A generator can be used to work on larger data piece-by-piece, saving memory and improving performance.
+
+Generators are also very helpful when a process or calculation is _complex_, _expensive_, or _infinite_:
+
+```python
+>>> def infinite_sequence():
+>>>     current_number = 0
+>>>     while True:
+>>>         yield current_number
+>>>         current_number += 1
+```
+
+Now whenever `__next__()` is called on the `infinite_sequence` object, it will return the _previous number_ + 1.
diff --git a/concepts/generators/introduction.md b/concepts/generators/introduction.md
index fcde7464..2c148371 100644
--- a/concepts/generators/introduction.md
+++ b/concepts/generators/introduction.md
@@ -1,2 +1,5 @@
-#TODO: Add introduction for this concept.
+# Introduction
 
+A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation).
+
+_Lazy iterators_ are similar to `lists`, and other `iterators`, but with one key difference: They do not store their `values` in memory, but _generate_ their values when needed.
diff --git a/concepts/generators/links.json b/concepts/generators/links.json
index eb5fb7c3..b8ae2f7b 100644
--- a/concepts/generators/links.json
+++ b/concepts/generators/links.json
@@ -1,18 +1,10 @@
 [
   {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
+    "url": "https://docs.python.org/3.8/reference/expressions.html#yield-expressions",
+    "description": "Official Python 3.8 docs for the yield expression."
   },
   {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
-  },
-  {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
-  },
-  {
-    "url": "http://example.com/",
-    "description": "TODO:  add new link (above) and write a short description here of the resource."
+    "url": "https://en.wikipedia.org/wiki/Lazy_evaluation",
+    "description": "Wikipedia page about lazy evaluation"
   }
 ]
diff --git a/config.json b/config.json
index 25b5653c..31a38abd 100644
--- a/config.json
+++ b/config.json
@@ -176,12 +176,37 @@
         "prerequisites": ["basics", "dicts", "lists", "loops", "tuples"],
         "status": "beta"
       },
+      {
+        "slug": "plane-tickets",
+        "name": "Plane Tickets",
+        "uuid": "3ba3fc89-3e1b-48a5-aff0-5aeaba8c8810",
+        "concepts": [
+          "generators"
+        ],
+        "prerequisites": [
+          "conditionals",
+          "dicts",
+          "lists",
+          "loops"
+        ],
+        "status": "wip"
+      },
       {
         "slug": "ellens-alien-game",
         "name": "Ellen's Alien Game",
         "uuid": "3550ec07-f6c6-48bd-b2b4-086e75faf9e7",
         "concepts": ["classes"],
-        "prerequisites": ["basics", "bools", "comparisons", "loops", "dicts", "lists", "numbers", "sets", "strings", "tuples"],
+        "prerequisites": ["basics",
+          "bools",
+          "comparisons",
+          "loops",
+          "dicts",
+          "lists",
+          "numbers",
+          "sets",
+          "strings",
+          "tuples"
+        ],
         "status": "beta"
       }
     ],
diff --git a/docs/TOOLS.md b/docs/TOOLS.md
index 80eb5e5a..a5673875 100644
--- a/docs/TOOLS.md
+++ b/docs/TOOLS.md
@@ -37,7 +37,6 @@ To create a virtual environment, `cd` to the directory you want to store your en
 ```bash
 $ python3 -m venv {name_of_virtualenv}
 created virtual environment ... in 8568ms
-```
 
 #### Activating your virtual environment
 
@@ -181,7 +180,7 @@ Sublime Text comes with a lot of tools already, but some of these tools could ma
 
 ### JupyterLab
 
-[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text.  
+[Jupyter lab](https://jupyter.org/install) is an in-browser code editor that runs on your machine, combining the ability to write Markdown along side executable code and data. Jupyter supports multiple programming languages via `kernels` and can display graphs and tables as well as code and text.
 
 Jupyter comes pre-packaged with Anaconda.  You can also install it in a **virtual environment** using `pip` or `conda`.  Jupyter will use that environment and its Python version. Jupyter is most well-known for its code "notebooks". It is commonly used among data-scientists.
 
diff --git a/exercises/concept/plane-tickets/.docs/hints.md b/exercises/concept/plane-tickets/.docs/hints.md
new file mode 100644
index 00000000..72d814b2
--- /dev/null
+++ b/exercises/concept/plane-tickets/.docs/hints.md
@@ -0,0 +1,11 @@
+# Hints
+
+## 1. Generate an amount of seats
+
+- The returned value should be of _type_ `generator`.
+- Row `13` should be skipped, so go from `12` to `14`.
+- Keep in mind that the returned values should be ordered from low to high. `1A, 1B, 2A, ...`
+
+## 2. Assign seats to passengers
+
+- Make sure your seat numbers do not have any space in them.
diff --git a/exercises/concept/plane-tickets/.docs/instructions.md b/exercises/concept/plane-tickets/.docs/instructions.md
new file mode 100644
index 00000000..290aad2e
--- /dev/null
+++ b/exercises/concept/plane-tickets/.docs/instructions.md
@@ -0,0 +1,73 @@
+# Instructions
+
+Conda airlines is the programming-world's biggest airline, with over 10.000 flights a day!
+
+They are currently assigning all seats to passengers by hand, this will need to automated.
+
+They have asked _you_ to create software to automate the assigning of seats to passengers. They require your software to be memory efficient and performant.
+
+Conda's airplanes have up to _4 seats_ in each row, and each airplane has many rows.
+
+While the rows are defined using numbers, seats in each row are defined using letters from the alphabet, with `seat A` being the first _seat_ in the row.
+
+You can use this table as a guide:
+
+| x             | 1         | 2     |
+| :----:        | :----:    | :----:|
+| Row           | 5         | 21    |
+| Seat letter   | A         | D     |
+| Result        | 5A        | 21D   |
+
+## 1. Generate an amount of seats
+
+Implement the `generate_seats()` function that returns an _iterable_ of seats given the following variable:
+
+`amount`: The amount of seats to be generated.
+
+Many airlines do not have _row_ number 13 on their flights, due to superstition amongst passengers.
+Conda Airlines also follows this convention, so make sure you _don't_ generate seats for _row_ number 13.
+
+_Note: The returned seats should be ordered, like: 1A 1B 1C._
+
+```python
+>>> seats = generate_seats(10)
+>>> next(seats)
+"1A"
+>>> next(seats)
+"1B"
+```
+
+## 2. Assign seats to passengers
+
+Implement the `assign_seats()` function that returns a _dictionary_ of `passenger` as _key_, and `seat_number` as _value_. Given is the following _list_:
+
+`passengers`: A list containing passenger names.
+
+```python
+>>> passengers = ['Jerimiah', 'Eric', 'Bethaney', 'Byte', 'SqueekyBoots', 'Bob']
+
+>>> assign_seats(passengers)
+{'Jerimiah': '1A', 'Eric': '1B', 'Bethaney': '1C', 'Byte': '1D', 'SqueekyBoots': '2A', 'Bob': '2B'}
+```
+
+## 3. Ticket codes
+
+Each ticket has a _12_ character long string code for identification.
+
+This code begins with the `assigned_seat` followed by the `flight_id`. The rest of the code is appended by `0s`.
+
+Implement a `generator` that yields a `ticket_number` given the following arguments:
+
+`seat_numbers`: A _list_ of *seat_numbers*.
+`flight_id`: A string containing the flight identification.
+
+```python
+>>> seat_numbers = ['1A', '17D']
+>>> flight_id = 'CO1234'
+>>> ticket_ids = generate_codes(seat_numbers, flight_id)
+
+>>> next(ticket_ids)
+'1ACO12340000'
+>>> next(ticket_ids)
+'17DCO1234000'
+```
diff --git a/exercises/concept/plane-tickets/.docs/introduction.md b/exercises/concept/plane-tickets/.docs/introduction.md
new file mode 100644
index 00000000..1b557b44
--- /dev/null
+++ b/exercises/concept/plane-tickets/.docs/introduction.md
@@ -0,0 +1,5 @@
+# Introduction
+
+A generator in Python is a _callable function_ that returns a [lazy iterator](https://en.wikipedia.org/wiki/Lazy_evaluation).
+
+_Lazy iterators_ are similar to iterables such as `lists`, and other types of `iterators` in Python -- but with one key difference: `generators` do not store their `values` in memory, but _generate_ their values as needed or when called.
diff --git a/exercises/concept/plane-tickets/.meta/config.json b/exercises/concept/plane-tickets/.meta/config.json
new file mode 100644
index 00000000..96559aa2
--- /dev/null
+++ b/exercises/concept/plane-tickets/.meta/config.json
@@ -0,0 +1,11 @@
+{
+  "blurb": "Learn about generators by assigning seats to passengers.",
+  "authors": ["J08K"],
+  "icon": "poker",
+  "contributors": ["BethanyG"],
+  "files": {
+    "solution": ["plane_tickets.py"],
+    "test": ["plane_tickets_test.py"],
+    "exemplar": [".meta/exemplar.py"]
+  }
+}
diff --git a/exercises/concept/plane-tickets/.meta/design.md b/exercises/concept/plane-tickets/.meta/design.md
new file mode 100644
index 00000000..58542aab
--- /dev/null
+++ b/exercises/concept/plane-tickets/.meta/design.md
@@ -0,0 +1,89 @@
+This issue describes how to implement the `generators` concept exercise for the Python track.
+
+## Goal
+
+The goal of this exercise is to teach the syntax and use of `generators` in Python. 
+
+## Learning objectives
+
+-  Understand what generators are and how/when to use them
+-  Understand how generators relate to `loops` and `iterators`
+-  Understand how to use the `yield` keyword
+-  Understand the `__next__()` method
+-  Create a generator
+
+
+## Out of scope
+
+- Memory and performance characteristics and optimizations
+- `throw(type, value=None, traceback=None)`
+- `close()`
+- `generator expressions`
+- `yield from`
+- `generators` used as coroutines
+
+
+## Concepts covered
+
+-  `generators`
+-  `yield`
+-   `__next__()`
+-  `iterators`
+
+
+## Prerequisites
+
+-  `conditionals`
+-  `dicts`
+-  `functions`
+-  `higher-order-functions`
+-  `lists`
+-  `loops`
+-  `iteration`
+-  `iterators`
+-  `sequences`
+
+
+
+## Resources to refer to
+
+-  [Generators (Python official docs)](https://docs.python.org/3/howto/functional.html#generators)
+- [generator (Python official docs glossary)](https://docs.python.org/3/glossary.html#term-generator)
+- [The yield statement (Python official docs)](https://docs.python.org/3/reference/simple_stmts.html#the-yield-statement)
+- [Yield expressions (Python official docs)](https://docs.python.org/3/reference/expressions.html#yieldexpr)
+- [Iterators(Python official docs)](https://docs.python.org/3/howto/functional.html?#iterators)
+- [Generator-iterator methods (Python official docs)](https://docs.python.org/3/reference/expressions.html#generator-iterator-methods)
+- [How to Use Generators and yield in Python (Real Python)](https://realpython.com/introduction-to-python-generators/)
+
+### Hints
+
+-  Referring to one or more of the resources linked above, or analogous resources from a trusted source.
+- `Generators` section of the Python Docs Functional How to tutorial:  [Generators](https://docs.python.org/3/howto/functional.html#generators)
+
+
+## Concept Description 
+
+(_a variant of this can be used for the `v3/languages/python/concepts/<concept>/about.md` doc and this exercises  `introduction.md` doc._)
+
+_**Concept Description Needs to Be Filled In Here/Written**_
+
+_Some "extras" that we might want to include as notes in the concept description, or as links in `links.json`:_
+
+-  Additional `Generator-iterator methods`, such as `generator.send()` and `generator.throw()`
+- `generator expressions`
+-  Asynchronous generator functions
+- `generators` used as coroutines
+
+## Implementing
+
+The general Python track concept exercise implantation guide can be found [here](https://github.com/exercism/v3/blob/master/languages/python/reference/implementing-a-concept-exercise.md).
+
+Tests should be written using `unittest.TestCase` and the test file named `generators_test.py`.
+
+Code in the `.meta/example.py` file should **only use syntax & concepts introduced in this exercise or one of its prerequisites.** Please do not use comprehensions, generator expressions, or other syntax not previously covered.  Please also follow [PEP8](https://www.python.org/dev/peps/pep-0008/) guidelines.
+
+## Help
+
+If you have any questions while implementing the exercise, please post the questions as comments in this issue, or contact one of the maintainers on our Slack channel.
+
+
diff --git a/exercises/concept/plane-tickets/.meta/exemplar.py b/exercises/concept/plane-tickets/.meta/exemplar.py
new file mode 100644
index 00000000..fa8927d7
--- /dev/null
+++ b/exercises/concept/plane-tickets/.meta/exemplar.py
@@ -0,0 +1,60 @@
+"""Plane Tickets Exercise"""
+
+import math
+
+SEATS_IN_ROW = ['A', 'B', 'C', 'D']
+
+def generate_seats(amount):
+
+    """Generate a series of seat numbers for airline boarding.
+
+    :param amount: Amount of seats to be generated. (int)
+    :return: Generator that generates seat numbers. (generator)
+
+    There should be no row 13
+
+    Seat numbers are generated with each row having 4 seats.
+    These should be sorted from low to high.
+
+    Example: 3C, 3D, 4A, 4B
+
+    """
+
+    amount = amount + 4 if amount >= 13 else amount
+
+    for seat in range(amount):
+        row_number = math.ceil((seat+1) / 4)
+        if row_number != 13:
+            seat_letter = SEATS_IN_ROW[seat % 4]
+            yield f'{str(row_number)}{seat_letter}'
+
+def assign_seats(passengers):
+
+    """Assign seats to passenger.
+
+    :param passengers: A list of strings containing names of passengers. (list[str])
+    :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values.
+
+    Example output: {"Foo": "1A", "Bar": "1B"}
+
+    """
+
+    amount = len(passengers)
+    output = {}
+    for passenger, seat_number in zip(passengers, generate_seats(amount)):
+        output[passenger] = seat_number
+    return output
+
+def generate_codes(seat_numbers, flight_id):
+
+    """Generate codes for a ticket.
+
+    :param seat_numbers: A list of seat numbers. (list[str])
+    :param flight_id: A string containing the flight identification. (str)
+    :return: Generator that generates 12 character long strings. (generator[str])
+
+    """
+
+    for seat in seat_numbers:
+        base_string = f'{seat}{flight_id}'
+        yield base_string + '0' * (12 - len(base_string))
diff --git a/exercises/concept/plane-tickets/plane_tickets.py b/exercises/concept/plane-tickets/plane_tickets.py
new file mode 100644
index 00000000..44d68553
--- /dev/null
+++ b/exercises/concept/plane-tickets/plane_tickets.py
@@ -0,0 +1,42 @@
+"""Plane Tickets Exercise"""
+
+def generate_seats(amount):
+
+    """ Generate a series of seat numbers for airline boarding.
+
+    :param amount: Amount of seats to be generated. (int)
+    :return: Generator that yields seat numbers.
+
+    There should be no row 13
+
+    Seat numbers are generated with each row having 4 seats.
+    These should be sorted from low to high.
+
+    Example: 3C, 3D, 4A, 4B
+
+    """
+
+    pass
+
+def assign_seats(passengers):
+
+    """ Assign seats to passengers.
+
+    :param passengers: A list of strings containing names of passengers. (list[str])
+    :return: A dictionary type object containing the names of the passengers as keys and seat numbers as values.
+
+    Example output: {"Foo": "1A", "Bar": "1B"}
+
+    """
+
+    pass
+
+def generate_codes(seat_numbers, flight_id):
+
+    """Generate codes for a ticket.
+
+    :param seat_numbers: A list of seat numbers. (list[str])
+    :param flight_id: A string containing the flight identification. (str)
+    :return: Generator that generates 12 character long strings. (generator[str])
+
+    """


Test patch:
diff --git a/exercises/concept/plane-tickets/plane_tickets_test.py b/exercises/concept/plane-tickets/plane_tickets_test.py
new file mode 100644
index 00000000..f4255c43
--- /dev/null
+++ b/exercises/concept/plane-tickets/plane_tickets_test.py
@@ -0,0 +1,72 @@
+from typing import Generator
+import unittest
+import pytest
+
+from plane_tickets import (
+    generate_seats,
+    assign_seats,
+    generate_codes
+)
+
+class PlaneTicketsTest(unittest.TestCase):
+
+
+    @pytest.mark.task(taskno=1)
+    def test_task1_is_generator(self):  # * Tests if [Task 1] actually returns a generator.
+        input_var = 5
+        output_type = Generator
+        error_message = f"Expected: {str(output_type)} type, but got a different type."
+        self.assertIsInstance(generate_seats(input_var), output_type, msg=error_message)
+
+    @pytest.mark.task(taskno=1)
+    def test_task1_output(self):
+        input_vars = [1, 2, 3, 4, 5]
+        output = [["1A"], ["1A", "1B"], ["1A", "1B", "1C"], ["1A", "1B", "1C", "1D"], ["1A", "1B", "1C", "1D", "2A"]]
+        for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1):
+            error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)."
+            with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output):
+                self.assertEqual(list(generate_seats(input_var)), output, msg=error_message)
+
+    @pytest.mark.task(taskno=1)
+    def test_task1_skips_row_13(self):
+        input_vars = [14 * 4]
+        output = [["1A", "1B", "1C", "1D", "2A", "2B", "2C", "2D",
+                   "3A", "3B", "3C", "3D", "4A", "4B", "4C", "4D",
+                   "5A", "5B", "5C", "5D", "6A", "6B", "6C", "6D",
+                   "7A", "7B", "7C", "7D", "8A", "8B", "8C", "8D",
+                   "9A", "9B", "9C", "9D", "10A", "10B", "10C", "10D",
+                   "11A", "11B", "11C", "11D", "12A", "12B", "12C", "12D",
+                   "14A", "14B", "14C", "14D", "15A", "15B", "15C", "15D"]]
+        for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1):
+            error_message = f"Expected: {output}, but something went wrong while generating {input_var} seat(s)."
+            with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output):
+                self.assertEqual(list(generate_seats(input_var)), output, msg=error_message)
+
+    @pytest.mark.task(taskno=2)
+    def test_task2(self):
+        input_vars = [["Passenger1", "Passenger2", "Passenger3", "Passenger4", "Passenger5"], 
+                      ["TicketNo=5644", "TicketNo=2273", "TicketNo=493", "TicketNo=5411", "TicketNo=824"]]
+        output = [{"Passenger1": "1A", "Passenger2": "1B", "Passenger3": "1C", "Passenger4": "1D", "Passenger5": "2A"}, 
+                  {"TicketNo=5644": "1A", "TicketNo=2273": "1B", "TicketNo=493": "1C", "TicketNo=5411": "1D", "TicketNo=824": "2A"}]
+        for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1):
+            error_message = f"Expected: {output}, but something went wrong while assigning seats to passengers {input_var}."
+            with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output):
+                self.assertEqual(assign_seats(input_var), output, msg=error_message)
+
+    @pytest.mark.task(taskno=3)
+    def test_task3_is_generator(self):
+        input_var = ("11B", "HA80085")
+        output_type = Generator
+        error_message = f"Expected: {str(output_type)} type, but got a different type."
+        self.assertIsInstance(generate_codes(input_var[0], input_var[1]), output_type, msg=error_message)
+
+    @pytest.mark.task(taskno=3)
+    def test_task3(self):
+        input_vars = [(["12A", "38B", "69C", "102B"],"KL1022"),
+                      (["22C", "88B", "33A", "44B"], "DL1002")]
+        output = [['12AKL1022000', '38BKL1022000', '69CKL1022000', '102BKL102200'],
+                  ['22CDL1002000', '88BDL1002000', '33ADL1002000', '44BDL1002000']]
+        for variant, (input_var, output) in enumerate(zip(input_vars, output), start=1):
+            error_message = f"Expected: {input_var}, but something went wrong while generating ticket numbers."
+            with self.subTest(f"variation #{variant}", input_data=input_var, output_data=output):
+                self.assertEqual(list(generate_codes(input_var[0], input_var[1])), output, msg=error_message)
\ No newline at end of file