<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: aliona matveeva</title>
    <description>The latest articles on DEV Community by aliona matveeva (@heyaliona).</description>
    <link>https://dev.to/heyaliona</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F460073%2Fdb9b9a44-2667-4960-9c3a-e707025af293.jpg</url>
      <title>DEV Community: aliona matveeva</title>
      <link>https://dev.to/heyaliona</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/heyaliona"/>
    <language>en</language>
    <item>
      <title>Send Node.js logs from Docker to Grafana Cloud with Alloy</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Fri, 02 May 2025 10:34:30 +0000</pubDate>
      <link>https://dev.to/catbytes-community/send-nodejs-logs-from-docker-to-grafana-cloud-with-alloy-54b5</link>
      <guid>https://dev.to/catbytes-community/send-nodejs-logs-from-docker-to-grafana-cloud-with-alloy-54b5</guid>
      <description>&lt;p&gt;&lt;strong&gt;&lt;em&gt;tl;dr:&lt;/em&gt;&lt;/strong&gt; alloy config, docker-compose.&lt;/p&gt;

&lt;p&gt;Any service that's meant to live more than couple of weeks eventually reaches the stage where you feel the need to properly monitor it. You usually start with simple &lt;code&gt;console.log&lt;/code&gt; logging, but soon realize it's not readable enough, it's not searchable enough and it's only available on your server. Probably inside Docker container.&lt;/p&gt;

&lt;p&gt;I was exactly at that point, annoyed by constant need to &lt;code&gt;ssh&lt;/code&gt; into my server to check one log line. I've wanted to play with Grafana ecosystem for a while, too. So it seemed to be the perfect moment.&lt;/p&gt;

&lt;p&gt;In this post, I’ll walk you through a simple and minimal setup that streams my &lt;strong&gt;Node.js&lt;/strong&gt; application's logs into the &lt;strong&gt;Grafana Cloud&lt;/strong&gt; dashboard using &lt;strong&gt;Grafana Alloy&lt;/strong&gt; and &lt;strong&gt;Loki&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This is what my final setup looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftl1fmtlnzctqlma68xwi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftl1fmtlnzctqlma68xwi.png" alt="Final setup with Grafana" width="685" height="181"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now let’s break down how to make it work.&lt;/p&gt;

&lt;h2&gt;
  
  
  Producing Logs
&lt;/h2&gt;

&lt;p&gt;I'm using &lt;code&gt;pino&lt;/code&gt; to generate logs from my service. I won't dive deep into the setup as the library outputs raw JSON to stdout by default. An example log line would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"level"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"time"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;1746117737323&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"pid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"hostname"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"96773881a0b3"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"module"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"server.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"env"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"development"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nl"&gt;"msg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="s2"&gt;"Server is running"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Docker’s default logging behavior captures all stdout and stderr from your container and writes them into a file. &lt;code&gt;json-file&lt;/code&gt; is the default logging driver in Docker, you can confirm this using:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker info --format '{{.LoggingDriver}}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also find out the actual log file location for a specific container:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;docker inspect -f '{{.LogPath}}' &amp;lt;container&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And this exactly what &lt;strong&gt;Alloy&lt;/strong&gt; will be reading and forwarding to &lt;strong&gt;Grafana Cloud&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Onboarding to Grafana Cloud and Setting Up Loki
&lt;/h2&gt;

&lt;p&gt;Before we start wiring things up locally, let's prepare the Grafana Cloud workspace. &lt;/p&gt;

&lt;p&gt;Navigate to &lt;a href="https://grafana.com/" rel="noopener noreferrer"&gt;Grafana Cloud&lt;/a&gt; and sign up or log in. In the sidebar, select &lt;strong&gt;Connections&lt;/strong&gt; → &lt;strong&gt;Add new connection&lt;/strong&gt;, select &lt;strong&gt;Loki&lt;/strong&gt;. This is the place that prompts you to set up your Loki connection and allows you to generate an access token for Alloy.&lt;/p&gt;

&lt;p&gt;In section 2, &lt;strong&gt;Install Grafana Alloy,&lt;/strong&gt; click the "Run Grafana Alloy" button to retrieve necessary information. We're interested in the token, Loki username (&lt;code&gt;GCLOUD_HOSTED_LOGS_ID&lt;/code&gt;) and Loki URL (&lt;code&gt;GCLOUD_HOSTED_LOGS_URL&lt;/code&gt;). You'll need them later to set up Alloy config.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjknafh6g376b20p4hskm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjknafh6g376b20p4hskm.png" alt="Image description" width="800" height="658"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting Up Alloy Config
&lt;/h2&gt;

&lt;p&gt;The configuration for the collector is stored in a file with &lt;code&gt;*.alloy&lt;/code&gt; extension. Let's create &lt;code&gt;config.alloy&lt;/code&gt; in the project root.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Discover&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;containers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;extract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;metadata.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;discovery.docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"linux"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unix:///var/run/docker.sock"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Extract&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;using&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;a&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;relabeling&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;rule.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;discovery.relabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"logs_integrations_docker"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;targets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;discovery.docker.linux.targets&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;rule&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;source_labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"__meta_docker_container_name"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;regex&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/(.*)"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;target_label&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"service_name"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Collect&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;from&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;containers&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;together&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;with&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;relabel&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;information&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;and&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;forward&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Loki&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;receiver.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;loki.source.docker&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"default"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;host&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"unix:///var/run/docker.sock"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;targets&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;discovery.relabel.logs_integrations_docker.output&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;labels&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="s2"&gt;"platform"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"docker"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;forward_to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="err"&gt;loki.write.cloud.receiver&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;

&lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Step&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="err"&gt;.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Send&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;logs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Grafana&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Cloud&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Loki.&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="err"&gt;loki.write&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"cloud"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="err"&gt;endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;url&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sys.env(&lt;/span&gt;&lt;span class="s2"&gt;"GRAFANA_LOKI_URL"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="err"&gt;basic_auth&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;username&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sys.env(&lt;/span&gt;&lt;span class="s2"&gt;"GRAFANA_LOKI_USERNAME"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="err"&gt;password&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sys.env(&lt;/span&gt;&lt;span class="s2"&gt;"GRAFANA_CLOUD_API_KEY"&lt;/span&gt;&lt;span class="err"&gt;)&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This config basically defines a pipeline of operations Alloy performs to &lt;em&gt;collect&lt;/em&gt;, &lt;em&gt;transform&lt;/em&gt; and &lt;em&gt;deliver&lt;/em&gt; the data. In our case, we want to send logs from Docker to &lt;strong&gt;Grafana Cloud Loki&lt;/strong&gt;. To do that, we'll pass the credentials you got earlier to config - using environment variables for safety.&lt;/p&gt;

&lt;p&gt;💡 &lt;strong&gt;&lt;em&gt;Tip:&lt;/em&gt;&lt;/strong&gt; Grafana has a &lt;a href="https://github.com/grafana/alloy-scenarios/tree/main" rel="noopener noreferrer"&gt;great Alloy scenarios repo&lt;/a&gt; with examples for different setups and logs sources. It helped me a lot in understanding how the pieces fit together.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting It All Together in Docker Compose
&lt;/h2&gt;

&lt;p&gt;Since I’m already using docker-compose to run my service, integrating &lt;strong&gt;Alloy&lt;/strong&gt; was as simple as adding another service definition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;services&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;app&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;.&lt;/span&gt;
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;NODE_ENV&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${NODE_ENV}&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;8080:8080'&lt;/span&gt;

    &lt;span class="na"&gt;alloy&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
        &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;grafana/alloy:latest&lt;/span&gt;
        &lt;span class="na"&gt;container_name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;alloy&lt;/span&gt;
        &lt;span class="na"&gt;ports&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;12345:12345'&lt;/span&gt;
        &lt;span class="na"&gt;volumes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# mount config file&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;./config.alloy:/etc/alloy/config.alloy'&lt;/span&gt; 
            &lt;span class="c1"&gt;# give access to running docker containers for discovery.docker&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/run/docker.sock:/var/run/docker.sock&lt;/span&gt; 
            &lt;span class="c1"&gt;# give access to docker's log files directory (optional)&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/var/lib/docker/containers:/var/lib/docker/containers:ro&lt;/span&gt; 
        &lt;span class="na"&gt;environment&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="c1"&gt;# pass environment variables for config.alloy&lt;/span&gt;
            &lt;span class="na"&gt;GRAFANA_LOKI_URL&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GRAFANA_LOKI_URL}&lt;/span&gt;
            &lt;span class="na"&gt;GRAFANA_LOKI_USERNAME&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GRAFANA_LOKI_USERNAME}&lt;/span&gt;
            &lt;span class="na"&gt;GRAFANA_CLOUD_API_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${GRAFANA_CLOUD_API_KEY}&lt;/span&gt;
        &lt;span class="na"&gt;command&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;run&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--server.http.listen-addr=0.0.0.0:12345&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;--storage.path=/var/lib/alloy/data&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;/etc/alloy/config.alloy&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Grafana Cloud credentials might be stored in a &lt;code&gt;.env&lt;/code&gt; file on your server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Run and Verify
&lt;/h2&gt;

&lt;p&gt;Let's build and run the containers using &lt;code&gt;docker-compose up -d&lt;/code&gt; and verify everything is up by looking at the &lt;code&gt;docker ps&lt;/code&gt; output:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh553g0lonbcvwei4yg8s.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fh553g0lonbcvwei4yg8s.png" alt="Image description" width="800" height="39"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If both containers are up and healthy and there are no errors in logs, you can proceed to the Grafana Portal to verify the updates are reaching the Cloud. &lt;/p&gt;

&lt;p&gt;Navigate to &lt;strong&gt;Explore&lt;/strong&gt;, select your data source (&lt;strong&gt;Loki&lt;/strong&gt;) and apply filters. The &lt;code&gt;service_name&lt;/code&gt; filter label comes from the &lt;em&gt;relabelling&lt;/em&gt; step in the Alloy pipeline, so now you can easily search logs for your specific service.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wu7vv1rmpvw6aepez8v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6wu7vv1rmpvw6aepez8v.png" alt="Image description" width="800" height="341"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And just like that - the logs are flowing! You can play more with additional filtering and processing, for now I just added a basic JSON parsing for more readable output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6xsh5gmf795dudgr0h7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe6xsh5gmf795dudgr0h7.png" alt="Image description" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What’s Next?
&lt;/h2&gt;

