Skip to content

Commit afe8919

Browse files
authored
Merge branch 'laike9m:main' into main
2 parents b116772 + e556879 commit afe8919

File tree

127 files changed

+3764
-678
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

127 files changed

+3764
-678
lines changed

.github/workflows/test.yaml

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
name: Unit Test
2+
3+
on:
4+
push:
5+
branches: [ main, test ]
6+
pull_request:
7+
8+
jobs:
9+
test:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
- name: Set up PDM
14+
uses: pdm-project/setup-pdm@v3
15+
with:
16+
python-version: 3.12
17+
cache: true
18+
- name: Install dependencies
19+
run: pdm install
20+
- name: Preheat Pyright
21+
# If we don't do this, each pytest-xdist process will try to install the
22+
# dependencies of Pyright (Node.js packages, pyi). Each installation assumes
23+
# it's a clean install, and will error if files already exist.
24+
run: pdm run pyright --pythonversion 3.12 challenges/basic-any/solution.py
25+
continue-on-error: true
26+
- name: Run tests
27+
run: pdm run test
28+
- name: Send failure email
29+
if: failure()
30+
uses: dawidd6/action-send-mail@v3
31+
with:
32+
subject: Test Failure in ${{ github.repository }}
33+
body: The test for ${{ github.repository }} failed.
34+
to: ${{ secrets.MAIL_USERNAME }}
35+
from: GitHub
36+
server_address: smtp.gmail.com
37+
username: ${{ secrets.MAIL_USERNAME }}
38+
password: ${{ secrets.MAIL_PASSWORD }}

.pre-commit-config.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
repos:
2+
- repo: https://github.com/rtts/djhtml
3+
rev: '3.0.6'
4+
hooks:
5+
- id: djhtml
6+
entry: djhtml --tabwidth 2 templates

README.md

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,53 @@
11
# Python Type Challenges
22

3-
Learn Python 🐍 typing (type hints) by completing online challenges.
3+
Master Python typing (type hints) with interactive online exercises!
44

