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 -![](readme_resources/python-for-dotnet-course.png) +[![](readme_resources/python-for-dotnet-course.png)](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": "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\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