&lt;p&gt;I’m still getting familiar with &lt;strong&gt;Grafana Dashboards&lt;/strong&gt;, so there’s more to explore. In particular:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Pino logs use numeric levels (e.g. 30, 40), which aren’t parsed as human-readable levels by default in Grafana;&lt;/li&gt;
&lt;li&gt;I’d love to build some fancy filters and dashboards to better visualize logs;&lt;/li&gt;
&lt;li&gt;I'm eager to try pino-http logging and see how it can be visualized in Grafana.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These will probably be another blog post(s), so stay tuned :) Thanks for reading and I'd love to hear any suggestions or comments you may have! &lt;/p&gt;

</description>
      <category>grafana</category>
      <category>logging</category>
      <category>docker</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>Hello World, We Are CatBytes</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Fri, 02 May 2025 10:10:26 +0000</pubDate>
      <link>https://dev.to/catbytes-community/hello-world-we-are-catbytes-18o1</link>
      <guid>https://dev.to/catbytes-community/hello-world-we-are-catbytes-18o1</guid>
      <description>&lt;p&gt;Hi everyone! We’re are the &lt;strong&gt;CatBytes Community&lt;/strong&gt; and we're excited to officially launch our presence here on DEV!&lt;/p&gt;

&lt;h3&gt;
  
  
  🐾 Who we are
&lt;/h3&gt;

&lt;p&gt;We're an online community built &lt;strong&gt;by women in tech, for women in tech&lt;/strong&gt;. CatBytes is all about creating a safe and genuinely supportive digital space for women who want to break into the tech world — whether you're just starting out or leveling up your skills.&lt;/p&gt;

&lt;p&gt;Our mission is simple: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;get more women into tech and lift each other up along the way. 🚀&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;We run free weekly web development lessons, collaborate on real projects, and we’re lucky to have some amazing mentors — experienced software engineers and tech leads — guiding and cheering everyone on.&lt;/p&gt;

&lt;h3&gt;
  
  
  💻 What to expect
&lt;/h3&gt;

&lt;p&gt;Here on DEV, we are looking to &lt;strong&gt;learn, share, and connect&lt;/strong&gt;. So let's do that! 💜 &lt;/p&gt;

&lt;p&gt;Our first post is coming soon! We'll talk about the monitoring setup we're using for our community platform — streaming logs from a Dockerized Node.js app to Grafana Cloud using Alloy. &lt;/p&gt;

&lt;p&gt;More posts, insights, and stories to come. 🐱💬&lt;/p&gt;




&lt;p&gt;We’re just getting started here, so if this sounds interesting — follow along!&lt;br&gt;
Have questions, feedback, or just want to say hi? Drop a comment below — we’d love to hear from you.&lt;/p&gt;

&lt;p&gt;Thanks for stopping by — and we’re happy to be here!&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fw3s2xq3l2pb26y1qp7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4fw3s2xq3l2pb26y1qp7.png" alt="Image description" width="800" height="486"&gt;&lt;/a&gt;&lt;/p&gt;

</description>
      <category>welcome</category>
      <category>community</category>
      <category>womenintech</category>
      <category>webdev</category>
    </item>
    <item>
      <title>I Built a Bot to Chat with Our Team’s Wiki Using Azure OpenAI Service</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Wed, 16 Apr 2025 08:25:00 +0000</pubDate>
      <link>https://dev.to/heyaliona/i-built-a-bot-to-chat-with-our-teams-wiki-using-azure-openai-service-44m6</link>
      <guid>https://dev.to/heyaliona/i-built-a-bot-to-chat-with-our-teams-wiki-using-azure-openai-service-44m6</guid>
      <description>&lt;p&gt;🛠️ &lt;strong&gt;TL;DR:&lt;/strong&gt; I used Azure OpenAI on Your Data + Teams AI Library + a bit of Python to build a bot that chats with our internal wiki.&lt;br&gt;
It fetches content from Azure DevOps Wiki, pushes it to blob storage, indexes it with Azure AI Search, and answers questions — all without bothering my teammates 😄&lt;/p&gt;



&lt;p&gt;My team’s project has been around for many, many years. That means the internal wiki is huge, and while it holds a lot of useful information, it’s really hard to search. Especially if you don’t know exactly what you’re looking for.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Disclaimer: The views expressed in this article are my own and do not necessarily reflect those of Microsoft. I am a Microsoft employee, and this article is based on my personal experience.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjxqbnbeub1ek5b8i66i.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnjxqbnbeub1ek5b8i66i.webp" alt="header image with text " width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;So usually my interaction with team’s wiki would look something like this: I have a question and I ask some of my senior colleagues → I’m waiting for an answer → The answer is a link to a wiki page. :D&lt;/p&gt;

&lt;p&gt;Which is kind of ridiculous, because&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I &lt;em&gt;could&lt;/em&gt; find an answer myself, it was already documented, but I failed to search;&lt;/li&gt;
&lt;li&gt;Waiting for a reply might take hours​, if the colleague is busy or away;&lt;/li&gt;
&lt;li&gt;Distracting teammates breaks their focus.​&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Besides&lt;/em&gt;, what is the point of a Wiki if you still need someone else to find the right page for you?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;So I thought it would be fun to be able to ask a bot first to search the wiki more effectively and get quicker results.&lt;/p&gt;

&lt;p&gt;Luckily, in 2024 Microsoft announced something called &lt;strong&gt;Azure Open AI on Your Data&lt;/strong&gt; — a set of Azure services that “enables you to run advanced AI models such as GPT-35-Turbo and GPT-4 on your own enterprise data without needing to train or fine-tune models” (according to &lt;a href="https://learn.microsoft.com/en-us/azure/ai-services/openai/concepts/use-your-data" rel="noopener noreferrer"&gt;official documentation&lt;/a&gt;). Sounds about right. Let’s experiment!​&lt;/p&gt;
&lt;h2&gt;
  
  
  Getting started: Going over the sample tutorial
&lt;/h2&gt;

&lt;p&gt;Microsoft offers a wide set of tools to support your AI development. There’s the &lt;strong&gt;Teams AI Library&lt;/strong&gt; (available in C#, JS and Python) — an official SDK for integrating GPT-based language models with custom Teams apps, and &lt;strong&gt;Teams Toolkit&lt;/strong&gt; — a Visual Studio Code extension that simplifies creating, developing and testing those apps.&lt;/p&gt;

&lt;p&gt;I started with &lt;a href="https://learn.microsoft.com/en-us/microsoftteams/platform/teams-ai-library-tutorial" rel="noopener noreferrer"&gt;this simple tutorial&lt;/a&gt; that teaches us how to create a custom engine agent in Teams.&lt;/p&gt;

&lt;p&gt;Basically, it helps you:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Set up your data as the knowledge source for the GPT model&lt;/li&gt;
&lt;li&gt;Create a bot and connect it to your Azure resources&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I began by creating a new resource group and an Azure Open AI Service instance inside of my Azure subscription, then proceeded with the guide.&lt;/p&gt;

&lt;p&gt;If you’re going with this tutorial, it guides you through a very convenient set of preconfigured interfaces. It automatically creates:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Blob Storage for your files&lt;/li&gt;
&lt;li&gt;An Azure AI Search instance with an index&lt;/li&gt;
&lt;li&gt;Then it indexes your uploaded files (i.e. prepares them for search). Note that you can only upload and index your files once if you’re going by the manual.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once the resources are ready, you’ll switch to Visual Studio Code and set up a simple Teams bot application boilerplate where you will only need to populate .env file with your Azure resources information.&lt;/p&gt;

&lt;p&gt;We end up with this setup:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tkx4elt19be5jguixn6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4tkx4elt19be5jguixn6.png" alt="Basic Azure resources setup for Teams bot running locally." width="661" height="530"&gt;&lt;/a&gt;&lt;br&gt;
At this point you can already try running the code in Teams TestTool. If you did everything right, the chat will load in a browser window and you will be able to have a tiny conversation with your data. Yay!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yucsv9nu0snll0rrq5a.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2yucsv9nu0snll0rrq5a.png" alt="Image description" width="800" height="448"&gt;&lt;/a&gt;&lt;em&gt;Fed with couple of Wiki pages, the model now can respond to questions with a context specific to the team.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Making it dynamic: Adding an Indexer to Azure AI Search
&lt;/h2&gt;

&lt;p&gt;As I mentioned earlier, when you set up your data source through the Azure AI Foundry portal interface, it indexes the data you upload to the blob once at the set up. But what if the data has changed? For something like a team’s wiki, the data is never static, it constantly updates, and we need the bot to keep up. This is where we need an &lt;strong&gt;Indexer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;An &lt;strong&gt;Indexer&lt;/strong&gt; connects your &lt;strong&gt;data source&lt;/strong&gt; to the &lt;strong&gt;index&lt;/strong&gt;: all three are parts of Azure AI Search. The indexer is a crawler that processes textual data in the source files and populates the search index.&lt;/p&gt;

&lt;p&gt;The indexer is easily added via Azure Portal. Before doing so, first we’ll need to create a data source — basically a “connection” to the desired storage, Azure Blob Container in my case. Then select the index that was generated at the previous step. Finally, configure the indexer — it can run on a schedule or be triggered manually; I’m keeping it manual for now.&lt;/p&gt;

&lt;p&gt;Once your indexer is in place, you can upload new files to the blob storage and then hit the “Run” button to re-index. The fresh data will be loaded into the search index and instantly available to the bot.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7b0tp9flw1msh996ryr.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7b0tp9flw1msh996ryr.png" alt="Image description" width="456" height="452"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Automating it: Parsing data from Azure DevOps Wiki
&lt;/h2&gt;

&lt;p&gt;Our team’s wiki has couple of hundreds notes so uploading it manually is not an option. And then of course there’s a need to re-upload the blob container when articles change and new notes pop up. Let’s write a tiny Python script to do the job.&lt;/p&gt;

&lt;p&gt;Let’s break down what we want the script to be doing and go step by step.&lt;/p&gt;
&lt;h3&gt;
  
  
  Get access to Azure DevOps Wiki and download article contents
&lt;/h3&gt;

&lt;p&gt;To get machine access to Azure DevOps, you can either use a Personal Access Token (PAT) generated in your account settings, or a Managed Identity that already has access. Obviously, I would prefer Managed Identity, but for some reason it wouldn’t work specifically with ADO. After hours of failed attempts I gave up and decided to use PAT stored in Azure KeyVault, which, to my surprise, was available to connect using Managed Identity.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;credential&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AzureCliCredential&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;secret_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SecretClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vault_url&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;keyvault_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.vault.azure.net&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;credential&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# this token will later be passed as an Authorization header with API requests
&lt;/span&gt;&lt;span class="n"&gt;pat&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secret_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;AzureDevOps-PAT&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While Azure DevOps does have a Python SDK, the coverage is not optimal, so I went with raw API calls using &lt;code&gt;requests&lt;/code&gt; library.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# if recursionLevel=Full is passed to the request, it will return all SubPages' relative paths
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.azure.com/{ado_org}/{ado_project}/_apis/wiki/wikis/{ado_wiki_name}/pages?path={root_wiki_path}&amp;amp;includeContent=true&amp;amp;recursionLevel=Full&amp;amp;api-version=7.1-preview.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Authorization&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Basic &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;encoded_pat&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;# a helper function to extract all page paths
&lt;/span&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_all_pages_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;root_page&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;list&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="n"&gt;pages_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;root_page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;path&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;root_page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;subPages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]):&lt;/span&gt;
        &lt;span class="n"&gt;pages_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;extend&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;get_all_pages_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;pages_result&lt;/span&gt;