5-
🌟🌟 Click **[HERE](https://python-type-challenges.zeabur.app)** to start 🌟🌟
5+
Click 👉👉 **[HERE](https://python-type-challenges.zeabur.app)** to start
66

77
![](docs/images/usage.gif)
88

99
Happy typing!
1010

11+
## How to Run Locally
12+
13+
You can also run the challenge locally. To do that, clone the project and install necessary dependencies, using either PDM or `requirements.txt`.
14+
15+
[PDM](https://pdm-project.org/) is recommended. After [installing PDM](https://pdm.fming.dev/latest/#installation), you can install needed dependencies with the following steps:
16+
17+
```bash
18+
pdm install
19+
pdm dev # This will run a local Flask server
20+
```
21+
22+
Alternatively, you can install dependencies with `requirements.txt`:
23+
24+
```bash
25+
pip install -r requirements.txt
26+
flask run
27+
```
28+
1129
## How to Contribute
1230

13-
You're more than welcome to contribute new challenges!
31+
- **Add new challenges**
1432

15-
All challenges live under the [`challenges/`](https://github.com/laike9m/Python-Type-Challenges/tree/main/challenges) directory, and it's pretty easy to add a new one: **you only need to create a new folder, add a `question.py`, and that's it**. See [here](docs/Contribute.md) for a detailed guidance.
33+
Adding a new challenge is pretty simple: **you only need to create a new folder, add a `question.py` and a `solution.py`, and that's it**. See [here](docs/Contribute.md) for a detailed guidance.
34+
35+
- **New features & bug fixes**
36+
37+
If you want to fix a bug or add a new feature, follow the [guidance](docs/Development.md).
1638

1739
## Got Questions?
1840

1941
For general questions, you can post them in [Discussions](https://github.com/laike9m/Python-Type-Challenges/discussions).
2042

2143
If you met issues or want to suggest a new feature/improvement, feel free to [open a new issue](https://github.com/laike9m/Python-Type-Challenges/issues/new).
2244

45+
## Sponsor
46+
47+
[![Deployed on Zeabur](https://zeabur.com/deployed-on-zeabur-dark.svg)](https://zeabur.com?referralCode=laike9m&utm_source=laike9m&utm_campaign=oss)
48+
2349
## Credits
2450

2551
This project is inspired [Type Exercise in Rust](https://github.com/skyzh/type-exercise-in-rust/) by [@skyzh](https://github.com/skyzh), and [type-challenges](https://github.com/type-challenges/type-challenges/) by [@antfu](https://github.com/antfu).
52+
53+
Social graph [images](https://unsplash.com/photos/person-sitting-front-of-laptop-mfB1B1s4sMc) come from [Christin Hume](https://unsplash.com/@christinhumephoto).

app.py

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,41 @@
1-
from flask import Flask, redirect
2-
from views import views
1+
import threading
2+
from flask import Flask, redirect, request, send_from_directory
3+
from typing import cast
34

4-
app = Flask(__name__)
5+
from views import views, challenge
6+
from views.sitemap import sitemapper
7+
8+
app = Flask(
9+
__name__,
10+
template_folder="templates",
11+
static_folder="static",
12+
static_url_path="/static",
13+
)
514
app.register_blueprint(views.app_views)
15+
sitemapper.init_app(app)
616

717

818
@app.errorhandler(404)
919
def page_not_found(e):
1020
return redirect("/")
21+
22+
23+
@app.route("/robots.txt")
24+
def robots_txt():
25+
return send_from_directory(cast(str, app.static_folder), request.path[1:])
26+
27+
28+
@app.route("/sitemap.xml")
29+
def r_sitemap():
30+
return sitemapper.generate()
31+
32+
33+
# Temporary solution for
34+
# https://github.com/laike9m/Python-Type-Challenges/issues/49
35+
threading.Thread(
36+
target=challenge.challenge_manager.run_challenge,
37+
kwargs={
38+
"key": challenge.ChallengeKey(challenge.Level("basic"), "any"),
39+
"user_code": "",
40+
},
41+
).start()
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
"""
2+
TODO:
3+
4+
Define a callable type that accepts a string parameter called `name` and returns None.
5+
6+
HINT: Use Protocol
7+
"""
8+
9+
10+
class SingleStringInput:
11+
...
12+
13+
14+
## End of your code ##
15+
def accept_single_string_input(func: SingleStringInput) -> None:
16+
func(name="name")
17+
18+
19+
def string_name(name: str) -> None:
20+
...
21+
22+
23+
def string_value(value: str) -> None:
24+
...
25+
26+
27+
def return_string(name: str) -> str:
28+
return name
29+
30+
31+
accept_single_string_input(string_name)
32+
accept_single_string_input(string_value) # expect-type-error
33+
accept_single_string_input(return_string) # expect-type-error
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
"""
2+
TODO:
3+
4+
Define a callable type that accepts a string parameter called `name` and returns None.
5+
6+
HINT: Use Protocol
7+
"""
8+
from typing import Protocol
9+
10+
11+
class SingleStringInput(Protocol):
12+
def __call__(self, name: str) -> None:
13+
...
14+
15+
16+
## End of your code ##
17+
def accept_single_string_input(func: SingleStringInput) -> None:
18+
func(name="name")
19+
20+
21+
def string_name(name: str) -> None:
22+
...
23+
24+
25+
def string_value(value: str) -> None:
26+
...
27+
28+
29+
def return_string(name: str) -> str:
30+
return name
31+
32+
33+
accept_single_string_input(string_name)
34+
accept_single_string_input(string_value) # expect-type-error
35+
accept_single_string_input(return_string) # expect-type-error

challenges/advanced-callable/question.py

Lines changed: 0 additions & 35 deletions
This file was deleted.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
"""
2+
TODO:
3+
4+
Define a decorator that wraps a function and returns a function with the same signature.
5+
The decorator takes an argument `message` of type string
6+
"""
7+
8+
9+
def decorator(message):
10+
...
11+
12+
13+
## End of your code ##
14+
@decorator(message="x")
15+
def foo(a: int, *, b: str) -> None:
16+
...
17+
18+
19+
@decorator # expect-type-error
20+
def bar(a: int, *, b: str) -> None:
21+
...
22+
23+
24+
foo(1, b="2")
25+
foo(1, "2") # expect-type-error
26+
foo(a=1, e="2") # expect-type-error
27+
decorator(1) # expect-type-error
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
"""
2+
TODO:
3+
4+
Define a decorator that wraps a function and returns a function with the same signature.
5+
The decorator takes an argument `message` of type string
6+
"""
7+
from typing import TypeVar
8+
from collections.abc import Callable
9+
10+
# Python < 3.12
11+
#
12+
# T = TypeVar("T", bound=Callable)
13+
#
14+
# def decorator(func: T) -> T:
15+
# return func
16+
17+
18+
# Python >= 3.12
19+
def decorator[T: Callable](message: str) -> Callable[[T], T]:
20+
...
21+
22+
23+
## End of your code ##
24+
@decorator(message="x")
25+
def foo(a: int, *, b: str) -> None:
26+
...
27+
28+
29+
@decorator # expect-type-error
30+
def bar(a: int, *, b: str) -> None:
31+
...
32+
33+
34+
foo(1, b="2")
35+
foo(1, "2") # expect-type-error
36+
foo(a=1, e="2") # expect-type-error
37+
decorator(1) # expect-type-error
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
class MyClass:
2+
def __init__(self, x: int) -> None:
3+
self.x = x
4+
5+
# TODO: Fix the type hints of `copy` to make it type check
6+
def copy(self) -> MyClass:
7+
copied_object = MyClass(x=self.x)
8+
return copied_object
9+
10+
11+
## End of your code ##
12+
13+
from typing import assert_type
14+
15+
inst = MyClass(x=1)
16+
assert_type(inst.copy(), MyClass)

0 commit comments

Comments
 (0)