<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>Python &#8211; My Shitty Code</title>
	<atom:link href="https://myshittycode.com/tag/python/feed/" rel="self" type="application/rss+xml" />
	<link>https://myshittycode.com</link>
	<description>Embracing the Messiness in Search of Epic Solutions</description>
	<lastBuildDate>Tue, 24 Sep 2024 18:14:02 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.9.1</generator>

<image>
	<url>https://myshittycode.com/wp-content/uploads/2022/04/cropped-icon-32x32.png</url>
	<title>Python &#8211; My Shitty Code</title>
	<link>https://myshittycode.com</link>
	<width>32</width>
	<height>32</height>
</image> 
<site xmlns="com-wordpress:feed-additions:1">205304208</site>	<item>
		<title>MSAL: Caching Access Token</title>
		<link>https://myshittycode.com/2024/09/24/msal-caching-access-token/</link>
					<comments>https://myshittycode.com/2024/09/24/msal-caching-access-token/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Tue, 24 Sep 2024 18:13:59 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[MacOS]]></category>
		<category><![CDATA[MSAL]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2701</guid>

					<description><![CDATA[<p>Why Cache Access Token with MSAL? Building upon the previous post that performs delegated access authentication with MSAL, suppose your program uses this function to get the access token. In this case, it will always launch the browser to complete the authentication flow and retrieve the access token every time the program runs. While it [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-caching-access-token/">MSAL: Caching Access Token</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#why-cache-access-token-with-msal">Why Cache Access Token with MSAL?</a></li><li><a href="#solution-a-use-custom-persistence-cache">Solution A: Use custom persistence cache</a></li><li><a href="#solution-b-use-the-os-platforms-encrypted-vault-preferred">Solution B: Use the OS platform&#8217;s encrypted vault (PREFERRED)</a></li></ul></nav></div>



<h2 class="wp-block-heading" id="why-cache-access-token-with-msal">Why Cache Access Token with MSAL?</h2>



<p>Building upon <a href="https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/" data-type="post" data-id="2678">the previous post</a> that performs delegated access authentication with MSAL, suppose your program uses this function to get the access token.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
def get_access_token():
    app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
    result = app.acquire_token_interactive(scopes=SCOPES)
    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>In this case, it will always launch the browser to complete the authentication flow and retrieve the access token every time the program runs.</p>



<figure class="wp-block-image size-full"><img fetchpriority="high" decoding="async" width="1650" height="1096" src="https://myshittycode.com/wp-content/uploads/2024/09/msal-browser.png?x45560" alt="" class="wp-image-2703" srcset="https://myshittycode.com/wp-content/uploads/2024/09/msal-browser.png 1650w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-300x199.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-1024x680.png 1024w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-768x510.png 768w, https://myshittycode.com/wp-content/uploads/2024/09/msal-browser-1536x1020.png 1536w" sizes="(max-width: 1650px) 100vw, 1650px" /></figure>



<p>While it works, it introduces a slight latency to your program. Fortunately, MSAL provides the capability to cache the access token.</p>



<h2 class="wp-block-heading" id="solution-a-use-custom-persistence-cache">Solution A: Use custom persistence cache</h2>



<p>In this solution, we can introduce our custom persistence cache.</p>



<p>First, we can use these functions to load the access token from the cache file and save it to the cache file.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os
import msal

...
CACHE_FILE = &#039;msal_token_cache.json&#039;

def load_cache():
    cache = msal.SerializableTokenCache()

    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE, &#039;rb&#039;) as f:
            cache.deserialize(f.read())

    return cache

def save_cache(cache):
    if cache.has_state_changed:
        with open(CACHE_FILE, &#039;wb&#039;) as f:
            f.write(cache.serialize().encode())
</pre></div>


<p>We can first attempt to get a valid access token from the cache. It will only get the token interactively via the browser if the access token doesn&#8217;t exist or has expired.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [2,6,8,9,10,11,12,13,14,17]; title: ; notranslate">
def get_access_token():
    cache = load_cache()
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=AUTHORITY,
        token_cache=cache,
    )
    result = None
    accounts = app.get_accounts()

    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts&#x5B;0])

    if not result:
        result = app.acquire_token_interactive(scopes=SCOPES)

    save_cache(cache)
    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>While this solution works, the implementation code is very lengthy. Furthermore, the access token is stored in the cache file as clear text.</p>



<h2 class="wp-block-heading" id="solution-b-use-the-os-platforms-encrypted-vault-preferred">Solution B: Use the OS platform&#8217;s encrypted vault (PREFERRED)</h2>



<p>A Python package called <a href="https://github.com/AzureAD/microsoft-authentication-extensions-for-python" target="_blank" rel="noopener">msal-extensions</a> allows the access token to be stored securely in the OS platform&#8217;s encrypted vault. For example, If the program runs on MacOS, KeyChain will be used. Windows uses DPAPI, and Linux uses LibSecret.</p>



<p>To begin, install that Python package.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [4]; title: ; notranslate">
# requirements.txt

msal
msal-extensions
</pre></div>


<p>The configuration is much simpler than Solution A.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [2,5,11]; title: ; notranslate">
import msal
from msal_extensions import build_encrypted_persistence, PersistedTokenCache

...
CACHE_FILE = &#039;msal_token_cache.json&#039;

def get_access_token():
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=AUTHORITY,
        token_cache=PersistedTokenCache(build_encrypted_persistence(CACHE_FILE)),
    )

    result = None
    accounts = app.get_accounts()

    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts&#x5B;0])

    if not result:
        result = app.acquire_token_interactive(scopes=SCOPES)

    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>When running the program on MacOS, the access token is stored in the KeyChain.</p>



<figure class="wp-block-image size-full"><img decoding="async" width="1940" height="1068" src="https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain.png?x45560" alt="" class="wp-image-2704" srcset="https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain.png 1940w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-300x165.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-1024x564.png 1024w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-768x423.png 768w, https://myshittycode.com/wp-content/uploads/2024/09/msal-keychain-1536x846.png 1536w" sizes="(max-width: 1940px) 100vw, 1940px" /></figure>



<p><strong>Note:</strong> An empty cache file named msal_token_cache.json has also been created. Unfortunately, there is no option in <code>msal-extensions==1.2.0</code> to disable the creation of this file at the moment.</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-caching-access-token/">MSAL: Caching Access Token</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/09/24/msal-caching-access-token/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2701</post-id>	</item>
		<item>
		<title>MSAL: Launching Specific Browser with Delegated Access Authentication</title>
		<link>https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/</link>
					<comments>https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Tue, 24 Sep 2024 15:16:59 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Development Tools]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[MacOS]]></category>
		<category><![CDATA[MSAL]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2689</guid>

					<description><![CDATA[<p>Why Launch a Specific Browser with MSAL? Building upon the previous post that performs delegated access authentication with MSAL, your institution may sometimes allow you to access Microsoft 365 products only on the approved browsers. By default, MSAL launches the default browser on your machine to obtain the access token interactively. If your institution only [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/">MSAL: Launching Specific Browser with Delegated Access Authentication</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#why-launch-a-specific-browser-with-msal">Why Launch a Specific Browser with MSAL?</a></li><li><a href="#overriding-with-browser-environment-variable">Overriding with BROWSER environment variable</a></li><li><a href="#what-if-the-browser-environment-variables-predefined-value-doesnt-work">What if the BROWSER environment variable&#8217;s predefined value doesn&#8217;t work?</a></li></ul></nav></div>



<h2 class="wp-block-heading" id="why-launch-a-specific-browser-with-msal">Why Launch a Specific Browser with MSAL?</h2>



<p>Building upon <a href="https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/" data-type="post" data-id="2678">the previous post</a> that performs delegated access authentication with MSAL, your institution may sometimes allow you to access Microsoft 365 products only on the approved browsers.</p>



<p>By default, MSAL launches the default browser on your machine to obtain the access token interactively. </p>



<p>If your institution only allows Chrome and your default browser is Firefox, you will get the following message:</p>



<figure class="wp-block-image aligncenter size-full"><img decoding="async" width="774" height="690" src="https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked.png?x45560" alt="msal" class="wp-image-2693" srcset="https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked.png 774w, https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked-300x267.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/firefox-blocked-768x685.png 768w" sizes="(max-width: 774px) 100vw, 774px" /></figure>



<h2 class="wp-block-heading" id="overriding-with-browser-environment-variable">Overriding with BROWSER environment variable</h2>



<p>MSAL relies on Python&#8217;s built-in module called <a href="https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/331c16fdd803df6a629cb215a6372dbad93f0ba8/msal/oauth2cli/authcode.py#L68" target="_blank" rel="noopener">webbrowser</a> to launch the browser. This allows us to change the browser by setting the <a href="https://docs.python.org/3/library/webbrowser.html" target="_blank" rel="noopener">BROWSER environment variable</a> to the predefined values (&#8216;firefox&#8217;, &#8216;chrome&#8217;, etc.)</p>



<p>For example, to launch Firefox instead of the default browser, we can do this:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [5]; title: ; notranslate">
import os
import msal

# ... 
os.environ&#x5B;&#039;BROWSER&#039;] = &#039;firefox&#039;
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
result = app.acquire_token_interactive(scopes=SCOPES)
</pre></div>


<h2 class="wp-block-heading" id="what-if-the-browser-environment-variables-predefined-value-doesnt-work">What if the BROWSER environment variable&#8217;s predefined value doesn&#8217;t work?</h2>



<p>I discovered that Python&#8217;s webbrowser failed to launch the correct browser, especially when dealing with Chromnium-based browsers.</p>



<p>For example, suppose my default browser is Brave, and I set the BROWSER environment variable to Chrome. In that case, it will still launch the Brave browser.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [3]; title: ; notranslate">
# default browser = Brave

os.environ&#x5B;&#039;BROWSER&#039;] = &#039;chrome&#039;  # doesn&#039;t work
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
result = app.acquire_token_interactive(scopes=SCOPES)
</pre></div>


<p>Fortunately, it is possible to <a href="https://github.com/AzureAD/microsoft-authentication-library-for-python/discussions/750#discussioncomment-10710600" target="_blank" rel="noopener">specify a full path ending with &#8216;%s&#8217;</a> instead:</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; highlight: [3]; title: ; notranslate">
# default browser = Brave

os.environ&#x5B;&#039;BROWSER&#039;] = &#039;open -a /Applications/Google\\ Chrome.app %s&#039;  # works
app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
result = app.acquire_token_interactive(scopes=SCOPES)
</pre></div>


<p>If you are stuck on the &#8220;Approve sign in request&#8221; or<em> </em>&#8220;Set up your device to get access&#8221; screen indefinitely, consider clearing the browser cache and then try again.</p>



<p></p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/">MSAL: Launching Specific Browser with Delegated Access Authentication</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/09/24/msal-launching-specific-browser-with-delegated-access-authentication/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2689</post-id>	</item>
		<item>
		<title>MSAL: Delegated Access Authentication</title>
		<link>https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/</link>
					<comments>https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Mon, 23 Sep 2024 20:17:14 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Programming Language]]></category>
		<category><![CDATA[Azure]]></category>
		<category><![CDATA[MS 365]]></category>
		<category><![CDATA[MSAL]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2678</guid>

					<description><![CDATA[<p>The Microsoft Authentication Library (MSAL) supports various programming languages and frameworks to simplify the authentication flow against the Microsoft identity platform, which is a prerequisite to invoke APIs such as Microsoft Graph. In this example, we will write a Python script that performs delegated authentication flow using the user&#8217;s credentials. Once authenticated, the script will [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/">MSAL: Delegated Access Authentication</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#app-registration-and-configuration">App Registration and Configuration</a></li><li><a href="#custom-python-script">Custom Python Program</a></li></ul></nav></div>



<p>The <a href="https://learn.microsoft.com/en-us/entra/msal/overview" target="_blank" rel="noopener">Microsoft Authentication Library (MSAL)</a> supports various programming languages and frameworks to simplify the authentication flow against the Microsoft identity platform, which is a prerequisite to invoke APIs such as Microsoft Graph.</p>



<p>In this example, we will write a Python script that performs delegated authentication flow using the user&#8217;s credentials. Once authenticated, the script will pull the user&#8217;s OneNote data via Microsoft Graph.</p>



<p><strong>Note:</strong> While we can also authenticate via app-only access, this is generally scrutinized in larger institutions due to the blast radius. This is because the app can modify other users&#8217; content stored in Microsoft 365 products (emails, calendars, etc).</p>



<h2 class="wp-block-heading" id="app-registration-and-configuration">App Registration and Configuration</h2>



<p>Even if you plan to run your program on your local machine, you must register it in the Azure Portal to generate a unique client ID to authenticate.</p>



<ul class="wp-block-list">
<li>Go to <a href="https://portal.azure.com/" target="_blank" rel="noopener">https://portal.azure.com/</a> and sign in.</li>



<li>Go to <strong>App Registrations</strong> and register your app.</li>



<li>Go to the registered app -> <strong>Manage</strong> (<em>step 1</em>) -> <strong>Authentication</strong> (<em>step </em>2). Ensure the following configurations exist:
<ul class="wp-block-list">
<li>A <strong>Mobile and desktop applications</strong> platform with the redirect URI set as <strong>http://localhost</strong> (<em>step </em>3).</li>



<li>The <strong>Access tokens (used for implicit flows)</strong> option is checked (<em>step </em>4).</li>
</ul>
</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2114" height="2908" src="https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication.png?x45560" alt="" class="wp-image-2680" srcset="https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication.png 2114w, https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication-218x300.png 218w, https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication-744x1024.png 744w, https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication-768x1056.png 768w, https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication-1117x1536.png 1117w, https://myshittycode.com/wp-content/uploads/2024/09/msal-authentication-1489x2048.png 1489w" sizes="auto, (max-width: 2114px) 100vw, 2114px" /></figure>



<ul class="wp-block-list">
<li>Go to the registered app -> <strong>Manage</strong> (<em>step 1</em>) -> <strong>API permissions</strong> (<em>step </em>2).</li>



<li>Add the permissions you need (<em>step </em>3). OneNote&#8217;s permissions are added in this example because the script will query OneNote via Microsoft Graph. </li>



<li>Before we can call the Microsoft Graph API, these permissions must have green check marks (<em>step </em>5), accomplished by clicking the <strong>Grant admin consent</strong> button (<em>step </em>4). If this button is grayed out, request your institution&#8217;s Azure administrators to perform this step on your behalf.</li>
</ul>



<figure class="wp-block-image size-full"><img loading="lazy" decoding="async" width="2110" height="1484" src="https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions.png?x45560" alt="" class="wp-image-2681" srcset="https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions.png 2110w, https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions-300x211.png 300w, https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions-1024x720.png 1024w, https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions-768x540.png 768w, https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions-1536x1080.png 1536w, https://myshittycode.com/wp-content/uploads/2024/09/msal-permissions-2048x1440.png 2048w" sizes="auto, (max-width: 2110px) 100vw, 2110px" /></figure>



<h2 class="wp-block-heading" id="custom-python-script">Custom Python Program</h2>



<p>Now that the app is registered and configured in the Azure Portal, we can work on the custom script.</p>



<p>Install the msal library.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
# requirements.txt

requests
msal
</pre></div>


<p>Define the needed variables. The client ID and tenant ID can be retrieved from your registered app in the Azure Portal.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import msal
import requests

CLIENT_ID = &#039;&#x5B;YOUR_CLIENT_ID]&#039;
TENANT_ID = &#039;&#x5B;TENANT_ID]&#039;
AUTHORITY = f&#039;https://login.microsoftonline.com/{TENANT_ID}&#039;
SCOPES = &#x5B;&#039;Notes.Read&#039;, &#039;User.Read&#039;]
</pre></div>


<p>A function to perform the authentication and to retrieve the access token. MSAL will seamlessly create a web server running on localhost to accept the incoming token data (hence, the need to configure the redirect URI in the Azure Portal).</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
def get_access_token():
    app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
    result = app.acquire_token_interactive(scopes=SCOPES)
    access_token = result&#x5B;&#039;access_token&#039;]

    return access_token
</pre></div>


<p>A simple function to query OneNote pages using Microsoft Graph.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
def get_onenote_pages(access_token):
    url = &#039;https://graph.microsoft.com/v1.0/me/onenote/pages&#039;

    headers = {
        &#039;Authorization&#039;: f&#039;Bearer {access_token}&#039;,
        &#039;Accept&#039;: &#039;application/json&#039;
    }

    response = requests.get(url, headers=headers)
    response.raise_for_status()

    return response.json()
</pre></div>


<p>Finally, run the app to test if the app registration and configuration are set up correctly in the Azure Portal.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
def run():
    access_token = get_access_token()
    print(access_token)

    onenote_pages = get_onenote_pages(access_token)
    print(onenote_pages)


if __name__ == &#039;__main__&#039;:
    run() 
</pre></div><p>The post <a rel="nofollow" href="https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/">MSAL: Delegated Access Authentication</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/09/23/msal-delegated-access-authentication/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2678</post-id>	</item>
		<item>
		<title>Cloud Run: Connecting to Cloud SQL with Private IP and SSL</title>
		<link>https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/</link>
					<comments>https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Thu, 08 Aug 2024 22:05:43 +0000</pubDate>
				<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Programming Language]]></category>
		<category><![CDATA[Cloud Run]]></category>
		<category><![CDATA[Cloud SQL]]></category>
		<category><![CDATA[Docker]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<category><![CDATA[Python]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=2642</guid>

					<description><![CDATA[<p>There are various ways for a Cloud Run instance to connect to a Cloud SQL instance. Regardless of the solutions, if both resources reside in the same GCP organization, connecting using a Cloud SQL&#8217;s private IP with SSL/TLS encryption is highly recommended to prevent network traffic from going out to the internet. It is possible [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/">Cloud Run: Connecting to Cloud SQL with Private IP and SSL</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<div class="wp-block-rank-math-toc-block" id="rank-math-toc"><h2>Table of Contents</h2><nav><ul><li><a href="#cloud-sql-configuration">Cloud SQL Configuration</a></li><li><a href="#options">Options</a><ul><li><a href="#option-a-cloud-sql-language-connectors">Option A: Cloud SQL Language Connectors</a></li><li><a href="#option-b-managed-cloud-sql-proxy-in-cloud-run">Option B: Managed Cloud SQL Proxy in Cloud Run</a></li><li><a href="#option-c-manually-configure-cloud-sql-proxy">Option C: Manually Configure Cloud SQL Proxy</a></li><li><a href="#option-d-use-cloud-sq-ls-private-ip-and-client-certificate">Option D: Use Cloud SQL&#8217;s Private IP and Client Certificate</a></li></ul></li><li><a href="#recommendations">Recommendations</a><ul><li><a href="#best-solution-option-a">Best Solution &#8211; Option A</a></li><li><a href="#distance-next-best-solution-option-d">Distance Next Best Solution &#8211; Option D</a></li><li><a href="#least-favorite-solution-option-b-c">Least Favorite Solution &#8211; Option B &amp; C</a></li></ul></li></ul></nav></div>



<p>There are various ways for a Cloud Run instance to connect to a Cloud SQL instance. Regardless of the solutions, if both resources reside in the same GCP organization, connecting using a Cloud SQL&#8217;s private IP with SSL/TLS encryption is highly recommended to prevent network traffic from going out to the internet.</p>



<p>It is possible to have a Cloud SQL instance with both private and public IPs enabled, and there are legitimate reasons for doing so. For example, it allows the developers to connect to the Cloud SQL instance outside the GCP environment. However, utmost care is needed to ensure the private IP is always used when connecting from the Cloud Run instance.</p>



<p>This article evaluates the pros and cons of each solution and provides some recommendations.</p>



<h2 class="wp-block-heading" id="cloud-sql-configuration">Cloud SQL Configuration</h2>



<p>For consistency&#8217;s sake, SSL/TLS encryption is enabled on the Cloud SQL instance using client certificates.</p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="742" src="https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-1024x742.png?x45560" alt="Cloud SQL" class="wp-image-2651" style="width:800px" srcset="https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-1024x742.png 1024w, https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-300x217.png 300w, https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1-768x557.png 768w, https://myshittycode.com/wp-content/uploads/2024/08/ssl-mode-1.png 1104w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p>A client certificate is also created. </p>



<figure class="wp-block-image size-large is-resized"><img loading="lazy" decoding="async" width="1024" height="417" src="https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-1024x417.png?x45560" alt="" class="wp-image-2650" style="width:800px" srcset="https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-1024x417.png 1024w, https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-300x122.png 300w, https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1-768x313.png 768w, https://myshittycode.com/wp-content/uploads/2024/08/client-certificate-1.png 1096w" sizes="auto, (max-width: 1024px) 100vw, 1024px" /></figure>



<p><strong>Note:</strong> Some solutions, such as Cloud SQL Proxy and Cloud SQL Language Connectors, do not need the client certificate. So, these steps can be safely skipped. From the <a href="https://cloud.google.com/sql/docs/mysql/configure-ssl-instance#enforcing-ssl" target="_blank" rel="noopener">documentation</a>:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>With Cloud SQL Auth Proxy and Cloud SQL Connectors, client and server identities are also automatically verified regardless of the SSL mode setting.</p>
</blockquote>



<h2 class="wp-block-heading" id="options">Options</h2>



<h3 class="wp-block-heading" id="option-a-cloud-sql-language-connectors">Option A: Cloud SQL Language Connectors</h3>



<p>This solution uses <a href="https://cloud.google.com/sql/docs/mysql/connect-connectors" target="_blank" rel="noopener">Cloud SQL Language Connectors</a>, a programming language library maintained by Google.</p>



<p>The following configurations are <strong>NOT</strong> required:</p>



<ul class="wp-block-list">
<li>SSL certificates in the Cloud SQL instance</li>



<li>Cloud SQL volume mounting in the Cloud Run instance.</li>
</ul>



<p><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

from google.cloud.sql.connector import Connector, IPTypes

# ... simplified for brevity

connector = Connector(IPTypes.PRIVATE)  # use Private IP

connector.connect(
  os.getenv(&#039;DB_INSTANCE_CONNECTION_NAME&#039;),  # project:region:instance
  &#039;pymysql&#039;,
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
  db=os.getenv(&#039;DB_NAME&#039;),
)
</pre></div>


<p><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>Very concise configuration in both IaC and application code since there&#8217;s no need to manage client certificates.</li>
</ul>



<p><strong>Cons</strong></p>



<ul class="wp-block-list">
<li>Only supports Python, Java, Go, and Node.js.</li>
</ul>



<h3 class="wp-block-heading" id="option-b-managed-cloud-sql-proxy-in-cloud-run">Option B: Managed Cloud SQL Proxy in Cloud Run</h3>



<p>This solution uses a <a href="https://cloud.google.com/sql/docs/mysql/connect-run#configure" target="_blank" rel="noopener">managed Cloud SQL Proxy</a> that is configurable directly in the Cloud Run instance. Once configured, a Cloud SQL volume mount is created in the Cloud Run instance. </p>



<p><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

import pymysql

# ... simplified for brevity

pymysql.connect(
  database=os.getenv(&#039;DB_NAME&#039;),
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
  unix_socket=f&#039;/cloudsql/{os.getenv(&quot;DB_INSTANCE_CONNECTION_NAME&quot;)}&#039;,  # /cloudsql/project:region:instance
)
</pre></div>


<p><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>Very concise configuration in both IaC and application code since there&#8217;s no need to manage SSL certificates.</li>
</ul>



<p><strong>Cons</strong></p>



<ul class="wp-block-list">
<li><strong>It doesn&#8217;t work if the Cloud SQL instance has both public IP and private IP enabled</strong>. There&#8217;s no way to specify which IP to use, and Cloud SQL Proxy automatically uses the public IP if it exists.
<ul class="wp-block-list">
<li><strong>Note:</strong> If you know how to pull this off, please drop a comment below.</li>
</ul>
</li>



<li><a href="https://cloud.google.com/sql/docs/mysql/sql-proxy#production-environment" target="_blank" rel="noopener">Additional configurations</a> must be considered when running in production, which introduces many points of failure.</li>



<li>It may exceed the default <a href="https://cloud.google.com/sql/docs/mysql/connect-run#api-quota-limits" target="_blank" rel="noopener">Cloud SQL Admin API quota limit</a>.</li>
</ul>



<h3 class="wp-block-heading" id="option-c-manually-configure-cloud-sql-proxy">Option C: Manually Configure Cloud SQL Proxy</h3>



<p>This solution <a href="https://cloud.google.com/sql/docs/mysql/connect-auth-proxy" target="_blank" rel="noopener">manually configures Cloud SQL Proxy</a> via Dockerfile instead of relying on the managed Cloud SQL volume mount from Cloud Run.</p>



<p><strong>Dockerfile</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: plain; highlight: [13,14,16]; title: ; notranslate">
FROM python:3.12-alpine

RUN apk update &amp;amp;&amp;amp; apk add curl

WORKDIR /app

COPY . /app

RUN pip install --no-cache-dir -r requirements.txt

EXPOSE 8080

RUN curl -o cloud-sql-proxy https://storage.googleapis.com/cloud-sql-connectors/cloud-sql-proxy/v2.12.0/cloud-sql-proxy.linux.amd64
RUN chmod +x cloud-sql-proxy

CMD &#x5B;&quot;sh&quot;, &quot;-c&quot;, &quot;./cloud-sql-proxy --private-ip --port 3306 ${DB_INSTANCE_CONNECTION_NAME} &amp;amp; sleep 5 &amp;amp;&amp;amp; python app.py&quot;]
</pre></div>


<p><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

import pymysql

# ... simplified for brevity

pymysql.connect(
  host=os.getenv(&#039;DB_HOST&#039;),  # 127.0.0.1
  database=os.getenv(&#039;DB_NAME&#039;),
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
)
</pre></div>


<p><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>Very concise configuration in both IaC and application code since there&#8217;s no need to manage client certificates.</li>



<li>Unlike the managed Cloud SQL Proxy volume mount solution, this solution will work even if the Cloud SQL instance has both private and public IPs.</li>
</ul>



<p><strong>Cons</strong></p>



<ul class="wp-block-list">
<li>Need to manage the Cloud SQL Proxy installation and configuration in the Dockerfile.</li>



<li>All the cons described in the managed Cloud SQL Proxy solution above still apply here.</li>
</ul>



<h3 class="wp-block-heading" id="option-d-use-cloud-sq-ls-private-ip-and-client-certificate">Option D: Use Cloud SQL&#8217;s Private IP and Client Certificate</h3>



<p>Instead of relying on external tools/libraries, the application code connects directly to the Cloud SQL instance using the private IP where the client certificate is also provided.</p>



<p><strong>Python App</strong></p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: python; title: ; notranslate">
import os

import pymysql

# ... simplified for brevity

pymysql.connect(
  host=os.getenv(&#039;DB_HOST&#039;), # Cloud SQL&#039;s Private IP
  database=os.getenv(&#039;DB_NAME&#039;),
  user=os.getenv(&#039;DB_USER&#039;),
  password=os.getenv(&#039;DB_PASS&#039;),
  ssl={
    &#039;ca&#039;: os.getenv(&#039;DB_SSL_SERVER_CA&#039;),     # /app/certs/ca/server-ca.pem
    &#039;cert&#039;: os.getenv(&#039;DB_SSL_CLIENT_CERT&#039;), # /app/certs/cert/client-cert.pem
    &#039;key&#039;: os.getenv(&#039;DB_SSL_CLIENT_KEY&#039;),   # /app/certs/key/client-key.pem
    &#039;check_hostname&#039;: False,
  }
)
</pre></div>


<p><strong>Note:</strong> The hostname checking must be disabled because the database host (Cloud SQL&#8217;s Private IP) will never<br>match the common name specified in the certificate. If the hostname checking is not disabled, it will produce the following error:</p>



<blockquote class="wp-block-quote is-layout-flow wp-block-quote-is-layout-flow">
<p>Error connecting to database: (2003, &#8220;Can&#8217;t connect to MySQL server on &#8217;10.x.x.x&#8217; ([SSL: CERTIFICATE_VERIFY_FAILED]<br>certificate verify failed: IP address mismatch, certificate is not valid for &#8217;10.x.x.x&#8217;. (_ssl.c:1000))&#8221;)</p>
</blockquote>



<p><strong>Pros</strong></p>



<ul class="wp-block-list">
<li>No additional wrappers like Cloud SQL Proxy or Cloud SQL Language Connectors are required.</li>
</ul>



<p><strong>Cons</strong></p>



<ul class="wp-block-list">
<li>Additional configurations and complexity on the IaC and application code are needed to manage and ingest the client certificate securely.</li>
</ul>



<h2 class="wp-block-heading" id="recommendations">Recommendations</h2>



<h3 class="wp-block-heading" id="best-solution-option-a">Best Solution &#8211; Option A</h3>



<p>Use Cloud SQL Language Connectors if your app uses one of the supported programming languages (Python, Java, Go, or Node.js). </p>



<p>It eliminates many unnecessary configurations on the IaC code and application code. In other words, there&#8217;s no need to do the following:</p>



<ul class="wp-block-list">
<li>Create a client certificate for Cloud SQL.</li>



<li>Store server certificate, client certificate, and client key in Secret Manager securely.</li>



<li>Mount files containing the server certificate, client certificate, and client key as volumes in the Cloud Run instance.</li>



<li>Pass certificate information to the application code via environment variables. </li>
</ul>



<p>None of the above is needed. </p>



<p>Less codebase = Less chance of errors.</p>



<h3 class="wp-block-heading" id="distance-next-best-solution-option-d">Distance Next Best Solution &#8211; Option D</h3>



<p>Connect directly using the Cloud SQL instance&#8217;s Private IP and client certificate.</p>



<p>It eliminates the need to rely on additional cloud configuration or external library (i.e., fewer points of failure). </p>



<p>However, it introduces additional configurations in IaC and application code to maintain and ingest the certificate described above.</p>



<h3 class="wp-block-heading" id="least-favorite-solution-option-b-c">Least Favorite Solution &#8211; Option B &amp; C</h3>



<p>While both Cloud SQL Proxy solutions (volume mount in the Cloud Run instance or installing it directly via Dockerfile) don&#8217;t require a client certificate, they come with <a href="https://cloud.google.com/sql/docs/mysql/sql-proxy#production-environment" target="_blank" rel="noopener">additional considerations and complexities when running in production</a>. </p>



<p>Furthermore, the volume mount solution won&#8217;t work if the Cloud SQL instance has both private and public IPs. </p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/">Cloud Run: Connecting to Cloud SQL with Private IP and SSL</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2024/08/08/cloud-run-connecting-to-cloud-sql-with-private-ip-and-ssl/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">2642</post-id>	</item>
		<item>
		<title>Enabling Python VirtualEnv in JupyterLab</title>
		<link>https://myshittycode.com/2022/03/18/enabling-python-virtualenv-in-jupyterlab/</link>
					<comments>https://myshittycode.com/2022/03/18/enabling-python-virtualenv-in-jupyterlab/#respond</comments>
		
		<dc:creator><![CDATA[Shitty Author]]></dc:creator>
		<pubDate>Fri, 18 Mar 2022 21:26:42 +0000</pubDate>
				<category><![CDATA[Programming Language]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Google Cloud Platform]]></category>
		<category><![CDATA[ipython]]></category>
		<category><![CDATA[Python]]></category>
		<category><![CDATA[Vertex AI Workbench]]></category>
		<category><![CDATA[virtualenv]]></category>
		<guid isPermaLink="false">https://myshittycode.com/?p=1380</guid>

					<description><![CDATA[<p>This post illustrates how you can enable Python virtualenv in GCP JupyterLab so that you can organize your .ipynb files to use different virtual environments to keep track of Python package dependencies. PROBLEM You are using GCP JupyterLab. You want to adhere to the Python development best practices by not polluting the global environment with [&#8230;]</p>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2022/03/18/enabling-python-virtualenv-in-jupyterlab/">Enabling Python VirtualEnv in JupyterLab</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></description>
										<content:encoded><![CDATA[
<p>This post illustrates how you can enable Python virtualenv in GCP JupyterLab so that you can organize your <strong>.ipynb</strong> files to use different virtual environments to keep track of Python package dependencies.</p>



<h2 class="wp-block-heading">PROBLEM</h2>



<ul class="wp-block-list"><li>You are using GCP JupyterLab.</li><li>You want to adhere to the Python development best practices by not polluting the global environment with your Python packages so that you can generate a cleaner &#8220;pip freeze&#8221; in the future.</li><li>You want each Notebook file (.ipynb) to have its own environment so that you can run them with different package versions.</li><li>You configured a Python virtual environment, but &#8220;pip install&#8221; from the Notebook file still installs the packages in the global environment.</li><li>You are fed up.</li></ul>



<h2 class="wp-block-heading">SOLUTION</h2>



<h4 class="wp-block-heading">Configuring Virtual Environments</h4>



<p>In the JupyterLab Notebook&#8217;s terminal, create an empty directory to organize all virtual environments.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
mkdir virtualenv
</pre></div>


<p>Ensure ipykernel is installed. This is used to create new IPython kernels.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
python3 -m pip install ipykernel
</pre></div>


<p>For each new virtual environment, run the following commands to perform these steps:</p>



<ul class="wp-block-list"><li>Get into the base virtual environment directory.</li><li>Define a new virtual environment name. Replace <strong>[NEW_ENV_NAME]</strong> with a new name.</li><li>Create new Python virtual environment.<ul><li>The <strong>&#8211;system-site-packages</strong> option ensures you can still use the &#8220;data-sciencey&#8221; packages that come pre-installed with the GCP JupyterLab Notebook within your new virtual environment. </li></ul></li><li>Jump into the newly created virtual environment.</li><li>Create a new IPython kernel.</li><li>Exit from virtual environment.</li></ul>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
cd virtualenv
VENV=&#x5B;NEW_ENV_NAME] # Update this!
python3 -m venv $VENV --system-site-packages
source $VENV/bin/activate
python -m ipykernel install --user --name=$VENV
deactivate
</pre></div>


<h4 class="wp-block-heading">Configuring Notebook File</h4>



<p>Create a new Notebook file (.ipynb).</p>



<p>In <strong>Select Kernel</strong> dialog, select the kernel that you created. In this example, there are 2 new virtual environments (&#8220;smurfs&#8221; and &#8220;thundercats&#8221;).</p>



<div class="wp-block-image size-large"><figure class="aligncenter is-resized"><a href="https://myshittycode.com/wp-content/uploads/2022/03/screen-shot-2022-03-18-at-3.32.19-pm.png?x45560"><img loading="lazy" decoding="async" src="https://myshittycode.com/wp-content/uploads/2022/03/screen-shot-2022-03-18-at-3.32.19-pm.png?x45560" alt="Selecting a kernel in JupyterLab" class="wp-image-1397" width="342" height="342" srcset="https://myshittycode.com/wp-content/uploads/2022/03/screen-shot-2022-03-18-at-3.32.19-pm.png 684w, https://myshittycode.com/wp-content/uploads/2022/03/screen-shot-2022-03-18-at-3.32.19-pm-300x300.png 300w, https://myshittycode.com/wp-content/uploads/2022/03/screen-shot-2022-03-18-at-3.32.19-pm-150x150.png 150w" sizes="auto, (max-width: 342px) 100vw, 342px" /></a></figure></div>



<p>To perform a simple test, install a new package.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
%pip install pandas==1.3.0
</pre></div>


<p><strong>IMPORTANT:</strong> You need to use IPython&#8217;s <a href="https://ipython.readthedocs.io/en/stable/interactive/magics.html" target="_blank" rel="noopener">Magics</a> (literally speaking) to ensure the packages are installed in the virtual environment.</p>



<ul class="wp-block-list"><li><strong>%pip</strong> = This uses the pip package manager within the current kernel. Magic!</li><li><strong>! pip</strong> = This uses the pip package manager from the underlying OS. No magic!</li></ul>



<p>From the menu bar, select <strong>Kernel</strong> -&gt; <strong>Restart Kernel and Clear All Outputs</strong>&#8230; . Always restart the kernel when new packages are installed with <strong>%pip</strong>.</p>



<div class="wp-block-image size-large"><figure class="aligncenter is-resized"><a href="https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_41_47_pm.png?x45560"><img loading="lazy" decoding="async" src="https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_41_47_pm.png?x45560" alt="Kernel -&gt; Restart Kernel and Clear All Outputs in JupyterLab" class="wp-image-1398" width="388" height="332" srcset="https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_41_47_pm.png 776w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_41_47_pm-300x257.png 300w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_41_47_pm-768x657.png 768w" sizes="auto, (max-width: 388px) 100vw, 388px" /></a></figure></div>



<p>Inspect the package version. This should show the version you have just installed.</p>


<div class="wp-block-syntaxhighlighter-code "><pre class="brush: bash; title: ; notranslate">
import pandas
print(pandas.__version__)
</pre></div>


<p>To verify this actually works, create another Notebook file pointing to another kernel. In this file, install the same package but with different version.</p>



<figure class="wp-block-image size-large"><a href="https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2.png?x45560"><img loading="lazy" decoding="async" width="2394" height="1586" src="https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2.png?x45560" alt="Testing Python Virtualenv in JupyterLab" class="wp-image-1394" srcset="https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2.png 2394w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2-300x199.png 300w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2-1024x678.png 1024w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2-768x509.png 768w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2-1536x1018.png 1536w, https://myshittycode.com/wp-content/uploads/2022/03/screen_shot_2022-03-18_at_3_57_05_pm-2-2048x1357.png 2048w" sizes="auto, (max-width: 2394px) 100vw, 2394px" /></a></figure>
<p>The post <a rel="nofollow" href="https://myshittycode.com/2022/03/18/enabling-python-virtualenv-in-jupyterlab/">Enabling Python VirtualEnv in JupyterLab</a> appeared first on <a rel="nofollow" href="https://myshittycode.com">My Shitty Code</a>.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://myshittycode.com/2022/03/18/enabling-python-virtualenv-in-jupyterlab/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
		<post-id xmlns="com-wordpress:feed-additions:1">1380</post-id>	</item>
	</channel>
</rss>

<!--
Performance optimized by W3 Total Cache. Learn more: https://www.boldgrid.com/w3-total-cache/?utm_source=w3tc&utm_medium=footer_comment&utm_campaign=free_plugin

Page Caching using Disk: Enhanced 
Lazy Loading (feed)
Database Caching using Disk

Served from: myshittycode.com @ 2026-02-20 05:57:54 by W3 Total Cache
-->