&lt;span class="c1"&gt;# get list of all available wiki pages paths
&lt;/span&gt;&lt;span class="n"&gt;pages_paths&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;get_all_pages_paths&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every subpage has a relative path you can plug into the wiki URL’s path query parameter, like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;subpage_path&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;pages_paths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# no need for recursionLevel parameter as we only need to load the page and its contents
&lt;/span&gt;  &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://dev.azure.com/{ado_org}/{ado_project}/_apis/wiki/wikis/{ado_wiki_name}/pages?path={subpage_path}&amp;amp;includeContent=true&amp;amp;api-version=7.1-preview.1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;page_response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c1"&gt;# some markdown content
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Load Wiki files to Blob Container
&lt;/h3&gt;

&lt;p&gt;To access blob storage, I used &lt;code&gt;azure-storage-blob&lt;/code&gt; lib. The SDK provides a very simple interface for connecting to a Blob Container and uploading data into it. Nice thing is that we don't even need to store files locally, and can use content loaded from Wiki API directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;blob_connection_string&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;secret_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;BlobConnectionString&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;
&lt;span class="n"&gt;blob_service_client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;BlobServiceClient&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_connection_string&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;blob_connection_string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# after connecting to a blob storage, file upload is done easily
&lt;/span&gt;&lt;span class="n"&gt;blob_service_client&lt;/span&gt; \
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_blob_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;container&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;blob_container&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;blob&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; \
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;upload_blob&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;overwrite&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since the bot is supposed to help people navigate the wiki, I decided to also store original wiki page’s URL in the file metadata. This metadata can later be reused in model’s response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Trigger Search Indexer re-run
&lt;/h3&gt;

&lt;p&gt;After all the files are uploaded to blob container, we need to re-index the data so the latest changes become available to the GPT model for search. This can be done with a simple API call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;indexer_run_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;search_service_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;.search.windows.net/indexers/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;indexer_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/run?api-version=2023-07-01-Preview&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;indexer_run_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api-key&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;secret_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_secret&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;IndexerAdminKey&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Content-Length&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After putting all this code together, we get a simple script that will download all of our wiki contents into Azure Blob Storage and then re-trigger Search Indexer to update the index. In production system, I would deploy this code as an Azure Functions App that would run on a schedule — so updates happen automatically. That only requires installing &lt;code&gt;azure-functions&lt;/code&gt; lib and slightly adjusting the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Customize The Search: Index custom blob metadata
&lt;/h2&gt;

&lt;p&gt;By default, the indexer processes blob’s content and some standard metadata like the filename or filepath. But if we want to include some custom metadata — in our case, &lt;code&gt;wikiPageLink&lt;/code&gt; that stores original wiki page URL — we will need to make some adjustments to both the indexer and the index schemas.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmnkyqot1ua8rtaomdze.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsmnkyqot1ua8rtaomdze.png" alt="Image description" width="800" height="528"&gt;&lt;/a&gt;&lt;em&gt;Adding a fieldMapping to the Indexer so it knows to process our custom metadata.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;In the Search Indexer, let’s add a new entry into the &lt;code&gt;fieldMappings&lt;/code&gt; array: &lt;code&gt;metadata_storage_wikiPageLink&lt;/code&gt; (where &lt;strong&gt;metadata_storage_&lt;/strong&gt; is the default prefix for storage files metadata) value will be mapped to a &lt;code&gt;wikiPageLink&lt;/code&gt; field in the index. Note that we also need to apply a &lt;code&gt;mappingFunction&lt;/code&gt; because custom metadata is &lt;strong&gt;base64 encoded&lt;/strong&gt; by default.&lt;/p&gt;

&lt;p&gt;Next, we’ll update the Search Index to include the very same field and make it &lt;em&gt;searchable&lt;/em&gt; and &lt;em&gt;retrievable&lt;/em&gt;. This will return &lt;code&gt;wikiPageLink&lt;/code&gt; value in a search result. The final touch — we need to let the model know it should actually use the value. I believe there are some default settings that just teach the GPT model to process content of the search result. To make it aware of additional data, we can update the base prompt.&lt;/p&gt;

&lt;p&gt;The default prompt I used was: &lt;strong&gt;“You are an AI assistant that helps engineers find information about their work and specifically project in their internal wiki.”&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Now I will add additonal instructions: &lt;strong&gt;“In the end of your response, please tell explicitly “You can read more about this in the wiki page…” and add wikiPageLink URL value of index used for generating the response (if available). Add multiple urls if used multiple indexes and adjust the message accordingly.”&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s try to run it. Nice!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqxs28q66wzq7i3glpo5.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdqxs28q66wzq7i3glpo5.png" alt="Image description" width="800" height="205"&gt;&lt;/a&gt;&lt;em&gt;The bot response now includes a link to Azure DevOps wiki page.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;It is worth noting that though at first glance it &lt;em&gt;looks&lt;/em&gt; good, unfortunately at some point I realized the model was hallucinating — returning incorrect links or even making up unexisting URLs. This is because by default the custom metadata fields aren’t processed in the default sample boilerplate setup. And unfortunately, the Teams AI Library used in this example doesn’t currently allow you to customize the search results before passing them to the model.&lt;/p&gt;

&lt;p&gt;As a temporary solution, I updated my script to include the original wiki URL directly into the blob content, however it is still not performing very well. Going forward, I think I’ll need to rewrite the bot to use direct API calls or explore other approaches that give me more control for application customization.&lt;/p&gt;

&lt;p&gt;For now, though — this is just a clear area for improvement in an otherwise promising setup.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Touch: Deploying the Bot
&lt;/h2&gt;

&lt;p&gt;Since this is still a new and rapidly evolving area, I found this part of the journey particularly challenging to navigate. Azure’s ecosystem for AI development is growing fast — and constantly changing. There are multiple libraries (like &lt;code&gt;botframework&lt;/code&gt; and the new &lt;code&gt;Teams AI Library&lt;/code&gt;), and a ton of tutorials and docs, some of which are already outdated.&lt;/p&gt;

&lt;p&gt;I spent quite some time just trying to figure out how to properly deploy a bot that runs fine locally — and make it actually available in Teams for my colleagues.&lt;/p&gt;

&lt;p&gt;In the end, I once again relied on some predefined functionality from the Teams Toolkit (using &lt;a href="https://learn.microsoft.com/en-us/microsoftteams/platform/sbs-teams-ai" rel="noopener noreferrer"&gt;this tutorial&lt;/a&gt;), which helped generate a setup that looked something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxbkyixxkcce0m16glhi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxbkyixxkcce0m16glhi.png" alt="Image description" width="563" height="361"&gt;&lt;/a&gt;&lt;br&gt;
Here,&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;App Service&lt;/strong&gt; — Hosts and runs your bot&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;App Service Plan&lt;/strong&gt; — Provides compute resources (CPU/memory)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Azure Bot&lt;/strong&gt; — Registers the bot with the Bot Framework and connects it to Teams&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage Account&lt;/strong&gt; (optional) — Supports deployment logs, state storage, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once deployed, the Azure Bot comes with a built-in Web Chat interface, so you don’t even need to upload it to Teams right away to test it. Very convenient.&lt;/p&gt;

&lt;p&gt;However, I realized quickly enough that the the Web Chat is open to the public internet. Meaning… if I leave the setup as-is, &lt;em&gt;anyone&lt;/em&gt; could talk to my internal wiki bot. Not great.&lt;/p&gt;

&lt;p&gt;To properly secure access and make the bot truly internal-only, I’d need to configure an Azure VNet (Virtual Network), since exposing sensitive data through a public endpoint is clearly not acceptable. To be honest, I haven’t gone down that rabbit hole yet — and I have a feeling it would be a whole separate article. I’ll get to it. Sooner or later.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges
&lt;/h2&gt;

&lt;p&gt;Alright, the cherry on top of this cake — here are a few issues I ran into while building the bot with Python. Hopefully, this saves you some time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Poor SDK coverage&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​Azure DevOps SDK didn’t support some of the operations I needed​, in particular loading wiki pages contents. This wasn’t big of an issue since I only needed a few endpoints, but it did make the code a bit messy: I had to mix SDK usage in some places with raw API calls in others.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Problems with&lt;/strong&gt; &lt;code&gt;TokenCredential&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As mentioned earlier, I couldn’t get Managed Identity to access Azure DevOps. While manually generating a token with &lt;code&gt;az cli&lt;/code&gt; worked fine, using &lt;code&gt;TokenCredential&lt;/code&gt; didn’t pick up the correct token automatically for some reason, so I ended up using PAT, which is not really the way to go on a long run. Here is some &lt;a href="https://github.com/Azure/azure-sdk-for-python/issues/21718" rel="noopener noreferrer"&gt;reference&lt;/a&gt; for the problem.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Python support in Azure Functions (free tier)&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;​The cheapest Azure Functions plan doesn’t support Python out of the box. Not a huge issue for this project, but something to keep in mind for anything more serious.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This was a fun little adventure — and there’s still a lot to explore. I love how AI services like this are becoming more available to the public audience helping to make life a bit easier. If you’ve tried something similar or have any thoughts about my setup, I’d love feedback!&lt;/p&gt;




&lt;p&gt;Did you find this post helpful? Hit like and follow to read more of them later :)&lt;/p&gt;

&lt;p&gt;​&lt;/p&gt;

