diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..e960a6a
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,35 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md.
+ "name": ".NET Core Launch (web)",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/apps/net/Ch6_Web/bin/Debug/net8.0/Ch6_Web.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/apps/net/Ch6_Web",
+ "stopAtEntry": false,
+ // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser
+ "serverReadyAction": {
+ "action": "openExternally",
+ "pattern": "\\bNow listening on:\\s+(https?://\\S+)"
+ },
+ "env": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ },
+ "sourceFileMap": {
+ "/Views": "${workspaceFolder}/Views"
+ }
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..a151094
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "build",
+ "${workspaceFolder}/apps/net/net.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/apps/net/net.sln",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary;ForceNoAlign"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/apps/net/net.sln"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/README.md b/README.md
index 68a6915..ba7cd59 100644
--- a/README.md
+++ b/README.md
@@ -1,3 +1,39 @@
# Python for the .NET Developer course
-
+[](https://training.talkpython.fm/courses/python-for-csharp-dotnet-developers)
+
+Are you a .NET developer who is interested in learning Python? **This is \*the\* course for you**. While there are many getting started courses for Python, this course is specifically geared towards helping C# and .NET developers quickly get up to speed with Python. It covers a wide spectrum of the developer landscape from the language, databases and ORMs, web frameworks, data science and computational notebooks, and much much more.
+
+## What's this course about and how is it different?
+
+This is the definitive course to learn the entire Python ecosystem for .NET developers. We spend over 9 hours comparing Python and C#, the Python runtime and the .NET CLR, NuGet to PyPI, and much more. **You will see working C# examples _first_**. Then we will build the Python equivalent of that application live, together, during the course.
+
+In this course, you will:
+
+- See how Python and .NET are similar and how they are different
+- Get setup and ready to write and run Python 3 on your computer
+- Dive deep into the Python language while comparing each element to it's C# equivalent
+- Work with classes, inheritance, method overriding and more in Python
+- Leverage the over 200,000 public packages (libraries) using pip and related tooling
+- Explore Python's two memory management models and how they differ from .NET's GC
+- Choose a Python web framework comparable to ASP.NET MVC
+- Build data driven web applications using Flask and the ORM SQLAlchemy
+- Test your Python libraries and application with pytest
+- Mock out your dependencies for true unit testing with pytest_mock
+- Leverage async and await (in Python!) for massively parallel processing
+- Explore and visualize data with computational notebooks using JupyterLab
+- Deploy a Flask (Python) web app on a Linux cloud VM using nginx and uWSGI
+- Secure your web app on Linux with Let's Encrypt to add free SSL support
+- And lots more
+- View the full [course outline](https://training.talkpython.fm/courses/python-for-csharp-dotnet-developers#course_outline).
+
+## Who is this course for?
+
+With this course, the name really does say it all. If you know C# and .NET and would like to leverage that expertise in the Python space, this course is for you.
+
+The course *does not assume Python knowledge*. But it does assume you know basic C# code and that you wish the leverage that knowledge to learn Python faster and deeper.
+
+## Join the course
+
+Take the course at [training.talkpython.fm/courses/python-for-csharp-dotnet-developers](https://training.talkpython.fm/courses/python-for-csharp-dotnet-developers).
+
diff --git a/apps/net/Ch3_Lang/Ch3_Lang.csproj b/apps/net/Ch3_Lang/Ch3_Lang.csproj
index 0e3cc8f..dbcc196 100644
--- a/apps/net/Ch3_Lang/Ch3_Lang.csproj
+++ b/apps/net/Ch3_Lang/Ch3_Lang.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net9.0
diff --git a/apps/net/Ch4_OOP/Ch4_OOP.csproj b/apps/net/Ch4_OOP/Ch4_OOP.csproj
index 5670ae8..48136ab 100644
--- a/apps/net/Ch4_OOP/Ch4_OOP.csproj
+++ b/apps/net/Ch4_OOP/Ch4_OOP.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net9.0
diff --git a/apps/net/Ch5_nuget/Ch5_nuget.csproj b/apps/net/Ch5_nuget/Ch5_nuget.csproj
index bfcc8a2..ecbb947 100644
--- a/apps/net/Ch5_nuget/Ch5_nuget.csproj
+++ b/apps/net/Ch5_nuget/Ch5_nuget.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net9.0
diff --git a/apps/net/Ch6_Web/Ch6_Web.csproj b/apps/net/Ch6_Web/Ch6_Web.csproj
index c255d1e..01afc9a 100644
--- a/apps/net/Ch6_Web/Ch6_Web.csproj
+++ b/apps/net/Ch6_Web/Ch6_Web.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
diff --git a/apps/net/Ch7_Db/Ch7_Db.csproj b/apps/net/Ch7_Db/Ch7_Db.csproj
index 4bd60f4..6addcb6 100644
--- a/apps/net/Ch7_Db/Ch7_Db.csproj
+++ b/apps/net/Ch7_Db/Ch7_Db.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
@@ -30,7 +30,7 @@
-
-
+
+
diff --git a/apps/net/Ch8_Testing/Ch8_Testing.csproj b/apps/net/Ch8_Testing/Ch8_Testing.csproj
index 2d55ab6..d729f73 100644
--- a/apps/net/Ch8_Testing/Ch8_Testing.csproj
+++ b/apps/net/Ch8_Testing/Ch8_Testing.csproj
@@ -1,7 +1,7 @@
- net8.0
+ net9.0
false
diff --git a/apps/net/Ch8_Testing_App/Ch8_Testing_App.csproj b/apps/net/Ch8_Testing_App/Ch8_Testing_App.csproj
index 7282f46..3a3df5a 100644
--- a/apps/net/Ch8_Testing_App/Ch8_Testing_App.csproj
+++ b/apps/net/Ch8_Testing_App/Ch8_Testing_App.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net9.0
diff --git a/apps/net/Ch9_Async/Ch9_Async.csproj b/apps/net/Ch9_Async/Ch9_Async.csproj
index b89bed3..8dea2ba 100644
--- a/apps/net/Ch9_Async/Ch9_Async.csproj
+++ b/apps/net/Ch9_Async/Ch9_Async.csproj
@@ -2,7 +2,7 @@
Exe
- net8.0
+ net9.0
enable
enable
diff --git a/apps/py/ch03_lang/L03_function_basics.py b/apps/py/ch03_lang/L03_function_basics.py
index eb0bd13..4df1f76 100644
--- a/apps/py/ch03_lang/L03_function_basics.py
+++ b/apps/py/ch03_lang/L03_function_basics.py
@@ -41,7 +41,7 @@ def get_guess():
print(f'{val} is not between 1 and 100.')
return None
return val
- except:
+ except: # noqa: E722
print(f'{val} is not an integer!')
return None
diff --git a/apps/py/ch03_lang/switchlang.py b/apps/py/ch03_lang/switchlang.py
index 2cb59e8..5eeafd4 100644
--- a/apps/py/ch03_lang/switchlang.py
+++ b/apps/py/ch03_lang/switchlang.py
@@ -93,9 +93,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
raise exc_val
if not self._func_stack:
- raise Exception(
- 'Value does not match any case and there ' 'is no default case: value {}'.format(self.value)
- )
+ raise Exception('Value does not match any case and there is no default case: value {}'.format(self.value))
for func in self._func_stack:
# noinspection PyCallingNonCallable
@@ -104,7 +102,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
@property
def result(self):
if self.__result == switch.__no_result:
- raise Exception('No result has been computed (did you access ' 'switch.result inside the with block?)')
+ raise Exception('No result has been computed (did you access switch.result inside the with block?)')
return self.__result
diff --git a/apps/py/ch04_oop/program.py b/apps/py/ch04_oop/program.py
index e3fe45c..445907e 100644
--- a/apps/py/ch04_oop/program.py
+++ b/apps/py/ch04_oop/program.py
@@ -1,4 +1,3 @@
-from pprint import pprint
from typing import List
from models.basic_car import BasicCar
diff --git a/apps/py/ch06_memory/mem_explorer.py b/apps/py/ch06_memory/mem_explorer.py
index 11e720f..d6a7d45 100644
--- a/apps/py/ch06_memory/mem_explorer.py
+++ b/apps/py/ch06_memory/mem_explorer.py
@@ -23,7 +23,7 @@ def ref_counting():
v2 = v1
print(f'Step 2: Ref count is {memutil.refs(v1_id)}')
- v2 = None
+ v2 = None # noqa: F841
print(f'Step 3: Ref count is {memutil.refs(v1_id)}')
v1 = None
diff --git a/apps/py/ch07_web/guitary/app.py b/apps/py/ch07_web/guitary/app.py
index 76aafd9..5364db3 100644
--- a/apps/py/ch07_web/guitary/app.py
+++ b/apps/py/ch07_web/guitary/app.py
@@ -4,8 +4,8 @@
folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, folder)
-import flask
-from guitary.services import catalog_service
+import flask # noqa: E402
+from guitary.services import catalog_service # noqa: E402
app = flask.Flask(__name__)
diff --git a/apps/py/ch07_web/speed_test.py b/apps/py/ch07_web/speed_test.py
index 9240bae..591ab00 100644
--- a/apps/py/ch07_web/speed_test.py
+++ b/apps/py/ch07_web/speed_test.py
@@ -58,7 +58,7 @@ def merge_environment_settings(self, url, proxies, stream, verify, cert):
for adapter in opened_adapters:
try:
adapter.close()
- except:
+ except: # noqa: E722
pass
diff --git a/apps/py/ch07_web/wsgi.py b/apps/py/ch07_web/wsgi.py
index 68578b6..e69de29 100644
--- a/apps/py/ch07_web/wsgi.py
+++ b/apps/py/ch07_web/wsgi.py
@@ -1 +0,0 @@
-from guitary.app import app
diff --git a/apps/py/ch08_db/guitary/app.py b/apps/py/ch08_db/guitary/app.py
index b4e0621..d0ffed6 100644
--- a/apps/py/ch08_db/guitary/app.py
+++ b/apps/py/ch08_db/guitary/app.py
@@ -4,10 +4,10 @@
folder = os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.path.insert(0, folder)
-import flask
-from guitary.services import catalog_service
-from guitary.data import session_factory
-from guitary.data import data_loader
+import flask # noqa: E402
+from guitary.services import catalog_service # noqa: E402
+from guitary.data import session_factory # noqa: E402
+from guitary.data import data_loader # noqa: E402
app = flask.Flask(__name__)
diff --git a/apps/py/ch08_db/guitary/data/context_session.py b/apps/py/ch08_db/guitary/data/context_session.py
index 0329413..7e99099 100644
--- a/apps/py/ch08_db/guitary/data/context_session.py
+++ b/apps/py/ch08_db/guitary/data/context_session.py
@@ -12,7 +12,7 @@ def __exit__(self, exc_type, exc_val, exc_tb):
if exc_val:
try:
self.session.rollback()
- except:
+ except: # noqa: E722
pass
self.session.close()
diff --git a/apps/py/ch08_db/guitary/data/session_factory.py b/apps/py/ch08_db/guitary/data/session_factory.py
index 8145f5b..c546b62 100644
--- a/apps/py/ch08_db/guitary/data/session_factory.py
+++ b/apps/py/ch08_db/guitary/data/session_factory.py
@@ -27,7 +27,6 @@ def create_tables():
raise Exception('You must call global_init() first.')
# noinspection PyUnresolvedReferences
- from guitary.data.guitar import Guitar
from guitary.data.sqlalchemybase import SqlAlchemyBase
SqlAlchemyBase.metadata.create_all(__engine)
diff --git a/apps/py/ch09_testing/test_lib.py b/apps/py/ch09_testing/test_lib.py
index 7893cec..fafad01 100644
--- a/apps/py/ch09_testing/test_lib.py
+++ b/apps/py/ch09_testing/test_lib.py
@@ -6,7 +6,6 @@
import pytest_mock
# noinspection PyUnresolvedReferences
-from test_fixtures import guitar_data
def test_something():
diff --git a/apps/py/ch11_notebooks/Lorenz.ipynb b/apps/py/ch11_notebooks/Lorenz.ipynb
index 9f7cf8d..909103e 100644
--- a/apps/py/ch11_notebooks/Lorenz.ipynb
+++ b/apps/py/ch11_notebooks/Lorenz.ipynb
@@ -21,7 +21,7 @@
"outputs": [],
"source": [
"%matplotlib inline\n",
- "from ipywidgets import interactive, fixed"
+ "from ipywidgets import interactive"
]
},
{
@@ -63,7 +63,8 @@
],
"source": [
"from lorenz import solve_lorenz\n",
- "w=interactive(solve_lorenz,sigma=(0.0,50.0),rho=(0.0,50.0))\n",
+ "\n",
+ "w = interactive(solve_lorenz, sigma=(0.0, 50.0), rho=(0.0, 50.0))\n",
"w"
]
},
@@ -169,7 +170,7 @@
"outputs": [
{
"data": {
- "image/png": "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\n",
+ "image/png": "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",
"text/plain": [
""
]
@@ -181,7 +182,7 @@
}
],
"source": [
- "plt.hist(xyz_avg[:,0])\n",
+ "plt.hist(xyz_avg[:, 0])\n",
"plt.title('Average $x(t)$');"
]
},
@@ -192,7 +193,7 @@
"outputs": [
{
"data": {
- "image/png": "iVBORw0KGgoAAAANSUhEUgAAAXAAAAEKCAYAAAALoA6YAAAABHNCSVQICAgIfAhkiAAAAAlwSFlzAAALEgAACxIB0t1+/AAAADh0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uMy4xLjEsIGh0dHA6Ly9tYXRwbG90bGliLm9yZy8QZhcZAAAShUlEQVR4nO3de7SldV3H8fcnJgSVJeKcuI8jpaRSJp3MS3nDlMBEy1pQBhSt6bLUbFU21ipXrkoyK3VZ2USILhUvpEVeSlKJanFxQEYug3JxlMHBOUgZWongtz/2M7g5nnP2Pnvvs/f8nPdrrb3Oc/nt5/edh83nPOf3PM9+UlVIktrzbbMuQJI0GgNckhplgEtSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGGeDSlCR5dZKXDWhzRZLHTqsmtc0A10wluTjJfyZ5wKxrWUtJ5oDTgb9etHxnksf3LXot8Kpp1qZ2GeCamSQbgR8GCnjeGmx/3aS3OYYzgQ9W1f/uWZBkPXAocH1fuwuBZyQ5bLrlqUUGuGbpdOAy4DzgjD0Lk/xWkgv6GyZ5fZI3dNNHJPm7JAtJPpPkpX3tdnTv/yTwlSTrkmxOcnOSu5Jcn+QFfe2PT/KJbt17krwryR/0rV+2r0X1PTjJvUkO71t2XJJdSQ4CfhT417513wXcSu//wS8m+WKSdVX1f8CVwHNG2aHatxjgmqXTgbd3r+ckObRb/k7gpC74SLIf8FPAO5J8G/CPwDbgSOAE4GVJ+gPvNOBk4OCquge4md6R/kOA3wfeluTwJPsD76P3C+QQ4HygP9yH6QuAqvoycANwfN/is4E/qqq7gO8BPtXX/ibgN4ALqurBVfWwrlaA7cDjhtqD2qcZ4JqJJD8EPBx4d1VdSS9kfxqgqj4LXMU3wvSZwP9U1WXADwBzVfWqqrq7qm4B/gY4tW/zb6iqW/cMV1TVe6rq81X19ap6F3Aj8ATgicC6rv3Xquq9wBV92xmmr34fpwvwJE8FHsM3xrwPBu5a1P5xwNVLbOeurr20IgNcs3IG8OGquqObfwd9wyjd/Gnd9E9389AL/SOS/NeeF/Db9MaS97i1v6Mkpye5uq/9ccB64Ajgtrr/dyr3v3eYvvrdF+DAa4Dfraq7u/n/BA5a1P776B3dL3YQ8F/L9CHdZ286yaN9RJID6Q2J7Jfk9m7xA4CDkzyuqrYB7wH+NMlR9I7En9S1uxX4TFU9coUu7gvkJA+nd9R8AnBpVd2b5GogwC7gyCTpC/Gj6f01MGxf/T4OvDzJTwAH8I1fOgCfBB7VtdkzPHMcSx+BPxp425B9ah/mEbhm4fnAvfSGGL6vez0a+Dd64+JU1QJwMfBmeiG6vXvvFcBd3YnKA5Ps150s/IFl+noQvUBfAEjyc/SCE+DSro4Xdyc7T6E3tLLHavvaBhwG/CnwikVH9h8EntY3f2D3ut//g0kOAL4fuGiZPqT7GOCahTOAN1fV56rq9j0v4I3Az/Rd/vcO4Fn0HclW1b3Ac+mF/meAO4Bz6J2g/CZVdT29QL0U+AK9k4n/0a27G/hx4Cx6QxYvAt4PfHXEvr4KXAPsqKoPLVr9VnonZg/s2n4FeBNwfZKdfe1+DLi4qj6/5J6T+sRHqknfkORy4E1V9eYR3rs/cBPwU90J18Xr/wjYXVWvG9D/WVV17Wr7177HANc+LcnT6F3edwfwM/SOio+pql0jbOsPu/eeNrCxNAGexNS+7ljg3fTGym8BXrja8E5yPPAxeicqXzCguTQxHoFLUqM8iSlJjZrqEMr69etr48aN0+xSkpp35ZVX3lFVc4uXTzXAN27cyNatW6fZpSQ1L8lnl1ruEIokNcoAl6RGGeCS1CgDXJIaZYBLUqMMcElq1MAAT3Jukt1Jrl20/CVJbkhyXZLXrF2JkqSlDHMEfh5wYv+CJM8ATgEeV1WPBV47+dIkSSsZGOBVdQlw56LFvwyc3X3/MVW1ew1qkyStYNQ7MR8F/HD39Zn/B/xGVX18qYZJNgGbADZs2DBid9pXbNz8gZn0u+Psk2fSrzSOUU9irgMOofdU798E3p0kSzWsqi1VNV9V83Nz33QrvyRpRKMG+E7gvdVzBfB1ek/5liRNyagB/vfAMwCSPArYn94TTSRJUzJwDDzJ+cDTgfXdw1dfCZwLnNtdWng3cEb5ZAhJmqqBAb7C8/1eNOFaJEmr4J2YktQoA1ySGmWAS1KjDHBJapQBLkmNMsAlqVEGuCQ1ygCXpEYZ4JLUKANckhplgEtSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGDQzwJOcm2d09fWfxul9PUkl8HqYkTdkwR+DnAScuXpjkaODZwOcmXJMkaQgDA7yqLgHuXGLVnwMvB3wWpiTNwEhj4ElOAW6rqm0TrkeSNKSBDzVeLMkDgd+mN3wyTPtNwCaADRs2rLY7SdIyRjkC/07gEcC2JDuAo4Crkhy2VOOq2lJV81U1Pzc3N3qlkqT7WfUReFVdA3zHnvkuxOer6o4J1iVJGmCYywjPBy4Fjk2yM8lZa1+WJGmQgUfgVXXagPUbJ1aNJGlo3okpSY0ywCWpUQa4JDXKAJekRhngktQoA1ySGmWAS1KjDHBJapQBLkmNMsAlqVEGuCQ1ygCXpEYZ4JLUKANckhplgEtSowxwSWqUAS5JjRrmkWrnJtmd5Nq+ZX+S5IYkn0zyviQHr22ZkqTFhjkCPw84cdGyi4Djqup7gU8Dr5hwXZKkAQYGeFVdAty5aNmHq+qebvYy4Kg1qE2StIJJjIH/PPCh5VYm2ZRka5KtCwsLE+hOkgRjBniS3wHuAd6+XJuq2lJV81U1Pzc3N053kqQ+60Z9Y5IzgecCJ1RVTawiSdJQRgrwJCcCLweeVlX/M9mSJEnDGOYywvOBS4Fjk+xMchbwRuAg4KIkVyd50xrXKUlaZOAReFWdtsTiv12DWiRJq+CdmJLUKANckhplgEtSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGGeCS1CgDXJIaZYBLUqMMcElqlAEuSY0ywCWpUQa4JDXKAJekRhngktSoYR6pdm6S3Umu7Vt2SJKLktzY/Xzo2pYpSVpsmCPw84ATFy3bDHykqh4JfKSblyRN0cAAr6pLgDsXLT4FeEs3/Rbg+ROuS5I0wKhj4IdW1a5u+nbg0OUaJtmUZGuSrQsLCyN2J0labOyTmFVVQK2wfktVzVfV/Nzc3LjdSZI6owb4F5IcDtD93D25kiRJwxg1wC8EzuimzwD+YTLlSJKGNcxlhOcDlwLHJtmZ5CzgbOBHktwIPKublyRN0bpBDarqtGVWnTDhWiRJq+CdmJLUKANckhplgEtSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGGeCS1CgDXJIaZYBLUqMMcElqlAEuSY0ywCWpUQa4JDXKAJekRhngktSosQI8ya8luS7JtUnOT3LApAqTJK1s5ABPciTwUmC+qo4D9gNOnVRhkqSVjTuEsg44MMk64IHA58cvSZI0jJEDvKpuA14LfA7YBXypqj68uF2STUm2Jtm6sLAweqWSpPsZZwjlocApwCOAI4AHJXnR4nZVtaWq5qtqfm5ubvRKJUn3M84QyrOAz1TVQlV9DXgv8OTJlCVJGmScAP8c8MQkD0wS4ARg+2TKkiQNMs4Y+OXABcBVwDXdtrZMqC5J0gDrxnlzVb0SeOWEapEkrYJ3YkpSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGGeCS1KixrgOfpo2bPzCzvnecffLM+pak5XgELkmNMsAlqVEGuCQ1ygCXpEYZ4JLUKANckhplgEtSowxwSWqUAS5JjRorwJMcnOSCJDck2Z7kSZMqTJK0snFvpX898E9V9cIk+wMPnEBNkqQhjBzgSR4CPBU4E6Cq7gbunkxZkqRBxhlCeQSwALw5ySeSnJPkQYsbJdmUZGuSrQsLC2N0J0nqN06ArwOOB/6qqh4PfAXYvLhRVW2pqvmqmp+bmxujO0lSv3ECfCews6ou7+YvoBfokqQpGDnAq+p24NYkx3aLTgCun0hVkqSBxr0K5SXA27srUG4Bfm78kiRJwxgrwKvqamB+QrVIklbBOzElqVEGuCQ1ygCXpEYZ4JLUKANckhplgEtSowxwSWrUuDfySN8SNm7+wMz63nH2yTPrW23zCFySGmWAS1KjDHBJapQBLkmNMsAlqVEGuCQ1ygCXpEYZ4JLUKANckho1doAn2S/JJ5K8fxIFSZKGM4kj8F8Ftk9gO5KkVRgrwJMcBZwMnDOZciRJwxr3CPx1wMuBry/XIMmmJFuTbF1YWBizO0nSHiMHeJLnArur6sqV2lXVlqqar6r5ubm5UbuTJC0yzhH4U4DnJdkBvBN4ZpK3TaQqSdJAIwd4Vb2iqo6qqo3AqcBHq+pFE6tMkrQirwOXpEZN5Ik8VXUxcPEktiVJGo5H4JLUKANckhplgEtSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGGeCS1CgDXJIaZYBLUqMMcElqlAEuSY0ywCWpUQa4JDXKAJekRhngktSocZ5Kf3SSjyW5Psl1SX51koVJklY2ziPV7gF+vaquSnIQcGWSi6rq+gnVJklawThPpd9VVVd103cB24EjJ1WYJGllE3mocZKNwOOBy5dYtwnYBLBhw4ZJdCdJI9m4+QMz63vH2SdPfJtjn8RM8mDg74CXVdV/L15fVVuqar6q5ufm5sbtTpLUGSvAk3w7vfB+e1W9dzIlSZKGMc5VKAH+FtheVX82uZIkScMY5wj8KcDPAs9McnX3OmlCdUmSBhj5JGZV/TuQCdYiSVoF78SUpEYZ4JLUKANckhplgEtSowxwSWqUAS5JjTLAJalRBrgkNcoAl6RGGeCS1CgDXJIaZYBLUqMMcElqlAEuSY0ywCWpUQa4JDXKAJekRo37UOMTk3wqyU1JNk+qKEnSYOM81Hg/4C+AHwUeA5yW5DGTKkyStLJxjsCfANxUVbdU1d3AO4FTJlOWJGmQkR9qDBwJ3No3vxP4wcWNkmwCNnWzX07yqTH6HGQ9cMekN5o/nvQWl7Um9U+JtY9oAp8v9/1srKr2Mf87P3ypheME+FCqaguwZa37AUiytarmp9HXWmi5fmufnZbrt/bxjDOEchtwdN/8Ud0ySdIUjBPgHwcemeQRSfYHTgUunExZkqRBRh5Cqap7krwY+GdgP+DcqrpuYpWNZipDNWuo5fqtfXZart/ax5CqmnUNkqQReCemJDXKAJekRjUd4El+Msl1Sb6eZNnLeZLsSHJNkquTbJ1mjStZRf173VcWJDkkyUVJbux+PnSZdvd2+/3qJDM9yT1oPyZ5QJJ3desvT7Jx+lUubYjaz0yy0Levf2EWdS4lyblJdie5dpn1SfKG7t/2ySTHT7vG5QxR+9OTfKlvv//eVAusqmZfwKOBY4GLgfkV2u0A1s+63lHqp3eC+GbgGGB/YBvwmL2g9tcAm7vpzcAfL9Puy7Ouddj9CPwK8KZu+lTgXbOuexW1nwm8cda1LlP/U4HjgWuXWX8S8CEgwBOBy2dd8ypqfzrw/lnV1/QReFVtr6q1vLNzTQ1Z/976lQWnAG/ppt8CPH+GtQxjmP3Y/2+6ADghSaZY43L21s/AUKrqEuDOFZqcAry1ei4DDk5y+HSqW9kQtc9U0wG+CgV8OMmV3a39LVnqKwuOnFEt/Q6tql3d9O3Aocu0OyDJ1iSXJZllyA+zH+9rU1X3AF8CHjaV6lY27GfgJ7ohiAuSHL3E+r3V3voZH9aTkmxL8qEkj51mx2t+K/24kvwLcNgSq36nqv5hyM38UFXdluQ7gIuS3ND9Zl1zE6p/JlaqvX+mqirJctejPrzb98cAH01yTVXdPOlaxT8C51fVV5P8Ir2/JJ4545r2BVfR+4x/OclJwN8Dj5xW53t9gFfVsyawjdu6n7uTvI/en6RTCfAJ1D+zryxYqfYkX0hyeFXt6v7c3b3MNvbs+1uSXAw8nt547rQNsx/3tNmZZB3wEOCL0ylvRQNrr6r+Os+hd46iFc1+LUdV/Xff9AeT/GWS9VU1lS/o+pYfQknyoCQH7ZkGng0seUZ5L7W3fmXBhcAZ3fQZwDf9NZHkoUke0E2vB54CXD+1Cu9vmP3Y/296IfDR6s5UzdjA2heNGT8P2D7F+sZ1IXB6dzXKE4Ev9Q3P7dWSHLbnPEmSJ9DL1On90p/1Wd4xzxC/gN542VeBLwD/3C0/AvhgN30MvbP224Dr6A1dzLz2Yevv5k8CPk3vyHWvqJ/e2PBHgBuBfwEO6ZbPA+d0008Grun2/TXAWTOu+Zv2I/Aq4Hnd9AHAe4CbgCuAY2a9n1dR+6u7z/c24GPAd8+65r7azwd2AV/rPu9nAb8E/FK3PvQeDnNz9zlZ9oqyvbD2F/ft98uAJ0+zPm+ll6RGfcsPoUjStyoDXJIaZYBLUqMMcElqlAEuSY0ywCWpUQa4JDXq/wEXGCNkf4/zeAAAAABJRU5ErkJggg==\n",
+ "image/png": "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",
"text/plain": [
""
]
@@ -204,14 +205,14 @@
}
],
"source": [
- "plt.hist(xyz_avg[:,1])\n",
+ "plt.hist(xyz_avg[:, 1])\n",
"plt.title('Average $y(t)$');"
]
}
],
"metadata": {
"kernelspec": {
- "display_name": "Python 3",
+ "display_name": "venv",
"language": "python",
"name": "python3"
},
@@ -225,7 +226,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
- "version": "3.7.4"
+ "version": "3.13.7"
}
},
"nbformat": 4,
diff --git a/apps/py/ch11_notebooks/lorenz.py b/apps/py/ch11_notebooks/lorenz.py
index 95c02b4..0a2099d 100644
--- a/apps/py/ch11_notebooks/lorenz.py
+++ b/apps/py/ch11_notebooks/lorenz.py
@@ -1,5 +1,4 @@
from matplotlib import pyplot as plt
-from mpl_toolkits.mplot3d import Axes3D
import numpy as np
from scipy import integrate
diff --git a/apps/py/ch11_notebooks/references.ipynb b/apps/py/ch11_notebooks/references.ipynb
index 83fc384..e685aec 100644
--- a/apps/py/ch11_notebooks/references.ipynb
+++ b/apps/py/ch11_notebooks/references.ipynb
@@ -13,9 +13,7 @@
"metadata": {},
"outputs": [],
"source": [
- "import requests\n",
"import bs4\n",
- "import collections\n",
"import feedparser"
]
},
@@ -59,7 +57,7 @@
}
],
"source": [
- "print(f\"Size of downloaded data: {len(str(feed))/1024/1024:,.1f} MB!\")"
+ "print(f'Size of downloaded data: {len(str(feed)) / 1024 / 1024:,.1f} MB!')"
]
},
{
@@ -84,7 +82,7 @@
],
"source": [
"entries = feed.get('entries')\n",
- "print(f\"Downloaded {len(entries):,} entries.\")\n",
+ "print(f'Downloaded {len(entries):,} entries.')\n",
"# print(entries[0].get('description')[:300])"
]
},
@@ -116,10 +114,10 @@
"for e in entries:\n",
" desc = e.get('description')\n",
" soup = bs4.BeautifulSoup('' + desc + '', 'html.parser')\n",
- " \n",
- " links = [ a['href'] for a in soup.findAll('a') ]\n",
- " links = [ l.replace('www.', '') for l in links]\n",
- " links = [ l.replace('do.co', 'digitalocean.com') for l in links]\n",
+ "\n",
+ " links = [a['href'] for a in soup.findAll('a')]\n",
+ " links = [l.replace('www.', '') for l in links]\n",
+ " links = [l.replace('do.co', 'digitalocean.com') for l in links]\n",
" all_links.extend(links)\n",
"\n",
"\n",
@@ -128,7 +126,7 @@
"# Yikes, bad links!\n",
"all_links = all_links[4:]\n",
"\n",
- "print(f\"Parsed {len(all_links):,} links from all episodes.\")"
+ "print(f'Parsed {len(all_links):,} links from all episodes.')"
]
},
{
@@ -153,10 +151,11 @@
],
"source": [
"from urllib.parse import urlparse\n",
+ "\n",
"excluded = {'pythonbytes.fm', '', '#'}\n",
"domains = [urlparse(link).netloc for link in all_links if link not in excluded]\n",
"domains = [d for d in domains if d not in excluded]\n",
- "print(f\"First 10 domains are {domains[:10]} ...\")"
+ "print(f'First 10 domains are {domains[:10]} ...')"
]
},
{
@@ -238,6 +237,7 @@
"source": [
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
+ "\n",
"%matplotlib inline"
]
},
@@ -267,16 +267,16 @@
}
],
"source": [
- "plt.rcParams[\"figure.figsize\"] = (15,5)\n",
+ "plt.rcParams['figure.figsize'] = (15, 5)\n",
"\n",
"values = [t[1] for t in top_25]\n",
- "value_bins = [t[0] for t in top_25 ]\n",
+ "value_bins = [t[0] for t in top_25]\n",
"\n",
- "ind = list(range(1, len(values)+1)) # the x locations for the groups\n",
- "width = 0.40 # the width of the bars: can also be len(x) sequence\n",
+ "ind = list(range(1, len(values) + 1)) # the x locations for the groups\n",
+ "width = 0.40 # the width of the bars: can also be len(x) sequence\n",
"\n",
"# p1 = plt.bar(ind, menMeans, width)\n",
- "p1 = plt.bar(ind, values, width*2)\n",
+ "p1 = plt.bar(ind, values, width * 2)\n",
"\n",
"plt.ylabel('Number of referrals')\n",
"plt.title('Sites referred to by Python Bytes')\n",
diff --git a/apps/py/requirements.piptools b/apps/py/requirements.piptools
index a310a83..9f4b622 100644
--- a/apps/py/requirements.piptools
+++ b/apps/py/requirements.piptools
@@ -1,8 +1,10 @@
# comments
colorama
bs4
-httpx
flask
+httpx
+ipykernel
+ipywidgets
matplotlib
numpy
scipy
diff --git a/apps/py/requirements.txt b/apps/py/requirements.txt
index daa7db3..c4bf3e3 100644
--- a/apps/py/requirements.txt
+++ b/apps/py/requirements.txt
@@ -1,117 +1,197 @@
-#
-# This file is autogenerated by pip-compile with Python 3.11
-# by the following command:
-#
-# pip-compile requirements.piptools
-#
-anyio==4.1.0
+# This file was autogenerated by uv via the following command:
+# uv pip compile requirements.piptools --output-file requirements.txt
+anyio==4.10.0
# via httpx
-beautifulsoup4==4.12.2
+appnope==0.1.4
+ # via ipykernel
+asttokens==3.0.0
+ # via stack-data
+beautifulsoup4==4.13.5
# via bs4
-blinker==1.7.0
+blinker==1.9.0
# via flask
-bs4==0.0.1
+bs4==0.0.2
# via -r requirements.piptools
-certifi==2023.11.17
+certifi==2025.8.3
# via
# httpcore
# httpx
# requests
-charset-normalizer==3.3.2
+charset-normalizer==3.4.3
# via requests
-click==8.1.7
+click==8.3.0
# via flask
colorama==0.4.6
# via -r requirements.piptools
-contourpy==1.2.0
+comm==0.2.3
+ # via
+ # ipykernel
+ # ipywidgets
+contourpy==1.3.3
# via matplotlib
cycler==0.12.1
# via matplotlib
-flask==3.0.0
+debugpy==1.8.17
+ # via ipykernel
+decorator==5.2.1
+ # via ipython
+executing==2.2.1
+ # via stack-data
+flask==3.1.2
# via -r requirements.piptools
-fonttools==4.46.0
+fonttools==4.60.0
# via matplotlib
-h11==0.14.0
+h11==0.16.0
# via httpcore
-httpcore==1.0.2
+httpcore==1.0.9
# via httpx
-httpx==0.25.2
+httpx==0.28.1
# via -r requirements.piptools
-idna==3.6
+idna==3.10
# via
# anyio
# httpx
# requests
-iniconfig==2.0.0
+iniconfig==2.1.0
# via pytest
-itsdangerous==2.1.2
+ipykernel==6.30.1
+ # via -r requirements.piptools
+ipython==9.5.0
+ # via
+ # ipykernel
+ # ipywidgets
+ipython-pygments-lexers==1.1.1
+ # via ipython
+ipywidgets==8.1.7
+ # via -r requirements.piptools
+itsdangerous==2.2.0
# via flask
-jinja2==3.1.2
+jedi==0.19.2
+ # via ipython
+jinja2==3.1.6
# via flask
-kiwisolver==1.4.5
+jupyter-client==8.6.3
+ # via ipykernel
+jupyter-core==5.8.1
+ # via
+ # ipykernel
+ # jupyter-client
+jupyterlab-widgets==3.0.15
+ # via ipywidgets
+kiwisolver==1.4.9
# via matplotlib
-markdown-it-py==3.0.0
+markdown-it-py==4.0.0
# via rich
-markupsafe==2.1.3
+markupsafe==3.0.2
# via
+ # flask
# jinja2
# werkzeug
-matplotlib==3.8.2
+matplotlib==3.10.6
# via -r requirements.piptools
+matplotlib-inline==0.1.7
+ # via
+ # ipykernel
+ # ipython
mdurl==0.1.2
# via markdown-it-py
-numpy==1.26.2
+nest-asyncio==1.6.0
+ # via ipykernel
+numpy==2.3.3
# via
# -r requirements.piptools
# contourpy
# matplotlib
# scipy
-packaging==23.2
+packaging==25.0
# via
+ # ipykernel
# matplotlib
# pytest
-pillow==10.1.0
+parso==0.8.5
+ # via jedi
+pexpect==4.9.0
+ # via ipython
+pillow==11.3.0
# via matplotlib
-pluggy==1.3.0
+platformdirs==4.4.0
+ # via jupyter-core
+pluggy==1.6.0
# via pytest
pprintpp==0.4.0
# via pytest-clarity
-pygments==2.17.2
- # via rich
-pyparsing==3.1.1
+prompt-toolkit==3.0.52
+ # via ipython
+psutil==7.1.0
+ # via ipykernel
+ptyprocess==0.7.0
+ # via pexpect
+pure-eval==0.2.3
+ # via stack-data
+pygments==2.19.2
+ # via
+ # ipython
+ # ipython-pygments-lexers
+ # pytest
+ # rich
+pyparsing==3.2.5
# via matplotlib
-pytest==7.4.3
+pytest==8.4.2
# via
# -r requirements.piptools
# pytest-clarity
# pytest-mock
pytest-clarity==1.0.1
# via -r requirements.piptools
-pytest-mock==3.12.0
+pytest-mock==3.15.1
# via -r requirements.piptools
-python-dateutil==2.8.2
- # via matplotlib
-requests==2.31.0
+python-dateutil==2.9.0.post0
+ # via
+ # jupyter-client
+ # matplotlib
+pyzmq==27.1.0
+ # via
+ # ipykernel
+ # jupyter-client
+requests==2.32.5
# via -r requirements.piptools
-rich==13.7.0
+rich==14.1.0
# via pytest-clarity
-scipy==1.11.4
+scipy==1.16.2
# via -r requirements.piptools
-six==1.16.0
+six==1.17.0
# via python-dateutil
-sniffio==1.3.0
- # via
- # anyio
- # httpx
-soupsieve==2.5
+sniffio==1.3.1
+ # via anyio
+soupsieve==2.8
# via beautifulsoup4
-sqlalchemy==2.0.23
+sqlalchemy==2.0.43
# via -r requirements.piptools
-typing-extensions==4.9.0
- # via sqlalchemy
+stack-data==0.6.3
+ # via ipython
+tornado==6.5.2
+ # via
+ # ipykernel
+ # jupyter-client
+traitlets==5.14.3
+ # via
+ # ipykernel
+ # ipython
+ # ipywidgets
+ # jupyter-client
+ # jupyter-core
+ # matplotlib-inline
+typing-extensions==4.15.0
+ # via
+ # beautifulsoup4
+ # sqlalchemy
unsync==1.4.0
# via -r requirements.piptools
-urllib3==2.1.0
+urllib3==2.5.0
# via requests
-werkzeug==3.0.1
+wcwidth==0.2.14
+ # via prompt-toolkit
+werkzeug==3.1.3
# via flask
+widgetsnbextension==4.0.14
+ # via ipywidgets
diff --git a/apps/ruff.toml b/apps/ruff.toml
new file mode 100644
index 0000000..eee74ff
--- /dev/null
+++ b/apps/ruff.toml
@@ -0,0 +1,43 @@
+# [ruff]
+line-length = 120
+format.quote-style = "single"
+
+# Enable Pyflakes `E` and `F` codes by default.
+lint.select = ["E", "F"]
+lint.ignore = []
+
+# Exclude a variety of commonly ignored directories.
+exclude = [
+ ".bzr",
+ ".direnv",
+ ".eggs",
+ ".git",
+ ".hg",
+ ".mypy_cache",
+ ".nox",
+ ".pants.d",
+ ".ruff_cache",
+ ".svn",
+ ".tox",
+ "__pypackages__",
+ "_build",
+ "buck-out",
+ "build",
+ "dist",
+ "node_modules",
+ ".env",
+ ".venv",
+ "venv",
+ "typings/**/*.pyi",
+]
+lint.per-file-ignores = { }
+
+# Allow unused variables when underscore-prefixed.
+# dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
+
+# Assume Python 3.13.
+target-version = "py313"
+
+#[tool.ruff.mccabe]
+## Unlike Flake8, default to a complexity level of 10.
+lint.mccabe.max-complexity = 10