diff --git a/python-314/README.md b/python-314/README.md new file mode 100644 index 0000000000..aac83ba86d --- /dev/null +++ b/python-314/README.md @@ -0,0 +1,3 @@ +# Python 3.14: Cool New Features for You to Try + +The materials contained in this folder are designed to complement the Real Python tutorial [Python 3.14: Cool New Features for You to Try](https://realpython.com/python314-new-features/). diff --git a/python-314/fib.py b/python-314/fib.py new file mode 100644 index 0000000000..d99979c8a3 --- /dev/null +++ b/python-314/fib.py @@ -0,0 +1,3 @@ +def fib(n): + print(f"Calculating fib({n})...") + return n if n < 2 else fib(n - 2) + fib(n - 1) diff --git a/python-314/jit_status.py b/python-314/jit_status.py new file mode 100644 index 0000000000..7e06718a38 --- /dev/null +++ b/python-314/jit_status.py @@ -0,0 +1,8 @@ +try: + from sys import _jit +except ImportError: + print("Module sys._jit unavailable") +else: + print("Python compiled with JIT support:", _jit.is_available()) + print("JIT enabled for this process:", _jit.is_enabled()) + print("JIT currently running:", _jit.is_active()) diff --git a/python-314/linked_list.py b/python-314/linked_list.py new file mode 100644 index 0000000000..72b0961667 --- /dev/null +++ b/python-314/linked_list.py @@ -0,0 +1,15 @@ +# Uncomment for Python 3.14 +# +# from dataclasses import dataclass +# from typing import Any, Optional +# +# +# @dataclass +# class LinkedList: +# head: Node +# +# +# @dataclass +# class Node: +# value: Any +# next: Optional[Node] = None diff --git a/python-314/repl/cli_script.py b/python-314/repl/cli_script.py new file mode 100644 index 0000000000..306f6407c9 --- /dev/null +++ b/python-314/repl/cli_script.py @@ -0,0 +1,5 @@ +import argparse + +parser = argparse.ArgumentParser("Command-Line Interface") +parser.add_argument("path", help="Path to a file") +parser.parse_args() diff --git a/python-314/repl/pythonrc.py b/python-314/repl/pythonrc.py new file mode 100644 index 0000000000..5602901682 --- /dev/null +++ b/python-314/repl/pythonrc.py @@ -0,0 +1,38 @@ +# Set a custom color theme in Python 3.14 +try: + from _colorize import ANSIColors, default_theme, set_theme +except ImportError: + pass +else: + custom_theme = default_theme.copy_with( + syntax=default_theme.syntax.copy_with( + prompt=ANSIColors.GREY, + builtin="\x1b[38;2;189;147;249m", + comment="\x1b[38;2;98;114;164m", + definition="\x1b[38;2;139;233;253m", + keyword="\x1b[38;2;255;121;198m", + keyword_constant="\x1b[38;2;255;121;198m", + soft_keyword="\x1b[38;2;255;121;198m", + number="\x1b[38;2;189;147;249m", + op="\x1b[38;2;249;152;204m", + string="\x1b[38;2;241;250;140m", + ), + traceback=default_theme.traceback.copy_with( + error_highlight=ANSIColors.BOLD_YELLOW, + error_range=ANSIColors.YELLOW, + filename=ANSIColors.BACKGROUND_RED, + frame=ANSIColors.BACKGROUND_RED, + line_no=ANSIColors.BACKGROUND_RED, + message=ANSIColors.RED, + type=ANSIColors.BOLD_RED, + ), + ) + set_theme(custom_theme) + +# Set a custom shell prompt +import platform +import sys + +version = platform.python_version() +sys.ps1 = f"{version} \N{SNAKE} " +sys.ps2 = "." * len(sys.ps1) diff --git a/python-314/repl/test_dummy.py b/python-314/repl/test_dummy.py new file mode 100644 index 0000000000..36245335ad --- /dev/null +++ b/python-314/repl/test_dummy.py @@ -0,0 +1,6 @@ +import unittest + + +class TestDummy(unittest.TestCase): + def test_dummy(self): + self.assertEqual(2 + 2, 22) diff --git a/python-314/repl/todo.json b/python-314/repl/todo.json new file mode 100644 index 0000000000..41f8d0a4e7 --- /dev/null +++ b/python-314/repl/todo.json @@ -0,0 +1 @@ +{"Shopping List": [{"title": "eggs", "details": null, "quantity": 12, "completed": true}, {"title": "bacon", "details": "smoke, 500g pack", "quantity": 1, "completed": false}, {"title": "ghee", "details": null, "quantity": 1, "completed": false}]} \ No newline at end of file diff --git a/python-314/subinterpreters.py b/python-314/subinterpreters.py new file mode 100644 index 0000000000..8ecbc29806 --- /dev/null +++ b/python-314/subinterpreters.py @@ -0,0 +1,28 @@ +from concurrent.futures import ( + InterpreterPoolExecutor, + ProcessPoolExecutor, + ThreadPoolExecutor, +) +from time import perf_counter + +MAX_VALUE = 35 +NUM_VALUES = 4 +NUM_WORKERS = 4 + + +def fib(n): + return n if n < 2 else fib(n - 2) + fib(n - 1) + + +def compute(Pool): + t1 = perf_counter() + with Pool(max_workers=NUM_WORKERS) as pool: + list(pool.map(fib, [MAX_VALUE] * NUM_VALUES)) + t2 = perf_counter() + print(f"{Pool.__name__}: {(t2 - t1):.2f}s") + + +if __name__ == "__main__": + compute(InterpreterPoolExecutor) + compute(ProcessPoolExecutor) + compute(ThreadPoolExecutor) diff --git a/python-314/tstrings.py b/python-314/tstrings.py new file mode 100644 index 0000000000..19785ceae0 --- /dev/null +++ b/python-314/tstrings.py @@ -0,0 +1,64 @@ +from dataclasses import dataclass +from string.templatelib import Interpolation, Template, convert +from typing import Any + + +@dataclass(frozen=True) +class SQLQuery: + statement: str + params: list[Any] + + def __init__(self, template: Template) -> None: + items, params = [], [] + for item in template: + match item: + case str(): + items.append(item) + case Interpolation(value, _, conversion, format_spec): + converted = convert(value, conversion) + if format_spec: + converted = format(converted, format_spec) + params.append(converted) + items.append("?") + object.__setattr__(self, "statement", "".join(items)) + object.__setattr__(self, "params", params) + + +def find_users_query_v1(name: str) -> str: + """Return a SQL query to find users by name.""" + return f"SELECT * FROM users WHERE name = '{name}'" + + +# Uncomment for Python 3.14: +# +# def find_users_query_v2(name: str) -> Template: +# """Return a SQL query to find users by name.""" +# return t"SELECT * FROM users WHERE name = '{name}'" +# +# +# def find_users(name: str) -> SQLQuery: +# """Return a SQL query to find users by name.""" +# return SQLQuery(t"SELECT * FROM users WHERE name = {name}") + + +def render(template: Template) -> str: + return "".join( + f"{text}{value}" + for text, value in zip(template.strings, template.values) + ) + + +if __name__ == "__main__": + # Insecure f-strings + print(find_users_query_v1("' OR '1'='1")) + + # Uncomment for Python 3.14: + # + # # More secure t-strings + # print(find_users_query_v2("' OR '1'='1")) + # + # # Insecure way of rendering t-strings into plain strings + # print(render(find_users_query_v2("' OR '1'='1"))) + # + # # Rendering t-strings into an alternative representation + # print(find_users("' OR '1'='1")) diff --git a/python-314/web_server.py b/python-314/web_server.py new file mode 100644 index 0000000000..506a988bb4 --- /dev/null +++ b/python-314/web_server.py @@ -0,0 +1,25 @@ +import os +import sys +from http.server import BaseHTTPRequestHandler, HTTPServer + +HOSTNAME = "localhost" +PORT = 8000 + + +class Handler(BaseHTTPRequestHandler): + def do_GET(self): + user_agent = self.headers.get("User-Agent") + self.send_response(200) + self.end_headers() + self.wfile.write(f"Hello, {user_agent}".encode()) + + +def main(): + print(f"Starting the server on: http://{HOSTNAME}:{PORT}") + print("Run this command as a superuser to attach the debugger:") + print(f"{sys.executable} -m pdb -p {os.getpid()}") + HTTPServer((HOSTNAME, PORT), Handler).serve_forever() + + +if __name__ == "__main__": + main()