</description>
      <category>azure</category>
      <category>openai</category>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Guide to Debugging Python code (and why you should learn it)</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Tue, 15 Aug 2023 14:45:00 +0000</pubDate>
      <link>https://dev.to/heyaliona/a-guide-to-debugging-python-code-and-why-you-should-learn-it-403n</link>
      <guid>https://dev.to/heyaliona/a-guide-to-debugging-python-code-and-why-you-should-learn-it-403n</guid>
      <description>&lt;p&gt;Any time you start writing code for your application, everything works from the first try, you never encounter exceptions and errors, users are happy and so are you. This would be wonderful, wouldn’t it? Unfortunately it’s only possible in a dream. The reality is that your code fails. It raises exceptions, it produces unexpected outcomes, it makes users unhappy.&lt;/p&gt;

&lt;p&gt;This is why debugging skill is essential in a developer’s tool kit. Debugging is a process of determining and fixing errors in your code. And while basic errors can be spotted by eye in simpler code, as the application is getting more complicated, we need tools to help. This article discusses the importance of these tools and why you need to master them right now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Who the f… is the debugging?
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29ha26hegp7jwekxh138.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F29ha26hegp7jwekxh138.png" alt="Image description" width="754" height="498"&gt;&lt;/a&gt;&lt;br&gt;
Application issues generally fall into two categories: raising exceptions and silently doing things wrong. Sometimes, exceptions are clear, revealing mistakes immediately. Yet, for complex cases the answer is not on the surface, be it a weird exception thrown or an absolutely unexpected result of a program run encountered. And this is where we need to debug.&lt;/p&gt;

&lt;p&gt;There are several options for fixing your code. The “easiest” way is to chaotically change different parts of it, hoping for the best. Sometimes it helps, but to be honest, this one is too inefficient and will probably waste a ton of your time.&lt;/p&gt;

&lt;p&gt;To efficiently make changes, understanding the problem and, hence, the possible source of it, is the key. We begin this with studying the traceback. Here, when you have understood the problem you might start staring at your code, trying to think the solution out. Maybe you will add print()’s here and there, and maybe they will even give you a clue. This approach is legit when your application is simple, but, surprisingly, many developers stick to it even after years of working, spending more and more time with the code getting more complex.&lt;/p&gt;

&lt;p&gt;The third option is on the ‘evolutionary’ top of the previous two: the proper debugging as a union of tools, approaches and experience.&lt;/p&gt;
&lt;h2&gt;
  
  
  The goal of debugging and types of tools
&lt;/h2&gt;

&lt;p&gt;The ultimate goal of debugging is to discover the issue and figure out how each line of code affects the corrupted flow, how each value changes as the program keeps running. After this the only thing that’s left is implement a better solution that covers the edge cases.&lt;/p&gt;

&lt;p&gt;Debugging involves setting breakpoints, stepping through code and inspecting values. Existing debugging tools can be split into three main types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;standalone UI applications — for example, PuDB, Winpdb. These are applications designed specifically for debugging, and the UI is basically the same as in IDE, which then, in my opinion, is not worth wasting your time. However, you can always learn more about it by yourself if you’re interested;&lt;/li&gt;
&lt;li&gt;code-in tools, where you set breakpoints by adding temporary code to your program;&lt;/li&gt;
&lt;li&gt;IDEs. All of the popular modern development environments, such as PyCharm, Visual Studio, PyDev and many more, have integrated debugging tools, allowing you to run and inspect the code at the same time.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now let’s talk a bit more about the last two points.&lt;/p&gt;
&lt;h2&gt;
  
  
  An example
&lt;/h2&gt;

&lt;p&gt;To explain how to debug in action, I’ll show you the process with the example code below.&lt;/p&gt;

&lt;p&gt;Let’s say you’re creating an application to work with some external weather API. You build a pydantic model to load the API response in it to use the data afterwards (for example, print it to the console). Take a look at the example. This code doesn’t work.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from pydantic import BaseModel

class ApiResponse(BaseModel):
    city_id: int
    temperature: float
    weather: str

# we don’t see the contents of this method, pretend this is what an actual HTTP API call returns
def fake_weather_api_request(city: str):
    cities = {"New York": {"id": 42, "temperature": 25.6, "weather_type": "sunny"},
              "Marrakech": {"id": 12, "temperature": 30}}
    return cities.get(city)

if __name__ == "__main__":
    weather_resp = fake_weather_api_request("New York")
    ny_weather = ApiResponse(**weather_resp)
    print(f"Weather in New York (city id={ny_weather.city_id}): \n" +
          f"Temperature: {ny_weather.temperature} \n" +
          f"Weather type: {ny_weather.weather}")

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you run it, you will see the following exception message:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegp9z06xyfxg7pk007rw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fegp9z06xyfxg7pk007rw.png" alt="Image description" width="800" height="187"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Oh. What would that mean? If we’re going the easy way and trying to guess the problem, we might think — probably, the following fields don’t come with a response for certain cities. Let’s make them nullable!&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ApiResponse(BaseModel):
    city_id: int = None
    temperature: float = None
    weather: str = None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Awesome. It’s not failing anymore.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cuweb0dhn9dtds4z975.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2cuweb0dhn9dtds4z975.png" alt="Image description" width="520" height="90"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Or is it? What if we know for sure the API has both city id and weather type info for New York? Why is it None then? We could continue playing a guessing game, but this is actually the perfect moment to start debugging.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Python Debugger
&lt;/h2&gt;

&lt;p&gt;Python provides you with an awesome built-in code debugger: the *&lt;em&gt;pdb *&lt;/em&gt; module. This is an easy to use interactive Python code debugger. In Python versions prior to 3.6, we would need to import the module in the beginning of the file to use the debugger. In modern Python, though, even that is not necessary anymore — simply putting the built-in &lt;code&gt;breakpoint()&lt;/code&gt; function call whenever you want your program to pause will work.&lt;/p&gt;

&lt;p&gt;Let’s set a breakpoint before the API call.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if __name__ == "__main__":
    breakpoint()
    weather_resp = fake_weather_api_request("New York")
    ny_weather = ApiResponse(**weather_resp)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now you can run the application from the terminal:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0ep9piq6ajdipy4jsm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv0ep9piq6ajdipy4jsm0.png" alt="Image description" width="800" height="74"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The application execution has paused at the place where you set the breakpoint, and the pdb is now awaiting for your command input. Type “&lt;strong&gt;h&lt;/strong&gt;” to view the list of available commands:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgqv48heu9q3ibb3j9fm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fcgqv48heu9q3ibb3j9fm.png" alt="Image description" width="800" height="364"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Now you can navigate over your code, print variables values at different stages of program execution, step into methods to see what’s happening inside, etc. Let’s step over the API call by using the command &lt;strong&gt;n&lt;/strong&gt; (next) and then see what was the actual response body with &lt;strong&gt;p&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rqj63jnqgcn1qdzx7g4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1rqj63jnqgcn1qdzx7g4.png" alt="Image description" width="800" height="127"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Okay, it turns out, the response does actually contain all three fields, but why are some of them displayed as &lt;code&gt;None&lt;/code&gt; when printing it out? If you look closely at the &lt;code&gt;ApiResponse&lt;/code&gt; model defined in the beginning of the file, you will see that some of the fields names are slightly incorrect: &lt;strong&gt;city_id&lt;/strong&gt; instead of &lt;strong&gt;id&lt;/strong&gt;, and &lt;strong&gt;weather&lt;/strong&gt; instead of &lt;strong&gt;weather_type&lt;/strong&gt;. And the actual API response indeed doesn’t contain these fields. Let’s fix this.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;class ApiResponse(BaseModel):
    id: int = None
    temperature: float = None
    weather_type: str = None
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And don’t forget to change field names in the print statement and remove/comment the &lt;code&gt;breakpoint()&lt;/code&gt; call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if __name__ == "__main__":
    #breakpoint()
    weather_resp = fake_weather_api_request("New York")
    ny_weather = ApiResponse(**weather_resp)
    print(f"Weather in New York (city id={ny_weather.id}): \n" +
          f"Temperature: {ny_weather.temperature} \n" +
          f"Weather type: {ny_weather.weather_type}")
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After you run the code again — yay, it works!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foolt3wxhljovy3nan5kq.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Foolt3wxhljovy3nan5kq.png" alt="Image description" width="800" height="63"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congratulations, you’ve just successfully finished your first round of debugging. :)&lt;/p&gt;

&lt;h2&gt;
  
  
  Debugging in IDEs
&lt;/h2&gt;

&lt;p&gt;An IDE (integrated development environment) helps you develop software efficiently. It combines all of the main functions needed for software development — code editing, building, running, testing and, finally, debugging. IDEs make debugging considerably easier so unless you’re a rigid fan of a terminal, I would recommend trying debugging in an IDE.&lt;/p&gt;

&lt;p&gt;Most IDEs operate on similar principles and it doesn’t make sense to review all of them. So I will show you a few examples in PyCharm, my preferred IDE, but you can easily do the same in any other development environment of your choice.&lt;/p&gt;

&lt;p&gt;Let’s open the same program code in PyCharm. To set a breakpoint, you need to click on the left of the line you want to pause at.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed7xyx99iac2hjtpk6w6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fed7xyx99iac2hjtpk6w6.png" alt="Image description" width="800" height="479"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This will stop the program executing after receiving the API response. To see it in action, hit the green bug button in the upper right corner of the window.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2ehf1kbw6qqu9om0qg0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd2ehf1kbw6qqu9om0qg0.png" alt="Image description" width="764" height="190"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the application execution flow will reach the marked line of code, PyCharm will open a debugging sub-window containing useful things.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60q7zigtgj6udnpyobm0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F60q7zigtgj6udnpyobm0.png" alt="Image description" width="800" height="226"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the picture above, (1) is the list of currently defined variables and their values. Each variable can be expanded and deeply examined on all levels. This is extremely useful when the variable value is a complex object with nested values of different classes. (2) is the latest received/changed value. This is helpful for fast checking, if the value is simple as a dictionary like in our case or a string. And last, but not the least, at (3) you can see the toolbox for stepping through the code. Their description together with some additional documentation on debugging in PyCharm can be found on the official website: &lt;a href="https://www.jetbrains.com/pycharm/features/debugger.html" rel="noopener noreferrer"&gt;https://www.jetbrains.com/pycharm/features/debugger.html&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I, however, would like to pay extra attention to the tiny calculator button next to all the stepping buttons. This button is called “Evaluate expression” and the name is self-explanatory. This will save you a lot of time when you want to see different outcomes of an expression without changing and re-running your code. In our weather API example we could use the tool to see what might be the response for a city other than New York.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ipm02xcizbycvwxk2pn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3ipm02xcizbycvwxk2pn.png" alt="Image description" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Voila, we can see that for some cities, for example, Marrakech, the weather_type attribute is missing. We could also try loading the response into the ApiResponse model right away and see how it would behave.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rlsma2ap6xfetbe5mjd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8rlsma2ap6xfetbe5mjd.png" alt="Image description" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Turns out, for Marrakech the &lt;code&gt;weather_type&lt;/code&gt; field will be &lt;code&gt;None&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If you try to evaluate an incorrect expression, PyCharm will show you an error. But this error is only visible within the “Evaluate” window and won’t break the program flow. After you’ve checked everything you wanted, you can close the window and continue stepping through the code just like before trying out those things.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ytefhkvdes6gbx1ytuu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2ytefhkvdes6gbx1ytuu.png" alt="Image description" width="800" height="599"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;The debugging skill is only gained through experience, but once you embrace it, you’ll be surprised you managed without it for so long. Making mistakes is absolutely natural, and writing and writing bug-free code is nearly impossible (if you code more than a couple of lines a year). What truly matters is your ability to investigate the problem and fix the issue.&lt;/p&gt;




&lt;p&gt;Did you find this post helpful? Hit like and follow to read more of them later :)&lt;/p&gt;

</description>
      <category>python</category>
      <category>debugging</category>
      <category>programming</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Telegram Conversation Summarizer Bot with ChatGPT and Flask (Quart)</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Fri, 21 Apr 2023 12:00:00 +0000</pubDate>
      <link>https://dev.to/hyperskill/telegram-conversation-summarizer-bot-with-chatgpt-and-flask-quart-3dga</link>
      <guid>https://dev.to/hyperskill/telegram-conversation-summarizer-bot-with-chatgpt-and-flask-quart-3dga</guid>
      <description>&lt;p&gt;Everybody talks about ChatGPT right now. The exceptionally smart AI keeps dazzling the internet even a couple of months after its release. Having the ChatGPT available on the website is awesome, however, the real fun begins when you gain API access. This gives you a great opportunity to integrate smart AI into your projects and applications to make them more powerful and introduce amazing features.&lt;/p&gt;

&lt;p&gt;This article brings you a guide on how to create your own Telegram bot and integrate ChatGPT with it, using the OpenAI's Python library. This may sound like the simplest thing to do, but let's spice things up a bit by introducing the summarize command to get you a summary of several posts in the chat.&lt;/p&gt;




&lt;p&gt;The post assumes you have basic knowledge of Python. However, I recommend checking out Hyperskill's &lt;a href="https://hyperskill.org/tracks/2?utm_source=medium_hs&amp;amp;utm_medium=social&amp;amp;utm_campaign=chatgpt&amp;amp;utm_term=20.04.2023" rel="noopener noreferrer"&gt;Python&lt;/a&gt; and &lt;a href="https://hyperskill.org/tracks/29?utm_source=medium_hs&amp;amp;utm_medium=social&amp;amp;utm_campaign=chatgpt&amp;amp;utm_term=20.04.2023" rel="noopener noreferrer"&gt;Flask&lt;/a&gt; tracks to learn more about Python and developing web applications with Flask.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting everything up
&lt;/h2&gt;

&lt;p&gt;Before jumping right into the code, you'll need to do some preparations to get all the required accesses.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Register your bot in Telegram and retrieve the Telegram access token (using &lt;a class="mentioned-user" href="https://dev.to/botfather"&gt;@botfather&lt;/a&gt; in Telegram)&lt;/li&gt;
&lt;li&gt;Get access to the &lt;a href="https://my.telegram.org/auth" rel="noopener noreferrer"&gt;Telegram Core API&lt;/a&gt; and retrieve &lt;code&gt;api_hash&lt;/code&gt; and &lt;code&gt;app_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Sign up to &lt;a href="//ttps://platform.openai.com/"&gt;OpenAI&lt;/a&gt; and retrieve the API access token.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Save those secret strings and guard them with your life. No stranger should get access to them: this may lead to security violations.&lt;/p&gt;

&lt;h2&gt;
  
  
  Writing the skeleton
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Note&lt;/em&gt;: the full final project code (split into stages with commits) is available on my GitHub, please refer here for details: &lt;a href="https://github.com/yellalena/telegram-gpt-summarizer" rel="noopener noreferrer"&gt;https://github.com/yellalena/telegram-gpt-summarizer&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Python libraries you need to install for this step: flask, pydantic, requests, and pyngrok.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Let's begin with writing the code for the very basic Telegram bot. It should receive messages from chat and be able to respond to them. &lt;br&gt;
First thing first - create a directory for your project and initialize a Python virtual environment. By the way, if you use PyCharm, it will create a virtual environment for you.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkl10ma3mai74jcl3v2c.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffkl10ma3mai74jcl3v2c.png" alt="PyCharm screenshot" width="800" height="600"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At this stage, the goal is split into four parts:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a simple Flask app with one root route to handle webhook with telegram messages.&lt;/li&gt;
&lt;li&gt;Create a class for the Telegram bot and make it able to send messages to a chat.&lt;/li&gt;
&lt;li&gt;Make the application visible to the big internet.&lt;/li&gt;
&lt;li&gt;Register the application address in Telegram.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is what &lt;em&gt;main.py&lt;/em&gt; looks like at this point:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_webhook&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;update&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;chat_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;chat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a response for message: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OK&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;run_ngrok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;8000&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;http_tunnel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ngrok&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;http_tunnel&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;public_url&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_ngrok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;use_reloader&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Few things need clarification:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;I like to put all configuration things in one place, so I created the &lt;em&gt;config.py&lt;/em&gt; file, which will be gathering and storing our tokens and other useful information from exported environment variables.&lt;/li&gt;
&lt;li&gt;Telegram sends updates as a nested JSON, so let's create a set of pydantic models to parse the input to be more convenient later.&lt;/li&gt;
&lt;li&gt;To expose the application to the web, I use ngrok. It makes your localhost's specific port visible to everyone else, giving it a temporary public address. This is why it's important to make sure you expose the same port you're running your Flask app on.&lt;/li&gt;
&lt;li&gt;Finally, I initialize the bot and set a webhook to the ngrok's public URL, so that the bot knows it has to communicate to this URL whenever it receives any message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To set up a webhook, you will need to send a request to your bot's telegram API address, built using your acquired secret token. The telegram bot code looks as follows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TelegramBot&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot_api_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TELEGRAM_API&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/bot&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;set_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;set_webhook_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot_api_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/setWebhook?url=&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;set_webhook_url&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;send_message_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot_api_url&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/sendMessage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;send_message_url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chat_id&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                          &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;raise_for_status&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that everything is ready (don't forget I omitted some of the basic code, you can find it in the repo), export your bot token in an environment variable and hit that "Run"!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpftf0yg66jum85bxdl9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhpftf0yg66jum85bxdl9.png" alt="Telegram chat screenshot" width="752" height="1104"&gt;&lt;/a&gt;&lt;br&gt;
Yay! It's alive!&lt;/p&gt;
&lt;h2&gt;
  
  
  Adding a brain
&lt;/h2&gt;

&lt;p&gt;Amazing, the next step now should be adding a pinch of intelligence to our smart bot. Install an official OpenAI's lib for Python using pip: &lt;code&gt;pip install openai&lt;/code&gt;.&lt;br&gt;
After that, we will be able to create a helper class to communicate with the AI.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;OpenAiHelper&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;gpt-3.5-turbo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;model&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;openai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ChatCompletion&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                                &lt;span class="n"&gt;messages&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;role&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;content&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;message_text&lt;/span&gt;&lt;span class="p"&gt;}])&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;choices&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The API &lt;a href="https://platform.openai.com/docs/models/overview" rel="noopener noreferrer"&gt;suggests&lt;/a&gt; a variety of models to use for your project. The most popular are, of course, the GPTs. GPT-4 is the one that made the most noise lately, but (and because of that) it has limited access now, so for easier testing purposes, I choose GPT-3 instead. No big deal, you can always choose whichever you like the most, just change the string name you pass to the helper.&lt;/p&gt;

&lt;p&gt;Don't forget to add &lt;code&gt;OPENAI_TOKEN&lt;/code&gt; property to the config and let's use the helper in the code.&lt;/p&gt;

&lt;p&gt;First, of course, instantiate it in the &lt;code&gt;main()&lt;/code&gt; method:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1f3de8zn1abu4lc36g6.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp1f3de8zn1abu4lc36g6.png" alt="Initializing the OpenAI helper in main" width="800" height="211"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And then call this guy from the view function, just like that:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openai_helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Whoosh! The magic is happening!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff32ivkvhncjpb0tkfs1n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ff32ivkvhncjpb0tkfs1n.png" alt="Telegram chat screenshot with OpenAI response" width="756" height="1102"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Summarize it!
&lt;/h2&gt;

&lt;p&gt;&lt;em&gt;Python libraries to install for this step: quart, telethon.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I bet you've been there - you have been added to a chat with a group of friends who like to discuss interesting things or share some news or ideas. You've had a lot of stuff to do and you've missed all the fun in the chat. Next thing you see - a hundred unread messages there. Wouldn't it be nice if someone could give you a brief overview of what happened there instead of reading all that? Well, GPT can do that, of course. We only need to ask it.&lt;/p&gt;

&lt;p&gt;This is where the fun begins. For some reason, Telegram's bot API doesn't allow bots to read conversation history. We have webhooks, and we have the explicit GetUpdates() method, but they only work if someone has mentioned the bot. Another option is to make the bot get all the updates if it's added as an admin, but this approach has a few cons as well. First, you would need to set up whole storage for the messages. Second, what if you want to summarize the conversation that was going on before the bot was added to the chat? Not our case.&lt;/p&gt;

&lt;p&gt;Obviously, that's not the reason to give up. Telegram provides the Core API, and this one can help with retrieving a chat history. The only thing is that it's asynchronous. And the most popular Python library for it, Telethon, is asynchronous, too. And Flask is synchronous. Uh-oh.&lt;/p&gt;

&lt;p&gt;And that's where the mysterious &lt;a href="https://pgjones.gitlab.io/quart/tutorials/quickstart.html" rel="noopener noreferrer"&gt;Quart&lt;/a&gt; mentioned in the title comes onto the stage. Quart is the Flask API re-implemented using async, await, and ASGI web server (rather than synchronous and WSGI). Its main advantage in our case is that the syntax is basically the same. Let's do a quick code reorganization.&lt;/p&gt;

&lt;p&gt;The changes are simple. First, adjust imports and change every &lt;em&gt;Flask&lt;/em&gt; to &lt;em&gt;Quart&lt;/em&gt;:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7r5vc1ckx1fj4qqpqcj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw7r5vc1ckx1fj4qqpqcj.png" alt="Image description" width="758" height="112"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Then, make all web application's methods async. And await all the properties and methods that have become asynchronous now:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6lb038yazuqd32llzuc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fq6lb038yazuqd32llzuc.png" alt="Image description" width="800" height="221"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're not sure about what's async Python, I encourage you to check this part of Telethon &lt;a href="https://docs.telethon.dev/en/stable/concepts/asyncio.html" rel="noopener noreferrer"&gt;documentation on the asyncio basics&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have also moved ngrok and TelegramBot to launch them in a separate method decorated with &lt;code&gt;@app.before_serving&lt;/code&gt;. This is a Quart built-in decorator that will ensure everything inside this method will run before the web app is up and serving. It's required so that the bot and the helper are both initialized in the same event loop as the main application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nd"&gt;@app.before_serving&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;startup&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;host&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;run_ngrok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;TelegramBot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TELEGRAM_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openai_helper&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OpenAiHelper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OPENAI_TOKEN&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Running the app has slightly changed as well, but not much. Hypercorn is the ASGI server used to run Quart asynchronously, and if we want to specify an application's port, we need to do it in a config. Note that the &lt;code&gt;main()&lt;/code&gt; is now also async and is run using asyncio:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;quart_cfg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hypercorn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;quart_cfg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PORT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;hypercorn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quart_cfg&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. Let's check if the changes went smoothly for our bot. Run, text, enter:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finid8ehg07sfi7adjf7r.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Finid8ehg07sfi7adjf7r.png" alt="Image description" width="758" height="264"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It's speaking. Great. Now, get the chat history for AI to summarize. Let's use Telegram's Core API with the help of Telethon lib. There, we will need the last two secret strings you have - export them as environment variables too.&lt;/p&gt;

&lt;p&gt;TelegramBot has slight changes in the &lt;code&gt;__init__&lt;/code&gt; method: it will need to have a new &lt;code&gt;core_api_client&lt;/code&gt; property which initializes a Telethon's client and of course, you need to pass the Core API secrets as arguments.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp9z3k1nooimp6i5smfm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fhp9z3k1nooimp6i5smfm.png" alt="Image description" width="800" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And this tiny method will be responsible for retrieving the history:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_chat_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core_api_client&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
        &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;core_api_client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_messages&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sender&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;last_name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; &lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
                  &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reverse&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Telethon's get_messages has many more different parameters you can pass beside the &lt;code&gt;limit&lt;/code&gt; ones. For example, it can reverse the history, or limit it by date instead of the number of messages. It's fun to play with and you can adjust your own bot in any way you'd love to.&lt;/p&gt;

&lt;p&gt;Well, we're almost done. The final touch is to add a summarization option to the webhook handler. This is what getting an answer looks like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# process "summarize" command
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;startswith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/summarize&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;history&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_chat_history&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openai_helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Please, briefly summarize the following conversation history:&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;\
                                                  &lt;span class="n"&gt;history&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;openai_helper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;bot&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;chat_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's see it blooming!&lt;/p&gt;

&lt;p&gt;After you run the application for the first time, it will ask you to log in to Telegram. That's ok: it's required to get access to message history and other private data the Core API has to offer us. Enter the same phone number you used to get access to Telegram Core API. You will receive a verification code inside your app, and after that you're good to go.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjctn0wyfbrwmlnlddy9m.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjctn0wyfbrwmlnlddy9m.png" alt="Image description" width="800" height="97"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Add the bot to a conversation with friends and ask for a summary:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F208g8h5fqyd5k2bmajnd.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F208g8h5fqyd5k2bmajnd.png" alt="Image description" width="800" height="845"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's it! There's an endless list of things you can continue with: add processing other types of messages besides text, configure a number of messages to summarize from the chat, etc. Go for it and don't forget to push your code onto GitHub. Happy coding! :)&lt;/p&gt;

&lt;p&gt;Don't forget to jump onto &lt;a href="https://hyperskill.org/tracks?utm_source=medium_hs&amp;amp;utm_medium=social&amp;amp;utm_campaign=chatgpt&amp;amp;utm_term=20.04.2023" rel="noopener noreferrer"&gt;Hyperskill&lt;/a&gt; website to keep learning about developing web applications with Python and Flask. Here are links to some topics that you might find useful exactly for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://hyperskill.org/learn/step/17366?utm_source=medium_hs&amp;amp;utm_medium=social&amp;amp;utm_campaign=chatgpt&amp;amp;utm_term=20.04.2023" rel="noopener noreferrer"&gt;Error handlers&lt;/a&gt;: If you don't handle errors properly, there is a high chance of your application failing in the runtime or showing ugly tracebacks to the user. To avoid that, read about types of errors and how it's best to handle them in a Flask app.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hyperskill.org/learn/step/17609?utm_source=medium_hs&amp;amp;utm_medium=social&amp;amp;utm_campaign=chatgpt&amp;amp;utm_term=20.04.2023" rel="noopener noreferrer"&gt;Logging&lt;/a&gt;: That's one of the most important things when it comes to testing and debugging your application. Writing meaningful and readable logs is a must for a software developer. Check this topic to learn how to perform logging in Python.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://hyperskill.org/learn/step/12935?utm_source=medium_hs&amp;amp;utm_medium=social&amp;amp;utm_campaign=chatgpt&amp;amp;utm_term=20.04.2023" rel="noopener noreferrer"&gt;Intro to SQLAlchemy&lt;/a&gt;: When you decide you want to store some application data, whether it be any user info or conversation history, you will need to communicate to the database. This topic introduces you to the basics of SQLAlchemy which makes working with databases easy and convenient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Hyperskill is a project-based learning platform that offers a personalized curriculum and a variety of tracks to help people from different backgrounds gain market-relevant skills through online education. It's not only giving you solid pieces of theory but allows you to practice the skills right away - and practice makes learning perfect.&lt;/p&gt;




&lt;p&gt;Did you find this post helpful? Hit clap and follow Hyperskill and me to read more of them later :)&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>webdev</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>One Minute Notes: delegates in C#</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Mon, 02 Jan 2023 22:46:13 +0000</pubDate>
      <link>https://dev.to/heyaliona/one-minute-notes-delegates-in-c-c46</link>
      <guid>https://dev.to/heyaliona/one-minute-notes-delegates-in-c-c46</guid>
      <description>&lt;p&gt;I am staring a new section in my blog — the &lt;strong&gt;One Minute Notes&lt;/strong&gt;, short notes on some important IT topics that will take only one minute to read and get a brief understanding of the concept. In this episode of &lt;strong&gt;One Minute Notes&lt;/strong&gt;, let’s talk about what are delegates in C# and why do we need them.&lt;/p&gt;

&lt;p&gt;⚡&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delegates&lt;/strong&gt; might be considered as placeholders for functions that can be called later. Just like declaring a variable to store some integer value, you can declare a variable to store a function as a value, which allows you to change the function to be called &lt;strong&gt;in the runtime&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s an example of defining and using a delegate. Of course, this is the most naive implementation. A real-life example would probably contain an if-clause or switch to choose between functions to delegate based on some condition.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;public delegate int BinaryOperation(int a, int b);

class SampleClass
{
  public static int Add(int a, int b)
  {
     return a + b;
  }

  static int Subtract(int a, int b) =&amp;gt; a - b;

  static void Main(string[] args)
  {
    BinaryOperation operation = Subtract;
    int x = operation (10, 2); // 8

    operation = Add;
    int x = operation(10, 2); // 12
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Fun fact: .NET Base Class Library also has its own &lt;strong&gt;predefined delegates&lt;/strong&gt; that cover most popular use-cases, so it’s usually unnecessary to implement delegates of your own. The &lt;code&gt;Action&lt;/code&gt; delegate represents a void function, which might take a parameter(s) with generic type. For example, &lt;code&gt;Action&amp;lt;string&amp;gt; printText = x =&amp;gt; Console.WriteLine(x);&lt;/code&gt; There’s also a &lt;code&gt;Func&lt;/code&gt; delegate which defines functions that return a value. Overall, it has the same signature, but the last parameter defines the return value type. In the example above, the Add function might also be assigned to the &lt;code&gt;Func&amp;lt;int, int, int&amp;gt;&lt;/code&gt; delegate.&lt;/p&gt;




&lt;p&gt;Did you find this &lt;strong&gt;One Minute Note&lt;/strong&gt; helpful? Hit like and follow me to read more of them later🙂&lt;/p&gt;

</description>
      <category>emptystring</category>
    </item>
    <item>
      <title>One Minute Notes: Master-Slave Database Replication</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Sat, 08 Oct 2022 15:58:10 +0000</pubDate>
      <link>https://dev.to/heyaliona/one-minute-notes-master-slave-database-replication-52af</link>
      <guid>https://dev.to/heyaliona/one-minute-notes-master-slave-database-replication-52af</guid>
      <description>&lt;p&gt;I am staring a new section in my blog — the &lt;strong&gt;One Minute Notes&lt;/strong&gt;, short notes on some important IT topics that will take only one minute to read and get a brief understanding of the concept. In this episode of &lt;strong&gt;One Minute Notes&lt;/strong&gt;, let’s talk about two types of Master-Slave database replication.&lt;/p&gt;

&lt;p&gt;⚡&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data replication&lt;/strong&gt; is a process of copying data between application’s databases. This is done for consistency, reliability, and performance of a service. &lt;/p&gt;

&lt;p&gt;When replicating, the node where the data is modified and copied from is called &lt;strong&gt;master,&lt;/strong&gt; and the node that receives data copy — a &lt;strong&gt;slave.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There are two types of data replication.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Single Master&lt;/strong&gt; is the type of replication where data is modified on one (master) node only. After the changes are performed, the data from master database is replicated to slave database(s) that have read-only access for its clients.&lt;/p&gt;

&lt;p&gt;This is the simple configuration of Master-Slave replication model, as it excludes any risk of conflicts.   &lt;/p&gt;

&lt;p&gt;Single Master replication is useful when you want to achieve safe data reading or have an always up-to-date database backup.&lt;/p&gt;

&lt;p&gt;Some of Single Master replication’s weaknesses are the difficulty of write requests’ scale up and the need of manual failover handling. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Multi Master&lt;/strong&gt;, in turn, is the configuration where data updating is available for multiple master nodes, that at the same time act as slaves for each other as well. As the data is modified and replicated everywhere “at the same time”, it enhances data’s availability in case one of the masters fail.&lt;/p&gt;

&lt;p&gt;In this case it’s important to handle possible conflicts with concurrent changes on system level.&lt;/p&gt;

&lt;p&gt;Multi Master replication is useful when you want to improve data writing and provide your service with a quick failover option. &lt;/p&gt;

&lt;p&gt;One of main Multi Master replication’s weaknesses is the risk of losing some of transaction in case of one of master nodes failure.&lt;/p&gt;




&lt;p&gt;Did you find this &lt;strong&gt;One Minute Note&lt;/strong&gt; helpful? Hit like and follow me to read more of them later🙂&lt;/p&gt;

</description>
      <category>database</category>
      <category>webdev</category>
      <category>oneminutenotes</category>
    </item>
    <item>
      <title>Using ADFS as the Identity Provider for your Flask application</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Mon, 03 Oct 2022 19:18:29 +0000</pubDate>
      <link>https://dev.to/heyaliona/using-adfs-as-the-identity-provider-for-your-flask-application-4amg</link>
      <guid>https://dev.to/heyaliona/using-adfs-as-the-identity-provider-for-your-flask-application-4amg</guid>
      <description>&lt;p&gt;Federated identity allows users to access different resources using the same set of credentials. It makes the workflow more efficient not only by improving user experience but by increasing security. At the same time, it also makes it easier to develop your own applications, saving you from implementing custom auth every time.&lt;/p&gt;

&lt;p&gt;There are multiple different federated identity management systems, and in this article I’ll explain how to use one of them — Microsoft AD FS — as the identity provider in your Flask application. This article is intended for beginners.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;For a better understanding of the structure and idea of the flow, I put everything on the scheme:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86mxtslbykoixe0lti2v.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F86mxtslbykoixe0lti2v.png" alt="Interactions scheme" width="447" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here, the User wants to access some data on Resource X. As Resource X doesn’t have any credential storage or authentication mechanism, the Flask application needs to verify the user’s identity for Resource X — and this happens via AD FS. Usually, after the user identity was confirmed on the identity provider side, the IdP is sending a callback request to the Service Provider server. This callback request contains some trusted data (usually a token or user information) that will later be used to access the desired resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  AD FS Prerequisites
&lt;/h2&gt;

&lt;p&gt;Note that before actually writing a Flask app, you or your administrator need to configure the AD FS to be the identity provider. There are multiple manuals around the Web and I’m not going to repeat them. Here’s &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/external-identities/direct-federation-adfs#add-the-relying-party-trust-and-claim-rules" rel="noopener noreferrer"&gt;one of the many&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;These are the main things that we’ll need to have configured to establish a relationship between our Service and Identity Providers — &lt;/p&gt;

&lt;p&gt;for AD FS: &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;relying party SSO service URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;party trust identifier&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;custom claim rules&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;for Flask app:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;identity provider URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;sign in URL&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;AD FS certificate&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The talking protocol
&lt;/h2&gt;

&lt;p&gt;Many identity providers use SAML 2.0 protocol to exchange security data, AD FS is one of them as well. SAML-based identity federation enables using single sign-on (SSO). SSO simplifies password management and user authentication, and that’s exactly what we need to make user experience smooth and comfortable. You can read more on SAML &lt;a href="https://medium.com/@winma.15/why-saml-security-assertion-markup-language-3d961a333fd7" rel="noopener noreferrer"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;We’ll be using the python3-saml library by OneLogin to manage SAML communication. This library helps to easily add SAML support to your Python web application, and I love its simplicity yet such a usability at the same time.&lt;/p&gt;

&lt;p&gt;The python3-saml library has brilliant documentation, and for details you can always refer there, but as I was building my Service Provider app, I spent some time figuring out how do some of the things work, so I thought I’d put it all in one place. &lt;/p&gt;

&lt;h2&gt;
  
  
  The coding
&lt;/h2&gt;

&lt;p&gt;The first thing after installing the library is configuring the &lt;code&gt;settings.json&lt;/code&gt; file — the file that’s actually responsible for establishing a connection to the IdP. For my simple app, the bare minimum of the settings looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"strict"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"debug"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;

  &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;service&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;app&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sp_domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SP's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;address&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;case&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;address&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;of&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Flask&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;application.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;URL&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;used&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;by&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;to&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identiyfy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SP&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;based&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;on&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;provided&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;settings&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;file&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"entityId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;sp_domain&amp;gt;/metadata/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;endpoint&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;that&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;handle&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IdP's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SAML&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;response.&lt;/span&gt;&lt;span class="w"&gt; 
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;our&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;example&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;here&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sp_domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;also&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;contain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'adfs'&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;as&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;adfs_route&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;below&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ACS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Attribute&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Consumer&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Service&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"assertionConsumerService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;sp_domain&amp;gt;/?acs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;info&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;about&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;callback&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;endpoint.&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;SLS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;stands&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Single&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Logout&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Service&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"singleLogoutService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;sp_domain&amp;gt;/?sls"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"NameIDFormat"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"x509cert"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"privateKey"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;""&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;identity&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;provider&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;ad&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;fs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;--&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;data&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"idp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;idp_domain&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;is&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;set&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;server.&lt;/span&gt;&lt;span class="w"&gt;  
       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;/adfs/services/trust&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;URI&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;IdP's&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;metadata&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"entityId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"http://&amp;lt;idp_domain&amp;gt;/adfs/services/trust"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sent&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"singleSignOnService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;idp_domain&amp;gt;/adfs/ls/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
       &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;specifies&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;location&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;where&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;log&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;out&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;request&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;will&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;be&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;sent&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"singleLogoutService"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;idp_domain&amp;gt;/adfs/ls/"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="nl"&gt;"binding"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="err"&gt;//&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;AD&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FS&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;certificate&lt;/span&gt;&lt;span class="w"&gt; 
        &lt;/span&gt;&lt;span class="nl"&gt;"x509cert"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ExampleCertString"&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is basically it. Now we only need to configure Flask’s endpoints mentioned above, so the app is ready to communicate with the Identity Provider. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;settings.json&lt;/code&gt; file is loaded to the python-saml toolkit by initializing an OneLogin_Saml2_Auth object. This object will later be used to perform SAML2.0-based actions. It also requires a ‘request’ object that contains the current request’s data. Again, see the perfect &lt;a href="https://github.com/onelogin/python3-saml#the-request" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; where they provide detailed information on each of these objects and how to configure everything for your needs.&lt;/p&gt;

&lt;p&gt;I will just show you the final view that handles every AD FS interaction:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/adfs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;methods&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;GET&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;adfs_route&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;prepare_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# project_dirpath should contain full path to the root directory of the Flask project
&lt;/span&gt;    &lt;span class="n"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;OneLogin_Saml2_Auth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;custom_base_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;project_dirpath&lt;/span&gt;&lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;saml&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;

    &lt;span class="c1"&gt;# single sign on. the 'entrypoint'
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sso&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

    &lt;span class="c1"&gt;# process AD FS callback response
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;acs&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;not_auth_warn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;is_authenticated&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;process_response&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_errors&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;samlUserdata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_attributes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="n"&gt;self_url&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;OneLogin_Saml2_Utils&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_self_url&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RelayState&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;self_url&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RelayState&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;redirect_to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;form&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RelayState&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]))&lt;/span&gt;
        &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_settings&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;is_debug_active&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
            &lt;span class="n"&gt;error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_last_error_reason&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c1"&gt;# process log out
&lt;/span&gt;    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;slo&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;clear&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;redirect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;url_for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;adfs_route&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;samlUserdata&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;samlUserdata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;user_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USER_ID&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="c1"&gt;### send request to the Resource X here ###
&lt;/span&gt;        &lt;span class="c1"&gt;# this is a dummy code just to show the idea
&lt;/span&gt;        &lt;span class="n"&gt;resourcex_data&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://resourcex.com/&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;data.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;resourcex_data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;render_template&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;index.html&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;As simple as it is. In this example, I don’t process log out via AD FS, as for the minimum application and to demonstrate communication with AD FS Identity Provider terminating the session on our side only is pretty much fine. However, if you want to use the Single Log Out as well, you will need to configure the “sls” part of the endpoint, which will handle the AD FS log out callback after calling auth.logout().&lt;/p&gt;

&lt;h2&gt;
  
  
  Bonus — issues I ran into
&lt;/h2&gt;

&lt;p&gt;While developing the Flask Service Provider application, I ran into a couple of issues that I’d like to share here to save your time:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;At first the received AD FS callback had this ‘invalid_response’ error inside auth.get_errors(). The error message looked extra weird, like "The response was received at &lt;a href="https://sp_domain.com/adfs" rel="noopener noreferrer"&gt;https://sp_domain.com/adfs&lt;/a&gt; instead of &lt;a href="https://sp_domain.com/adfs" rel="noopener noreferrer"&gt;https://sp_domain.com/adfs&lt;/a&gt;". This is not a mistake, the URLs in the error message could be just the same. Turns out, sometimes, when your Flask app is hiding behind some proxy, some port confusion might appear. To solve that, consider configuring ‘server_port’ in the request dict built for the Saml2_Auth object.&lt;/li&gt;
&lt;li&gt;I was a bit confused with this NameID thing, what are the different formats and how are they used and even why. As my AD FS was configured by an admin, they didn’t have the NameID specified, which caused errors. Apparently, the NameID is an important SAML element usually used to identify the subject SP and IdP are communicating about (in our case, the user). However, it’s not required. If you’re building a very basic app, it might not be needed, and the python3-saml lib allows you to turn NameID checking off. This is easily done by setting “wantNameId” attribute to false in &lt;code&gt;advanced_settings.json&lt;/code&gt; file. If you want to know more about NameID formats, it’s worth checking section 8.3 of this &lt;a href="http://docs.oasis-open.org/security/saml/v2.0/saml-core-2.0-os.pdf" rel="noopener noreferrer"&gt;SAML documentation&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;As the number of services we use grows every day, we make our users remember more and more different credentials for different websites and applications. To simplify the process, most people reuse the same login/password pair for multiple accounts. But that’s not recommended as it creates a huge risk of an account being stolen or abused. Adding an Identity Provider to your organization solves this issue, improving user experience together with security. With AD FS and python3-saml it’s also easily implemented, and the bare minimum to set everything up is described in this article. Hope that helps!&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>adfs</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>5 Flask project ideas and why you should implement them</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Tue, 05 Jul 2022 07:33:38 +0000</pubDate>
      <link>https://dev.to/heyaliona/5-flask-project-ideas-and-why-you-should-implement-them-4n51</link>
      <guid>https://dev.to/heyaliona/5-flask-project-ideas-and-why-you-should-implement-them-4n51</guid>
      <description>&lt;p&gt;When learning a new skill, you need as much practice as possible because it’s the practice that makes your knowledge and applied skills stronger. Developing web apps is no exclusion: you can read tons of blog posts or crawl through hundreds of tutorials, but until you’ve touched it and figured out how it works by yourself, you will barely remember anything.&lt;/p&gt;

&lt;p&gt;Sometimes you even know your weaknesses and might want to improve your knowledge in particular areas but coming up with a project idea focused on some specific topic can be almost as challenging as actually implementing it afterwards.&lt;/p&gt;

&lt;p&gt;I gathered some Flask web applications ideas along with topics you can learn while working on them. Here we go:&lt;/p&gt;

&lt;h3&gt;
  
  
  Weather app
&lt;/h3&gt;

&lt;p&gt;Weather app is a good start for Flask beginners. It doesn’t require a lot of core code writing, so you can focus on building the good UI for your application.&lt;/p&gt;

&lt;p&gt;Using any open weather API (for example, &lt;a href="https://openweathermap.org/current" rel="noopener noreferrer"&gt;OpenWeather API&lt;/a&gt;) you can easily get weather information on almost any place in the world. And then create nice HTML templates to display this information in a pleasant way.&lt;/p&gt;

&lt;p&gt;Building a project from scratch all by yourself is indeed interesting, but if you need some pivots on how to organise the work, you can try &lt;a href="https://hyperskill.org/projects/164" rel="noopener noreferrer"&gt;Weather App at Hyperskill&lt;/a&gt;, where all the stages of implementing the project are described in detail.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Topics to work on: UI in Flask, Jinja, working with third party resources (APIs)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Movie Database
&lt;/h3&gt;

&lt;p&gt;The name speaks for itself: if you want to create a movie database, you’ll have to store a lot of connected data. Movies, Directors, Actors… more and more. &lt;/p&gt;

&lt;p&gt;All the instances are tightly connected to each other and designing and implementing this kind of relationships can get you some headache, but in the end, it is a very interesting and no doubt useful journey as storing data is something you’ll have to do in almost any project, so you better have some knowledge on it.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Topics to work on: Working with databases, designing tables and relations, SQLAlchemy in Flask&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Todo list
&lt;/h3&gt;

&lt;p&gt;I know, I know. Todo list is one of the most obvious things you usually see recommended to implement as it’s usually very easy. But if you want to get some more of a challenge, what if we turn a simple todo list idea into idea of creating some kind of simple Trello copy?&lt;/p&gt;

&lt;p&gt;Besides database things like creating correct relationships for entities and storing all the data, you can add a simple log in system to the project, which will lead you to practice working with user sessions/user accesses in Flask.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Topics to work on: Authorization/Authentication, Flask sessions, working with databases, designing tables and relations&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Web Calendar
&lt;/h3&gt;

&lt;p&gt;Web calendar is an application simple enough to make it as a REST API: creating and storing calendar events should be pretty easy, while retrieving the data via raw HTTP request should be implemented carefully so that the information is convenient to consume by the end-user. So that’s a good opportunity to learn more about REST API concept in general, the way you actually design this kind of application, and of course about applying REST approach in Flask, building endpoints and marshalling data for output. &lt;/p&gt;

&lt;p&gt;If you still don’t know where to start, check this &lt;a href="https://hyperskill.org/projects/170" rel="noopener noreferrer"&gt;Web Calendar project at Hyperskill&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Topics to work on: REST API, flask-restful, class-based views, marshalling data&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Shared expense tracker
&lt;/h3&gt;

&lt;p&gt;Shared expense tracker is not only a useful app in real life, it also allows you to practice validating user data before actually saving and using it. Also, this project needs to be tested good, as there are so many ways to break it: insert invalid data, split the bill incorrectly, etc. Building this project you can not only work with validating data, for example, using WTForms, but also learn about testing a Flask application, both unit and integration.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Topics to work on: Validating data, working with forms, Flask test context, pytest&lt;/em&gt; &lt;/p&gt;




&lt;p&gt;These five project ideas can of course be adjusted precisely to your needs. But I described the areas each of them suits better to practice on. So go ahead, choose one and start implementing. You’ll get good experience, learn new things to do in Flask and probably will have some fun (only joking. Of course you will). And don’t forget to push your project on to your GitHub! Happy coding :)&lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>webdev</category>
      <category>beginners</category>
    </item>
    <item>
      <title>Profiling Flask application to improve performance</title>
      <dc:creator>aliona matveeva</dc:creator>
      <pubDate>Sun, 28 Feb 2021 09:57:52 +0000</pubDate>
      <link>https://dev.to/heyaliona/profiling-flask-application-to-improve-performance-4970</link>
      <guid>https://dev.to/heyaliona/profiling-flask-application-to-improve-performance-4970</guid>
      <description>&lt;p&gt;In my earlier experience my Flask apps were pretty simple and everything worked just as it has been created from the scratch. I loved it, but then the inevitable thing has happened: my application got really slow and I &lt;strong&gt;HAD&lt;/strong&gt; to do something about it. In this post I’ll tell my story about looking for the bottleneck of my Flask app, solving the problem and will share some amazing tools I used for it.&lt;/p&gt;

&lt;p&gt;So I have a Flask application and a MySQL database which contains a lot of one-to-many super-nested objects. And by saying ‘super-nested’ I’m not hyperbolising: an object of total 3000 table rows  from five different tables is a common case. At the beginning everything was fine, but at some point processing the request started to take 3-5 or even 10 seconds! Oh.My.God. This was something I didn’t want to be happening, so I started to think what might cause the problem. &lt;/p&gt;

&lt;p&gt;At first, I naively went through my code base and tried to determine the area of possible bottleneck. The answer seemed to be obvious: I’m using the marshmallow library to serialise and validate data before inserting and after selecting it from the database. This is it, right? Googling “marshmallow +  slow” returned a few articles that confirmed my suspicions: it’s the library. One of the articles I found was by some guys from Lyft, that said: “We’re using marshmallow and it’s so slow,  so we created toasted-marshmallow which is 15x times faster”. Amazing!- I thought. This is what I need. At that point my requests took an average of three seconds. So I updated my code to using toasted marshmallow and prepared myself a red stripe and scissors. Sent a request… bam. Six seconds. That was impressive. &lt;/p&gt;

&lt;p&gt;I got pretty upset because I thought I have to rewrite half of my app’s logic. And then a colleague asked me if I tried using a profiler? Yes, at this point I have to admit I didn’t know profilers existed. I’m happy I do know now, though!&lt;/p&gt;

&lt;p&gt;What is a code profiler? Long story short, it’s a tool for dynamic code analysis that helps to detect performance problems, also known as bottlenecks of your program. The profiler gathers information on various metrics of how your program works, and based on this information you can identify where to move on with code optimisation.&lt;/p&gt;

&lt;p&gt;There are a lot of profiling tools for Python code, and most of them are built-in — like profile or cProfile. Since I’m speaking about Flask application, let’s see what the world has especially for it. There is a beautiful lib called &lt;a href="https://github.com/muatik/flask-profiler" rel="noopener noreferrer"&gt;flask-profiler&lt;/a&gt;, which has a web interface with some cool features such as route or date filters. But Flask also has a built-in in werkzeug's profiler. It looked awesomely easy in use, so it was the first — and the last — one I tried.&lt;br&gt;
To use the built-in profiler you’ll need to add only two lines of code to your project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;werkzeug.middleware.profiler&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ProfilerMiddleware&lt;/span&gt;
&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ProfilerMiddleware&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also configure it, for example, specify the &lt;strong&gt;profile_dir&lt;/strong&gt; or set &lt;strong&gt;restrictions&lt;/strong&gt; for the stats you want to see. &lt;/p&gt;

&lt;p&gt;After adding the two lines before the Flask &lt;code&gt;app.run()&lt;/code&gt; function and executing the program, overview result on each request will be displayed in the stdout. &lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbzvd447fa45ew5acoy4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzbzvd447fa45ew5acoy4.png" alt="Stdout profiling result" width="800" height="188"&gt;&lt;/a&gt;&lt;br&gt;
Sometimes this short result can give you an idea on what’s slowing your program down. But usually it’s interesting to see thee detailed analysis, which is put into the chosen profile directory as *.prof files. &lt;/p&gt;

&lt;p&gt;There are a few tools to visualise the profile dumps. Some of them providing a full GUI for navigatig within your profiling results ( &lt;a href="http://www.vrplumber.com/programming/runsnakerun/" rel="noopener noreferrer"&gt;RunSnakeRun&lt;/a&gt;), some of them represent the analysis result as a Graph (&lt;a href="https://github.com/jrfonseca/gprof2dot" rel="noopener noreferrer"&gt;gprof2dot&lt;/a&gt;).&lt;br&gt;
I stopped on &lt;a href="https://jiffyclub.github.io/snakeviz/#snakeviz" rel="noopener noreferrer"&gt;snakeviz&lt;/a&gt;, which is a browser based visualizer. &lt;br&gt;
It is easy installed using &lt;code&gt;pip install snakeviz&lt;/code&gt;, and then simply run with snakeviz profile_dir. The result looks something like this, and you can dive in to each of the visual parts to see its more close detalization, which is in my opinion is super cool and handy.&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgnonuyy8wxooda4ngws.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqgnonuyy8wxooda4ngws.png" alt="Snakeviz profiling result" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;After analysing the visual representation of the profiling results, it turned out that most of my performance problems were coming from service-database interaction. When I knew exactly which lines of code took inexcusably lot of time to execute, I was able to solve my problems whether by rewriting the queries, or improving the code itself. Improving code performance via enhancement of database interaction is a topic for a whole new article, so I will probably write about my experience with it later.&lt;/p&gt;

&lt;p&gt;To sum up: of course, automatic profilers are not perfect and they won’t 100% show you the mistakes in your code. Sometimes it’s simply ‘staring at your code’ method that actually works really good. But at least with the help of profilers you can detect the weak area which in most cases is more than enough. &lt;/p&gt;

&lt;p&gt;Thank you for reading and I hope it was somehow useful. :) &lt;/p&gt;

</description>
      <category>python</category>
      <category>flask</category>
      <category>profiling</category>
    </item>
  </channel>
</rss>
