<?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>Forem: Edun Rilwan</title>
    <description>The latest articles on Forem by Edun Rilwan (@emiloju).</description>
    <link>https://forem.com/emiloju</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%2F1374858%2Ff373c7ae-3be9-4d15-82ac-96d2b01a5bc2.jpg</url>
      <title>Forem: Edun Rilwan</title>
      <link>https://forem.com/emiloju</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emiloju"/>
    <language>en</language>
    <item>
      <title>A Goldmine for documentation leads/Technical writers</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Thu, 15 May 2025 15:34:29 +0000</pubDate>
      <link>https://forem.com/emiloju/a-goldmine-for-documentation-leads-2jbf</link>
      <guid>https://forem.com/emiloju/a-goldmine-for-documentation-leads-2jbf</guid>
      <description>&lt;div class="ltag__link--embedded"&gt;
  &lt;div class="crayons-story "&gt;
  &lt;a href="https://dev.to/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd" class="crayons-story__hidden-navigation-link"&gt;How to Migrate Technical Documentation: Tools, Checklist, and Tips&lt;/a&gt;


  &lt;div class="crayons-story__body crayons-story__body-full_post"&gt;
    &lt;div class="crayons-story__top"&gt;
      &lt;div class="crayons-story__meta"&gt;
        &lt;div class="crayons-story__author-pic"&gt;

          &lt;a href="/emiloju" class="crayons-avatar  crayons-avatar--l  "&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%2Fuser%2Fprofile_image%2F1374858%2Ff373c7ae-3be9-4d15-82ac-96d2b01a5bc2.jpg" alt="emiloju profile" class="crayons-avatar__image"&gt;
          &lt;/a&gt;
        &lt;/div&gt;
        &lt;div&gt;
          &lt;div&gt;
            &lt;a href="/emiloju" class="crayons-story__secondary fw-medium m:hidden"&gt;
              Edun Rilwan
            &lt;/a&gt;
            &lt;div class="profile-preview-card relative mb-4 s:mb-0 fw-medium hidden m:inline-block"&gt;
              
                Edun Rilwan
                
              
              &lt;div id="story-author-preview-content-2482352" class="profile-preview-card__content crayons-dropdown branded-7 p-4 pt-0"&gt;
                &lt;div class="gap-4 grid"&gt;
                  &lt;div class="-mt-4"&gt;
                    &lt;a href="/emiloju" class="flex"&gt;
                      &lt;span class="crayons-avatar crayons-avatar--xl mr-2 shrink-0"&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%2Fuser%2Fprofile_image%2F1374858%2Ff373c7ae-3be9-4d15-82ac-96d2b01a5bc2.jpg" class="crayons-avatar__image" alt=""&gt;
                      &lt;/span&gt;
                      &lt;span class="crayons-link crayons-subtitle-2 mt-5"&gt;Edun Rilwan&lt;/span&gt;
                    &lt;/a&gt;
                  &lt;/div&gt;
                  &lt;div class="print-hidden"&gt;
                    
                      Follow
                    
                  &lt;/div&gt;
                  &lt;div class="author-preview-metadata-container"&gt;&lt;/div&gt;
                &lt;/div&gt;
              &lt;/div&gt;
            &lt;/div&gt;

          &lt;/div&gt;
          &lt;a href="https://dev.to/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd" class="crayons-story__tertiary fs-xs"&gt;&lt;time&gt;May 13 '25&lt;/time&gt;&lt;span class="time-ago-indicator-initial-placeholder"&gt;&lt;/span&gt;&lt;/a&gt;
        &lt;/div&gt;
      &lt;/div&gt;

    &lt;/div&gt;

    &lt;div class="crayons-story__indention"&gt;
      &lt;h2 class="crayons-story__title crayons-story__title-full_post"&gt;
        &lt;a href="https://dev.to/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd" id="article-link-2482352"&gt;
          How to Migrate Technical Documentation: Tools, Checklist, and Tips
        &lt;/a&gt;
      &lt;/h2&gt;
        &lt;div class="crayons-story__tags"&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/documentation"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;documentation&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/git"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;git&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/writing"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;writing&lt;/a&gt;
            &lt;a class="crayons-tag  crayons-tag--monochrome " href="/t/ssgs"&gt;&lt;span class="crayons-tag__prefix"&gt;#&lt;/span&gt;ssgs&lt;/a&gt;
        &lt;/div&gt;
      &lt;div class="crayons-story__bottom"&gt;
        &lt;div class="crayons-story__details"&gt;
          &lt;a href="https://dev.to/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left"&gt;
            &lt;div class="multiple_reactions_aggregate"&gt;
              &lt;span class="multiple_reactions_icons_container"&gt;
                  &lt;span class="crayons_icon_container"&gt;
                    &lt;img src="https://assets.dev.to/assets/sparkle-heart-5f9bee3767e18deb1bb725290cb151c25234768a0e9a2bd39370c382d02920cf.svg" width="18" height="18"&gt;
                  &lt;/span&gt;
              &lt;/span&gt;
              &lt;span class="aggregate_reactions_counter"&gt;2&lt;span class="hidden s:inline"&gt; reactions&lt;/span&gt;&lt;/span&gt;
            &lt;/div&gt;
          &lt;/a&gt;
            &lt;a href="https://dev.to/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd#comments" class="crayons-btn crayons-btn--s crayons-btn--ghost crayons-btn--icon-left flex items-center"&gt;
              Comments


              &lt;span class="hidden s:inline"&gt;Add Comment&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="crayons-story__save"&gt;
          &lt;small class="crayons-story__tertiary fs-xs mr-2"&gt;
            8 min read
          &lt;/small&gt;
            
              &lt;span class="bm-initial"&gt;
                

              &lt;/span&gt;
              &lt;span class="bm-success"&gt;
                

              &lt;/span&gt;
            
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;

&lt;/div&gt;


</description>
      <category>documentation</category>
      <category>git</category>
      <category>writing</category>
      <category>ssgs</category>
    </item>
    <item>
      <title>How to Migrate Technical Documentation: Tools, Checklist, and Tips</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Tue, 13 May 2025 20:38:57 +0000</pubDate>
      <link>https://forem.com/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd</link>
      <guid>https://forem.com/emiloju/how-to-migrate-technical-documentation-tools-checklist-and-tips-28gd</guid>
      <description>&lt;p&gt;&lt;strong&gt;Your documentation platform might be the reason behind your growing user frustration and your team's productivity slump and you probably don’t realize it.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Documentation platforms are SaaS tools that help businesses create and publish documentation for their technical products. They simplify the process by offering boilerplate solutions like templates, themes, and ready-made components, making it easy to launch a fully functional documentation site quickly.&lt;/p&gt;

&lt;p&gt;These platforms range from no-code solutions like &lt;strong&gt;Document360&lt;/strong&gt; and &lt;strong&gt;KnowledgeOwl&lt;/strong&gt;, to low-code or code-first tools such as &lt;strong&gt;Docusaurus, GitBook, Astro Starlight&lt;/strong&gt;, and &lt;strong&gt;Mintlify&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;In this article, we’ll focus on the low-code/code-first category which are often referred to as Static Site Generators (SSGs).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SSGs&lt;/strong&gt; are widely used for developer-facing documentation. Their primary function is to generate static HTML pages from source files like Markdown. However, they’ve gained popularity among technical writers because they integrate smoothly with developer tools and workflows making them ideal for documenting technical products.&lt;/p&gt;

&lt;p&gt;Most SSGs come with well-structured project templates and support features like Markdown editing, version control, customization, automation, collaboration, and reusable components/utilities.&lt;/p&gt;

&lt;p&gt;If your current documentation platform lacks many (or most) of these capabilities, it might be time to consider migrating your documentation.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is documentation migration
&lt;/h2&gt;

&lt;p&gt;Documentation migration is the process of moving your documentation and its content from one platform to another. There are several reasons why teams choose to migrate their documentation. These reasons can be personal, technical, structural, or financial.&lt;/p&gt;

&lt;p&gt;Documentation migration is especially common in growing startups. Early on, when the team is small and requirements are minimal, technical writers may not have a well-defined reason for choosing a particular documentation tool. They often go with whatever works at the time, focusing on speed and quick deployment.&lt;/p&gt;

&lt;p&gt;But as the product evolves and documentation needs become more complex, the limitations of the initial platform become harder to ignore. Eventually, migrating to a more flexible and capable platform becomes necessary.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to consider migrating your documentation
&lt;/h2&gt;

&lt;p&gt;Easy collaboration, simplicity, automation, and continuous integration are essential features to look for when choosing a documentation tool/platform. These capabilities help streamline the documentation process and boost your team’s productivity.&lt;/p&gt;

&lt;p&gt;Once you’ve confirmed that the platform's tech stack aligns with your team’s skills and tools, the features mentioned above should be part of your evaluation checklist.&lt;/p&gt;

&lt;p&gt;Not sure if it’s time to migrate your documentation? The following signs can help you decide whether switching to a new platform is the right move.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A. Technical difficulties:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Some documentation platforms or SSGs can be challenging to set up, use, or deploy. They might have a unique setup process or offer limited support for certain operating systems, which can hinder collaboration. If technical writers (including external contributors) struggle to test the documentation locally on their machines or if your team frequently encounters bugs and errors, it may be time to consider a more accessible and developer-friendly platform.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Skill gap:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The choice of documentation platform should align with your team’s skillset. For example, if your technical writers have limited experience with JavaScript, platforms like Mintlify, GitBook, or MkDocs may be a better fit. On the other hand, if your team is proficient in JavaScript or React, tools like Docusaurus might offer more flexibility and control.&lt;/p&gt;

&lt;p&gt;That said, there’s a trade-off to consider. If the more suitable platform lacks essential features compared to your current tool, your team may need to invest time in upskilling rather than switching to a less capable alternative.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C. Financial constraints:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Not all documentation tools are free. Platforms like &lt;strong&gt;Mintlify&lt;/strong&gt; require a paid subscription to access premium features. If your current platform is becoming too expensive, it might be worth exploring more budget-friendly or open-source alternatives without sacrificing key capabilities.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D. Limited collaboration:&lt;/strong&gt; &lt;/p&gt;

&lt;p&gt;A good documentation platform should support effective collaboration, especially for distributed teams. Features like version control help track changes, identify contributors, and roll back errors when needed. If your current tool lacks collaboration features such as Git integration or commenting, it may be limiting your team’s workflow and accountability. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E. User Experience and customization:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;User Experience refers to the overall user satisfaction while reading your documentation. A good documentation platform should have a good user experience that ensures your audience are satisfied when using your documentation. &lt;/p&gt;

&lt;p&gt;A platform with poor UX can frustrate users and affect their ability to find the information they need. Look for platforms that support: responsive design across devices, clear and readable typography, light/dark mode, Alt text for accessibility, customization options for branding,such as fonts, colors, and layout.&lt;/p&gt;

&lt;p&gt;For example, Docusaurus allows customization using React and CSS/TailwindCSS making it easier to align the documentation with your brand identity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F. Scaling challenges:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;As your product or company grows, your documentation might outgrow its current structure or platform, requiring a more scalable documentation platform. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;G. Security or compliance requirements:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Regulations or internal policies may necessitate a move to a platform that offers better access control, encryption and on-premise deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;H. Integration and automation:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your docs should be easily integrated with CI/CD pipelines or analytic tools to track everything that's going on with your docs. If your current documentation platform doesn't support it, it's a sign to migrate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Popular documentation platforms &amp;amp; SSGs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A. Docusaurus:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docusaurus.io/" rel="noopener noreferrer"&gt;Docusaurus&lt;/a&gt; is an open-source documentation site generator built by Meta, designed for creating optimized, fast, and customizable websites using React. It supports markdown files, versioning, internationalization (i18n), and integrates well with Git-based workflows. Its React architecture allows for deep customization and dynamic components. Docusaurus is ideal for developer-focused documentation with a need for flexibility and branding.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Gitbook:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.gitbook.com/" rel="noopener noreferrer"&gt;GitBook&lt;/a&gt; is a documentation platform that provides a sleek, collaborative interface for technical teams and product documentation. It’s known for its clean UI, WYSIWYG editing, and support for markdown. GitBook supports team collaboration, access control, and integrates with tools like GitHub and Slack. It’s great for teams looking for a balance between ease of use and professionalism.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C. Mintlify:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt; is a modern documentation platform optimized for developer experience and SEO. It offers a clean UI, fast performance, and out-of-the-box support for features like code highlighting, analytics, and built-in search. Mintlify integrates with GitHub and allows teams to write in markdown or MDX. It's best suited for startups and API-first companies looking for polished, developer-friendly docs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D. Hugo:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://gohugo.io/" rel="noopener noreferrer"&gt;Hugo&lt;/a&gt; is a fast and flexible static site generator built in Go, known for its speed and large theme ecosystem. It supports markdown, taxonomies, multilingual content, and powerful templating with minimal dependencies. Hugo is highly performant and well-suited for building large-scale documentation sites. It’s ideal for teams seeking speed and customization with minimal runtime requirements. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E. Astro Starlight:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://starlight.astro.build/" rel="noopener noreferrer"&gt;Astro Starlight&lt;/a&gt; is a modern documentation theme built on the Astro framework, offering a minimal yet feature-rich experience. It supports markdown and MDX, built-in search, responsive design, dark mode, and performance optimization. With Astro’s component-based architecture, it allows for flexible integration with JavaScript/React/Vue components. It’s perfect for developers looking for a fast, lightweight, and highly customizable doc site.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F. MkDocs:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.mkdocs.org/" rel="noopener noreferrer"&gt;MkDocs&lt;/a&gt; is a static site generator designed specifically for project documentation and written in Python. It’s easy to set up, uses markdown for content, and features a number of themes, including the popular Material for MkDocs. MkDocs integrates well with Python-based workflows and CI/CD tools. It’s a great choice for Python developers and teams looking for simplicity and readability.&lt;/p&gt;

&lt;p&gt;Checkout this comprehensive article that compares &lt;a href="https://overcast.blog/11-documentation-website-generators-you-should-know-37eb7da6f36b" rel="noopener noreferrer"&gt;popular documentation site tools/generators.&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Steps to follow when performing a documentation migration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1.Identify and select a documentation platform:&lt;/strong&gt;&lt;br&gt;
The first step is to choose a documentation platform that aligns with your team’s technical skill level, your company’s budget, and the size or complexity of your project.&lt;/p&gt;

&lt;p&gt;Each of these factors should be carefully considered before making a decision. This step is critical to ensuring the long-term success of the documentation and avoiding future migrations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2.Create an action plan:&lt;/strong&gt;&lt;br&gt;
An action plan outlines the path your migration will take. It serves as a roadmap defining objectives, tasks, and deliverables, helping the team stay organized and focused.&lt;/p&gt;

&lt;p&gt;Some of the items to include in your action plan are the project deadline, number of contributors, and documentation sections. For example, if you're migrating documentation for a developer tool, the action plan should break it down into logical parts like “Getting Started,” “Advanced Concepts,” and “Troubleshooting.” Each section should have a clear timeline for completion.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3.Apply the divide and conquer strategy:&lt;/strong&gt;&lt;br&gt;
The documentation lead should divide the team into smaller groups, each responsible for a specific section of the documentation. If the team is small (1–3 people), then assign each person a section based on workload and expertise. This approach helps break the project into manageable stages and ensures progress is steady and organized.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4.Check-in frequently on the project's progress:&lt;/strong&gt;&lt;br&gt;
It’s important for the documentation lead to regularly check in on the migration process. Encourage each sub-group (or individual, if applicable) to report on their progress and raise any blockers they encounter.&lt;br&gt;
This ensures the team stays aligned, and problems can be addressed promptly before they escalate.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for migration
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;A. Migrate first, finetune later:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Don't get stuck trying to perfect every detail before completing the migration. Move the bulk of your content to the new platform first, then refine structure, formatting, links, and SEO gradually. This helps maintain momentum and avoids unnecessary delays.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;B. Understand git perfectly:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Most low-code documentation platforms rely on Git for version control and collaboration. Ensure that everyone involved in the migration has a good grasp of Git basics like branching, committing, merging, and resolving conflicts. This prevents errors and saves hours of rework during migration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;C. Outsource for contributors:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your internal team is too small or overwhelmed, consider bringing in external contributors like technical writers, open source contributors, or freelancers who are familiar with the platform you’re migrating to. This boosts speed, brings fresh perspectives, and distributes workload more efficiently. Most technical writers are always willing to contribute for free in exchange for experience. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;D. Clearly outline the migration goals:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Before migrating, define the objective of the migration. Are you migrating to improve SEO, enhance user experience, simplify collaboration, or reduce costs? When goals are clear, your team stays focused and aligned throughout the migration process.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;E. Create a clear communication channel:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Set up a centralized and clear communication channel like Slack, Discord, Notion, or whatever works for your team—to track progress, raise issues, and provide updates. Effective communication reduces misunderstandings and helps the team handle issues quickly.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;F. Inform your users:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Let your documentation users know that a migration is underway (or has happened). Provide a brief announcement or changelog, especially if URLs, navigation, or layout have changed. This builds trust and ensures users aren’t caught off guard when things look or behave differently.&lt;/p&gt;

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

&lt;p&gt;Migration can be very demanding, especially when large documentation is involved. However, the process can be made easier by splitting the entire process into smaller chunks, assigning each chunk to teams/writers, and setting realistic milestones. Finally, everyone must be committed to the project's success and there should be consistent communication between everyone involved.   &lt;/p&gt;

</description>
      <category>documentation</category>
      <category>git</category>
      <category>writing</category>
      <category>ssgs</category>
    </item>
    <item>
      <title>Docs-as-code in action: Documenting a Python library.</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Wed, 22 Jan 2025 10:36:38 +0000</pubDate>
      <link>https://forem.com/emiloju/docs-as-code-in-action-documenting-a-python-library-4pbe</link>
      <guid>https://forem.com/emiloju/docs-as-code-in-action-documenting-a-python-library-4pbe</guid>
      <description>&lt;p&gt;Documentation is a crucial resource for helping your target audience understand how to use your product effectively. High-quality documentation not only communicates the core problem your product solves but also empowers users to achieve their desired outcomes seamlessly.  &lt;/p&gt;

&lt;p&gt;The same holds true for open-source libraries and packages. Clear and accessible documentation is essential for guiding developers on how to integrate these tools into their projects successfully.  &lt;/p&gt;

&lt;p&gt;In recent years, the &lt;strong&gt;Docs-as-Code (DaC)&lt;/strong&gt; approach to documentation has gained significant popularity. This method treats documentation as a fundamental part of the software development lifecycle by using the same tools and processes developers rely on for code. &lt;/p&gt;

&lt;p&gt;This method is widely accepted because it promotes consistent, version-controlled, and easily maintainable documentation that evolves alongside the product.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Docs-as-code?
&lt;/h2&gt;

&lt;p&gt;In simple terms, DaC is a method that involves handling and maintaining documentation as you would do to your code. &lt;/p&gt;

&lt;p&gt;A typical software development life cycle involves 7 stages which include the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Planning&lt;/li&gt;
&lt;li&gt;Gathering Requirements and Analysis&lt;/li&gt;
&lt;li&gt;Design&lt;/li&gt;
&lt;li&gt;Coding and implementation&lt;/li&gt;
&lt;li&gt;Code testing&lt;/li&gt;
&lt;li&gt;Code deployment&lt;/li&gt;
&lt;li&gt;Code maintenance&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Therefore, DaC is a new approach that ensures documentation goes through the same stages. This keeps the documentation versioned and up to date with software changes. &lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Deployment without DaC&lt;/strong&gt;&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%2Faqeiiyozgfhudvd9vza4.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%2Faqeiiyozgfhudvd9vza4.png" alt="Deployment without DaC" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Deployment with DaC&lt;/em&gt;&lt;/strong&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%2Fzrma3r6jh0lywtlpd0si.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%2Fzrma3r6jh0lywtlpd0si.png" alt="Deployment with DaC" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;While this guide may not go in-depth into the theoretical aspect of DaC, you can explore the &lt;a href="https://dev.to/giladmaayan/beginners-guide-to-documentation-as-code-11o1"&gt;Beginner’s guide to Docs-as-code&lt;/a&gt; article which explains the concept behind DaC in detail. &lt;/p&gt;
&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This guide involves the practical implementation of DaC with Python. You will learn how to document an open-source Python library using &lt;a href="https://mintlify.com/" rel="noopener noreferrer"&gt;Mintlify&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;Mintlify is a static site generator and documentation site used for public-facing documentation. It is easy to maintain and use for various documentation needs such as developer documentation, API documentation, etc. It also works well with the DaC methodology.&lt;/p&gt;

&lt;p&gt;This tutorial is a sequel to an existing tutorial on &lt;a href="https://dev.to/emiloju/how-to-build-and-deploy-a-python-library-4al2"&gt;how to build and deploy a Python library&lt;/a&gt;. Using the DaC methodology, you'll learn how to document the Python library developed referenced tutorial.&lt;/p&gt;

&lt;p&gt;It is recommended that you complete the previous tutorial before you continue. However, you can proceed if you have an existing project to use for this tutorial.&lt;/p&gt;
&lt;h2&gt;
  
  
  Project requirements
&lt;/h2&gt;

&lt;p&gt;A basic knowledge of Git and GitHub, how to create a Github repository and how to push your code to GitHub us required. You also need the following tools for this tutorial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Mintlify account:&lt;/strong&gt; You need an active Mintlify account to create documentation (steps will be provided in the guide).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; You will need &lt;a href="http://Node.js" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt; version 18 and above to install Mintlify and edit your documentation locally.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Setup a Mintlify documentation
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to setup a documentation using Mintlify:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. &lt;a href="https://dashboard.mintlify.com/signup" rel="noopener noreferrer"&gt;Create an account&lt;/a&gt; on Mintlify&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Setup your Mintlify account:&lt;/strong&gt;&lt;br&gt;
A verification link will be sent to your mail. This link will redirect you to the page below:&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%2Fj4ztmaw2je85wdcj5jso.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%2Fj4ztmaw2je85wdcj5jso.png" alt="Image description" width="500" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Sign in with Github:&lt;/strong&gt;&lt;br&gt;
The first step requires you to sign in with your Github account.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Create a GitHub repository (repo) for your documentation:&lt;/strong&gt;&lt;br&gt;
The next step requires you to install and authorize the Mintlify app on your Github account. This allows Mintlify to automatically create a repo for your docs&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%2Fr55g2i70tje61yh8gzgo.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%2Fr55g2i70tje61yh8gzgo.png" alt="Image description" width="700" height="287"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Access your documentation repo:&lt;/strong&gt;&lt;br&gt;
The previous step creates a new &lt;code&gt;docs&lt;/code&gt; repo for your documentation. Check your GitHub repositories for a new &lt;code&gt;docs&lt;/code&gt; repo&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%2Fdrlu10ouq7hsejr6g5xn.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%2Fdrlu10ouq7hsejr6g5xn.png" alt="Image description" width="300" height="192"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  Add the documentation to your project
&lt;/h3&gt;

&lt;p&gt;The next step is to clone the &lt;code&gt;docs&lt;/code&gt; repo to your local environment and add it to an existing project such as a developer tool, open-source package, etc. If you already completed the previous tutorial, your project will be &lt;code&gt;exchangeLibrary&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Follow the steps below to add the documentation to your project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open the terminal and clone the docs repository with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git clone https://github.com/&amp;lt;your github username&amp;gt;/docs 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Copy the cloned docs folder to your project&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Open the project in a code editor.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your project file structure should now look 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%2Fa64cdjybdgn1w8vvj8fz.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%2Fa64cdjybdgn1w8vvj8fz.png" alt="Image description" width="199" height="148"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Preview documentation locally
&lt;/h3&gt;

&lt;p&gt;Mintlify allows you to preview your documentation locally before publishing it. Follow the steps below to set it up:&lt;br&gt;
&lt;strong&gt;1. Open your project in the terminal&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. Run the command below to install Mintlify globally:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;npm i &lt;span class="nt"&gt;-g&lt;/span&gt; mintlify
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Switch to the &lt;code&gt;docs&lt;/code&gt; folder in your project:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Start a mintlify server with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mintlify dev
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see a message like the one below in your 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%2F4hlfsnfot5y4tdoqq3sg.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%2F4hlfsnfot5y4tdoqq3sg.png" alt="Image description" width="319" height="51"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Open the URL to preview the documentation locally. The content of your documentation will be the Mintlify starter doc template. This will change when you start editing your documentation.&lt;/p&gt;

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

&lt;p&gt;A Mintlify documentation is powered by the &lt;code&gt;mint.json&lt;/code&gt; file. This file contains the color scheme, pagination, and navigation settings for the documentation. You can find it in the project’s &lt;code&gt;docs&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;Also, documentation files in Mintlify are written in &lt;code&gt;.mdx&lt;/code&gt;. It is almost similar to markdown(&lt;code&gt;.md&lt;/code&gt;) except that it allows special tags and symbols. &lt;/p&gt;

&lt;p&gt;In this section, you will learn how to edit your documentation settings in the &lt;code&gt;mint.json&lt;/code&gt; file, and how to add texts and special components to your documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Edit documentation settings
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;mint.json&lt;/code&gt; file is a JSON object made up of color schemes, pagination, navigation settings, etc. for your documentation. Below is a list of available settings and what they mean:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Color scheme and appearance:&lt;/strong&gt;&lt;br&gt;
This section is used to beautify and enhance your documentation appearance. It is used to change the logo (for both light and dark mode), favicon, title, and color scheme for the documentation. It starts from the &lt;code&gt;$schema&lt;/code&gt; key to the &lt;code&gt;colors&lt;/code&gt; key as seen below:&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="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"$schema"&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://mintlify.com/schema.json"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;your-documentation-title&amp;gt;"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"logo"&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;"dark"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;logo-for-dark-mode&amp;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;"light"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;logo-for-light-mode&amp;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;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"favicon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;link-to-a-favicon&amp;gt;"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"colors"&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;"primary"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0D9373"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"light"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#07C983"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"dark"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0D9373"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"anchors"&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;"from"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#0D9373"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"to"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"#07C983"&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="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Navigation links and CTA button:&lt;/strong&gt;&lt;br&gt;
This section is used for setting up navigation links and buttons at the top of the documentation page. Below is an example of a navigation link and button:&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%2Fsevcd899u20hy1rdmdjs.jpeg" 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%2Fsevcd899u20hy1rdmdjs.jpeg" alt="Image description" width="500" height="71"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The code below sets up the navigation links and a CTA button for your Mintlify documentation:&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="w"&gt;  &lt;/span&gt;&lt;span class="nl"&gt;"topbarLinks"&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;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Community"&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://mywebsite.com/community"&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="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Log In"&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://mywebsite.com/login"&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="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"topbarCtaButton"&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;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get Started"&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://mywebsite.com/get-started"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Tabs and anchors:&lt;/strong&gt;&lt;br&gt;
Tabs and anchors can be used to set up horizontal and vertical sections respectively in your documentation. Below are examples of tabs:&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%2Fgnphrmcmd3hfdko54w34.jpeg" 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%2Fgnphrmcmd3hfdko54w34.jpeg" alt="Image description" width="700" height="96"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is an example of an anchor:&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%2Fcxnfks7hv6dabkovaa8d.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%2Fcxnfks7hv6dabkovaa8d.png" alt="Image description" width="600" height="94"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The settings for these components are handled by the &lt;code&gt;tabs&lt;/code&gt; and &lt;code&gt;anchors&lt;/code&gt; keys.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Navigation settings:&lt;/strong&gt; &lt;br&gt;
This section helps you to group the pages in your documentation. It is an array consisting of a &lt;code&gt;group&lt;/code&gt; key, and a &lt;code&gt;pages&lt;/code&gt; array where the pages for the group are added sequentially. Below is an example of how it is added:&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;“navigation”:&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="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Get Started"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"pages"&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="s2"&gt;"introduction"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"installation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"authentication"&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="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The settings above will translate to the image below:&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%2Fiotuudwpoewznew1z9f5.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%2Fiotuudwpoewznew1z9f5.png" alt="Image description" width="400" height="99"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The pages(introduction, etc.) are &lt;code&gt;.mdx&lt;/code&gt; files in your project’s &lt;code&gt;docs&lt;/code&gt; folder.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Nested navigation:&lt;/strong&gt;&lt;br&gt;
Nested navigation is commonly used to create subsections within a documentation. Below is an example of a nested navigation:&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%2Feis5miwxcp44lorezz2t.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%2Feis5miwxcp44lorezz2t.png" alt="Image description" width="353" height="209"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Below is a sample code to set up a nested navigation on Mintlify:&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="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"APIs"&lt;/span&gt;&lt;span class="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
   &lt;/span&gt;&lt;span class="nl"&gt;"pages"&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;span class="nl"&gt;"group"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Exchange API"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="nl"&gt;"pages"&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="s2"&gt;"&amp;lt;a-path-to-a-mdx-file&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;a-path-to-a-mdx-file&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;a-path-to-a-mdx-file&amp;gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
            &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;a-path-to-a-mdx-file&amp;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;"icon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"money-bills"&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="err"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above nests a section/group within another section. The &lt;code&gt;icon&lt;/code&gt; key beautifies the section title with an icon when rendered on a web page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Footer settings:&lt;/strong&gt;&lt;br&gt;
The &lt;code&gt;footerSocials&lt;/code&gt; key is used to add social media accounts related to the documentation. Below is an example:&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%2Fjktl5486w4z5iv83wn98.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%2Fjktl5486w4z5iv83wn98.png" alt="Image description" width="273" height="96"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  How to add content
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://mintlify.com/docs/quickstart" rel="noopener noreferrer"&gt;Mintlify documentation&lt;/a&gt; guides you on how to add content to your documentation. I recommend you check out the documentation to learn how to add different content to your documentation.&lt;/p&gt;

&lt;p&gt;Check out this &lt;a href="https://rilwanorganization.mintlify.app/introduction" rel="noopener noreferrer"&gt;sample documentation&lt;/a&gt; for inspiration on how to structure your own documentation.&lt;/p&gt;
&lt;h3&gt;
  
  
  Documentation writing tips
&lt;/h3&gt;

&lt;p&gt;Below are few tips to help you write clear and user-friendly documentation: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be as direct as possible:&lt;/strong&gt; 
Avoid extraneous information that adds no value. Your documentation is for developers who want to use your package or tool in their next project so only show them what they need to achieve this. &lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;2. Add a description or overview of your tool:&lt;/strong&gt;&lt;br&gt;
Before going into details on how to use your tool, briefly describe what your tool is and the problem it solves. This should be on the first page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Add enough code samples:&lt;/strong&gt;&lt;br&gt;
This will help them understand how to use your tool without unnecessary errors. Code samples on installation, authentication, response samples, method arguments, etc are very important.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Errors and exceptions:&lt;/strong&gt;&lt;br&gt;
This will help users in debugging. Add a page to describe the kind of errors users may encounter when using your tool. Also show code samples for this.&lt;/p&gt;
&lt;h2&gt;
  
  
  Push the project to Github
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to push the project to Github:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open a git bash terminal in your project and switch into the docs folder with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;cd &lt;/span&gt;docs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Remove git from this folder with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nb"&gt;rm&lt;/span&gt; &lt;span class="nt"&gt;-rf&lt;/span&gt; .git 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command removes &lt;code&gt;.git&lt;/code&gt; from the &lt;code&gt;docs&lt;/code&gt; folder to avoid issues when you want to push the entire project to Github.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Push the project to GitHub.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy the documentation
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to deploy your documentation on Mintlify:&lt;br&gt;
&lt;strong&gt;1. Login to your Mintlify dashboard&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. Click on the Settings tab&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%2Fkmqd24bh0ud65v455tz3.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%2Fkmqd24bh0ud65v455tz3.png" alt="Image description" width="800" height="215"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;3. Change your Mintlify Github repo to your project’s repo&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%2Fla7mqn4n8kcgkd587nhz.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%2Fla7mqn4n8kcgkd587nhz.png" alt="Image description" width="500" height="183"&gt;&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;4. Activate the monorepo switch. This signifies that the docs folder exists within another project in a single repo.&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%2F8m3tzxyjneqnahspx2a0.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%2F8m3tzxyjneqnahspx2a0.png" alt="Image description" width="515" height="260"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Enter **docs&lt;/strong&gt; as the path to the &lt;code&gt;mint.json&lt;/code&gt; file in the new field that appears.**&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%2Foyhuns6ry5s6j38dbkzl.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%2Foyhuns6ry5s6j38dbkzl.png" alt="Image description" width="500" height="120"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Click the save button to save changes.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Your documentation can be accessed via the link displayed in the overview tab of your dashboard&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%2Fvza3n7yqbpu5t35hzjcq.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%2Fvza3n7yqbpu5t35hzjcq.png" alt="Image description" width="700" height="184"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Updating the Project
&lt;/h2&gt;

&lt;p&gt;You’re most likely to make changes to your project and may need to redeploy it. &lt;/p&gt;

&lt;p&gt;After making any updates in your project, ensure you push the changes to Github. Mintlify automatically picks up the new changes and updates your docs promptly. &lt;/p&gt;

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

&lt;p&gt;In this tutorial, you learnt how to build documentation for a Python library using the docs-as-code approach.&lt;/p&gt;

&lt;p&gt;Docs-as-code promotes collaboration and continuous integration on a project. When it comes to open source, docs-as-code allows people to collaborate seamlessly on a project while maintaining proper documentation that is up-to-date.&lt;/p&gt;

&lt;p&gt;There are different REST APIs without SDKs or programming libraries. Select one that interests you and create something similar.&lt;/p&gt;

&lt;p&gt;Keep building 👩‍💻!&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQs
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;How can I test my documentation?&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This feature is often used on large projects with multiple contributors. Documentation testing is run automatically when a pull request is made to the Project. If the test is successful, the changes are merged. Read this guide on how swimm offers automatic documentation testing to learn more.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Can I replicate this project in other programming languages?&lt;/em&gt;&lt;/strong&gt;&lt;br&gt;
Yes you can. Follow the procedures in this guide to get a similar result in your preferred language.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;strong&gt;Are there other documentation sites except Mintlify?&lt;/strong&gt;&lt;/em&gt;&lt;br&gt;
Yes there are other documentation sites you can use. Some of them include: Gitbook, Readme, Docusaurus, etc.&lt;/p&gt;

</description>
      <category>documentation</category>
      <category>python</category>
      <category>programming</category>
    </item>
    <item>
      <title>How to build and deploy a Python library</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Tue, 21 Jan 2025 12:14:23 +0000</pubDate>
      <link>https://forem.com/emiloju/how-to-build-and-deploy-a-python-library-4al2</link>
      <guid>https://forem.com/emiloju/how-to-build-and-deploy-a-python-library-4al2</guid>
      <description>&lt;p&gt;In software programming, there are lots of repetitive tasks which are popular in most projects. These tasks often require the same code and logic to be completed. This poses a huge problem for programmers as they always have to rewrite the same code and logic for each project. Examples of such tasks include validating an email, generating random strings, etc. &lt;/p&gt;

&lt;p&gt;A popular solution to this problem is by packaging the code into an installable package (also known as a library). The package can then be installed and used in every project without having to rewrite the logic. In most cases, such packages are always open-sourced and available for the public to use for free.&lt;/p&gt;

&lt;p&gt;This practice is common in most programming languages such as Python, JavaScript, etc. Through this tutorial, you will learn how to build and deploy a Python library to Pypi - the official repository for Python libraries.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Overview
&lt;/h2&gt;

&lt;p&gt;This tutorial will guide you on how to build and deploy an open-source Python library that wraps a REST API and its endpoints. &lt;/p&gt;

&lt;h3&gt;
  
  
  The Python Library
&lt;/h3&gt;

&lt;p&gt;The library you will build is a wrapper for the Exchange Rates REST API by &lt;a href="https://www.abstractapi.com/" rel="noopener noreferrer"&gt;Abstract API&lt;/a&gt;. The API has three endpoints which include the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;"/live":&lt;/strong&gt; To get live exchange rates between currencies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"/convert":&lt;/strong&gt; To get live exchange rates between two or more currencies by specifying a base amount, e.g 5 USD to GBP. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"/historical":&lt;/strong&gt; To get past exchange rates between currencies within a specific period. &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The library will allow developers to communicate with these endpoints through regular Python code. Also, you can use this API without declaring the endpoints every time you need to use it&lt;/p&gt;

&lt;h2&gt;
  
  
  Project requirements
&lt;/h2&gt;

&lt;p&gt;To take this tutorial, you are required to have a prior knowledge of coding with Python, and concepts such as Object-oriented programming (OOP), Python functions, etc. &lt;/p&gt;

&lt;p&gt;You should also understand how to use git, create a Github repository(repo), and push a project to Github.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tools and Packages
&lt;/h3&gt;

&lt;p&gt;You need the following tools for this guide:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python:&lt;/strong&gt; A Python interpreter(&amp;gt;=3.9) for running Python files.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requests:&lt;/strong&gt; A Python library used for making HTTP requests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Abstract API account:&lt;/strong&gt; You need an &lt;a href="https://www.abstractapi.com/" rel="noopener noreferrer"&gt;AbstractAPI&lt;/a&gt; account to access the API key for the Exchange rates API(steps will be provided in the tutorial). &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dotenv:&lt;/strong&gt; A Python library for handling environment variables.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Poetry:&lt;/strong&gt; A Python dependency management and packaging tool.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Create a new project.
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to create a new project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new folder using the terminal/command line with the &lt;br&gt;
     command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;mkdir&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exchangeLibrary&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install &lt;code&gt;virtualenv&lt;/code&gt; with pip:&lt;/strong&gt;&lt;br&gt;
     Run the command below to install&lt;code&gt;virtualenv&lt;/code&gt; (skip the process if &lt;br&gt;
     you have it installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;virtualenv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Change your working folder to the exchangeLibrary folder:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;cd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exchangeLibrary&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Create and activate a new virtual environment:&lt;/strong&gt;&lt;br&gt;
     Run the command below to create a env virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;virtualenv&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once it has been created, activate it with any of the commands below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;On Windows (Command prompt):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;\env\Scripts\activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;On Linux/macOS:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;env/bin/activate&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Install required packages:&lt;/strong&gt;&lt;br&gt;
     Run the command below to install required packages:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;dotenv&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Open the folder in a code editor like Pycharm/VScode:&lt;/strong&gt;&lt;br&gt;
     This is required to write the code for the library. &lt;/p&gt;
&lt;h2&gt;
  
  
  Setup project resources
&lt;/h2&gt;

&lt;p&gt;The project’s resources are the necessary folders and files required to build the library. Open up the project in a code editor like Pycharm or VScode.&lt;/p&gt;

&lt;p&gt;Do the following to add new files to your project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new folder in your project:&lt;/strong&gt; &lt;br&gt;
The folder must be named &lt;code&gt;src&lt;/code&gt;. In this folder, add a new folder named &lt;code&gt;exchange_python&lt;/code&gt;. Also, within the &lt;code&gt;exchange_python&lt;/code&gt; folder, add a &lt;code&gt;__init__.py&lt;/code&gt; and &lt;code&gt;exchange.py&lt;/code&gt; file. Your project file structure should now look like the image below:&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%2F6g5onrmi4ide4v0dowlp.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%2F6g5onrmi4ide4v0dowlp.png" alt="Screenshot of the project's file structure (1)" width="311" height="162"&gt;&lt;/a&gt;&lt;br&gt;
The &lt;code&gt;src&lt;/code&gt; folder separates the library files from the rest of the project. The &lt;code&gt;exchange_python&lt;/code&gt; folder is the main folder which will contain the files required by the Python library. The  &lt;code&gt;__init__.py&lt;/code&gt; and &lt;code&gt;exchange.py&lt;/code&gt; file will contain the library's code (more information on this later).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Create a new file in your project:&lt;/strong&gt; &lt;br&gt;
The file should be named &lt;code&gt;test.py&lt;/code&gt; which will be used to write tests for the Python library.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Get api key from AbstractAPI:&lt;/strong&gt;&lt;br&gt;
Follow the steps below to get an API key for the Exchange Rates API:&lt;br&gt;
   &lt;strong&gt;a.&lt;/strong&gt; Sign up on AbstractAPI &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%2F1p9o5ng2bqstx1g4v7oq.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%2F1p9o5ng2bqstx1g4v7oq.png" alt="Abstract API signup page" width="500" height="380"&gt;&lt;/a&gt;&lt;br&gt;
   &lt;strong&gt;b.&lt;/strong&gt; Login to your dashboard&lt;br&gt;
   &lt;strong&gt;c.&lt;/strong&gt; Hover your cursor on the left panel of your dashboard&lt;br&gt;
   &lt;strong&gt;d.&lt;/strong&gt; Select the Exchange Rates API &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%2Fl5oxyznxms63tg45lqev.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%2Fl5oxyznxms63tg45lqev.png" alt="User dashboard on Abstract API" width="700" height="170"&gt;&lt;/a&gt;&lt;br&gt;
   &lt;strong&gt;e.&lt;/strong&gt; Get the API key labelled &lt;strong&gt;Primary key&lt;/strong&gt; on the new page. &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%2Feszrtgg3h46h134trebn.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%2Feszrtgg3h46h134trebn.png" alt="User's API key for Abstract API's Exchange Rates API." width="500" height="210"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Create environment variables:&lt;/strong&gt; &lt;br&gt;
Create a new &lt;code&gt;.env&lt;/code&gt; file in your project. Add your API key to this file as seen below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_KEY=&amp;lt;your-api-key-here&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The file structure for your project should look like the image below:&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%2F6pa9qa1n1ah6oqtzgyba.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%2F6pa9qa1n1ah6oqtzgyba.png" alt="Screenshot of the project's file structure (2)" width="262" height="147"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Build the API wrapper
&lt;/h2&gt;

&lt;p&gt;The next step is to write a Python class as a wrapper for the Exchange Rates API. Each endpoint in this API will be represented as a method within the wrapper. Add the code below to the &lt;code&gt;exchange.py&lt;/code&gt; file:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExchangeRatesAPI&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;api_key&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;api_key&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;api_key&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;__ratesURL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://exchange-rates.abstractapi.com/v1/&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;__type_validation&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;type_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;arg&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="nf"&gt;isinstance&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;type_&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TypeError&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;Expected &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;type_&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; but got &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;arg&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;__name__&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, the &lt;code&gt;ExchangeRatesAPI&lt;/code&gt; class has two attributes - an &lt;strong&gt;api-key&lt;/strong&gt; to authenticate users, and the &lt;strong&gt;API's base URL&lt;/strong&gt;. It also has a &lt;code&gt;__type_validation()&lt;/code&gt; method which will be used to validate users’ input. Both the attributes and method are prefixed with double &lt;strong&gt;underscore(__)&lt;/strong&gt;. This keeps them private and not easily accessible outside the class. &lt;/p&gt;

&lt;p&gt;Although, it is not totally private as there are ways around it. But that is beyond the scope of this tutorial.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap the /live endpoint
&lt;/h3&gt;

&lt;p&gt;This endpoint accepts three request parameters - &lt;strong&gt;api-key(required)&lt;/strong&gt;, the &lt;strong&gt;base currency code(required)&lt;/strong&gt;, and the &lt;strong&gt;target currency code(optional)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;The base currency is that currency you want to get the exchange rates for, relative to other currencies. For example, I may want to know the rate at which USD exchanges with other currencies of the world. Therefore, USD is the base currency code.&lt;/p&gt;

&lt;p&gt;The target currency code is optional and limits the API response to the selected currencies. For example, CAD to USD, CAD, and GBP. This endpoint will be represented with the &lt;code&gt;live()&lt;/code&gt; method. Here is the code below:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExchangeRatesAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;live&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;base&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;params&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&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="nf"&gt;__type_validation&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;base&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base&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;base&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&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;target&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="nf"&gt;__type_validation&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;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&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;target&lt;/span&gt;


        &lt;span class="n"&gt;url&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;__ratesURL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;live&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;request&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json_response&lt;/span&gt; &lt;span class="o"&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;span class="n"&gt;json_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;status&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;live()&lt;/code&gt; method, the required parameters are set as positional arguments, while optional ones are set as keyword arguments. Type validation is also performed on each argument before they are added to the request parameters. &lt;/p&gt;

&lt;p&gt;Finally, a request is sent to the &lt;em&gt;/live&lt;/em&gt; endpoint of the API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap the /historical endpoint
&lt;/h3&gt;

&lt;p&gt;The request parameters are, &lt;strong&gt;api-key(required)&lt;/strong&gt;, &lt;strong&gt;base currency code(required)&lt;/strong&gt;, &lt;strong&gt;date(required)&lt;/strong&gt; and &lt;strong&gt;target currencies' codes(optional)&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;This is similar to the &lt;em&gt;/live&lt;/em&gt; endpoint except that it is dated in the past. The format for the date argument is &lt;strong&gt;YYYY-MM-DD&lt;/strong&gt;. This endpoint will be represented with the &lt;code&gt;historical()&lt;/code&gt; method. Below is the code for the method:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExchangeRatesAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
   &lt;span class="bp"&gt;...&lt;/span&gt;
   &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;historical&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;base&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;date&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;params&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&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="nf"&gt;__type_validation&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;base&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="nf"&gt;__type_validation&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;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base&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;base&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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;date&lt;/span&gt;


        &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;target&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&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;target&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="nf"&gt;__type_validation&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;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&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;target&lt;/span&gt;


        &lt;span class="n"&gt;url&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;__ratesURL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;historical&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;request&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json_response&lt;/span&gt; &lt;span class="o"&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;span class="n"&gt;json_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;status&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Wrap the /convert endpoint
&lt;/h3&gt;

&lt;p&gt;The request parameters for this endpoint are &lt;strong&gt;api-key(required)&lt;/strong&gt;, &lt;strong&gt;base currency(required)&lt;/strong&gt;, &lt;strong&gt;target currency(required)&lt;/strong&gt;, &lt;strong&gt;date(optional)&lt;/strong&gt;, and &lt;strong&gt;base_amount(optional)&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The date parameter is passed if you want the results to refer to a date in the past. The base amount is any amount of the base currency you want to convert. For example, 10 USD to CAD, where USD is the base currency and CAD is the target currency. &lt;/p&gt;

&lt;p&gt;This endpoint only accepts one target currency per request. It will be represented with the &lt;code&gt;convert()&lt;/code&gt; method. Below is the code for this method:&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="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;ExchangeRatesAPI&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="bp"&gt;...&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;convert&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;base&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;target&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="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;params&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;api_key&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="nf"&gt;__type_validation&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;base&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="nf"&gt;__type_validation&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;target&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base&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;base&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;target&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;target&lt;/span&gt;
        &lt;span class="n"&gt;date&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;date&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;base_amount&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;kwargs&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;base_amount&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="bp"&gt;None&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;date&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="nf"&gt;__type_validation&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;date&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;date&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;date&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;base_amount&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="nf"&gt;__type_validation&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;base_amount&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;base_amount&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;base_amount&lt;/span&gt;


        &lt;span class="n"&gt;url&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;__ratesURL&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;convert&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;request&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="n"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;params&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;json_response&lt;/span&gt; &lt;span class="o"&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;span class="n"&gt;json_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;status&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;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;status_code&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;json_response&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note that type enforcement is done for all arguments, e.g &lt;code&gt;base: str&lt;/code&gt;. While this may not validate each argument, it helps users know the data type for each argument when using the library.&lt;/p&gt;

&lt;h2&gt;
  
  
  Test the API wrapper
&lt;/h2&gt;

&lt;p&gt;Bugs are inevitable. Writing tests is important before publishing the library. Python's unittest library will be used to test the &lt;code&gt;ExchangeRatesAPI&lt;/code&gt; class. For a complete guide on unittest, read the &lt;a href="https://www.dataquest.io/blog/unit-tests-python/#:~:text=A%20Beginner%E2%80%99s%20Guide%20to%20Unit%20Tests%20in%20Python,...%204%20Running%20Tests%20from%20the%20Command-Line%20" rel="noopener noreferrer"&gt;Beginner’s guide to unit tests in Python&lt;/a&gt;. Follow the steps below to test the codebase:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Open the &lt;strong&gt;init&lt;/strong&gt;.py file and add the code below:&lt;/strong&gt;&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;.exchange&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ExchangeRatesAPI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This allows you to import and use the &lt;code&gt;ExchangeRatesAPI&lt;/code&gt; class as &lt;br&gt;
a package and not via a relative file path.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Add the code below to the test.py file:&lt;/strong&gt;&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;exchange_python&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ExchangeRatesAPI&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;unittest&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dotenv&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="c1"&gt;# Configure environment variables
&lt;/span&gt;&lt;span class="n"&gt;dotenv&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_dotenv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;


&lt;span class="n"&gt;exchange_api_key&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;environ&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;API_KEY&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="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;exchange&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ExchangeRatesAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange_api_key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;TestExchangeAPI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unittest&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TestCase&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;test_live&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsNotNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;live&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD&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;test_convert&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsNotNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;convert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD&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;BRL&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;test_historical&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;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;assertIsNotNone&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;exchange&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;historical&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;USD&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;2022-02-02&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="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;unittest&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;&lt;strong&gt;3. Run the test script:&lt;/strong&gt;&lt;br&gt;
     If there are no errors, you should see a message like the one &lt;br&gt;
     below in the console:&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%2Fdwzl782ij7yqymtgfd9f.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%2Fdwzl782ij7yqymtgfd9f.png" alt="Screenshot of test status" width="307" height="60"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Deploy the project
&lt;/h2&gt;

&lt;p&gt;This section will show you how to deploy your library&lt;/p&gt;
&lt;h3&gt;
  
  
  Setup deployment requirements
&lt;/h3&gt;

&lt;p&gt;Follow the steps below to set up deployment requirements:&lt;br&gt;
&lt;strong&gt;1. Create a .gitignore file in your project&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;2. Add the following files to it:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env
.env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Create a &lt;em&gt;README.md&lt;/em&gt; file and write a brief description about the &lt;br&gt;
     library&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;4. Create a pyproject.toml file in your project&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;strong&gt;5. Add the following content to this file:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[tool.poetry]
name = “&amp;lt;your python library name&amp;gt;” 
version = "0.1.0"
description = "&amp;lt;a description for the library&amp;gt;"
authors = ["&amp;lt;Your name here&amp;gt; &amp;lt;Your email here&amp;gt;"]
license = "MIT"
readme = "README.md"
repository = "&amp;lt;a link to the your project’s github repo&amp;gt;"
keywords = []
include = [
    { path = "src"}
]

[tool.poetry.dependencies]
python = "&amp;gt;=3.8"
requests = "&amp;gt;=2.32.2"

[build-system]
requires = [
    "poetry-core"
]

build-backend = "poetry.core.masonry.api"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This file contains the configuration details for your library. Fill in the spaces enclosed with these symbols – &lt;em&gt;“&amp;lt;&amp;gt;”&lt;/em&gt;. The &lt;code&gt;include&lt;/code&gt; variable consists of folders that contain the library files. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Proceed to Github to create a new repo.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Initialize git in your project with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Connect the repo to your project with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;git remote add origin “https://github.com/&amp;lt;your-github-username&amp;gt;/&amp;lt;your-github-repo-name&amp;gt;”
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. Update the pyproject.toml file with the link to your repo:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;repository = "https://github.com/&amp;lt;your-github-username&amp;gt;/&amp;lt;your-github-repo-name&amp;gt;"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;10. Push your code to Github.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Deploy the library
&lt;/h3&gt;

&lt;p&gt;Python libraries are deployed on &lt;a href="http://pypi.org" rel="noopener noreferrer"&gt;pypi.org&lt;/a&gt;. Before you continue, use the search bar on pypi to check if a library with the same name as yours exists.&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%2Fff8k9g7yhabdoyskqzni.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%2Fff8k9g7yhabdoyskqzni.png" alt="Screenshot of Pypi's homepage" width="800" height="263"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you find one, select a different name for your library and update the &lt;code&gt;pyproject.toml&lt;/code&gt; file with the new name.&lt;/p&gt;

&lt;p&gt;You need to create an account on Pypi to get an &lt;strong&gt;api-key&lt;/strong&gt; for deployment. The following steps will show you how to do this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Sign up and verify your email address:&lt;/strong&gt;&lt;br&gt;
This is required to complete the registration. A verification link will be sent to your mail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Set up 2 factor authentication:&lt;/strong&gt;&lt;br&gt;
The 2-factor authentication (2FA) is meant to protect your account. You cannot deploy a Python package without completing this step.&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%2F1wy7epuaslr37g5pgw6a.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%2F1wy7epuaslr37g5pgw6a.png" alt="Setting up 2FA on Pypi" width="800" height="268"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Click Generate recovery code to begin THE 2FA:&lt;/strong&gt;&lt;br&gt;
The final part OF THE 2FA requires an authenticator app to scan a QR code and complete the 2FA&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%2F7ux6vbqs4117c2flfj1v.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%2F7ux6vbqs4117c2flfj1v.png" alt="Generating recovery code from Pypi" width="602" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Download and configure an authenticator app:&lt;/strong&gt;&lt;br&gt;
Use an authenticator browser extension like &lt;a href="https://chromewebstore.google.com/detail/authenticator/bhghoamapcdpbohphigoooaddinpkbai" rel="noopener noreferrer"&gt;authenticator&lt;/a&gt; by &lt;a href="http://authenticator.cc" rel="noopener noreferrer"&gt;authenticator.cc&lt;/a&gt;. It works for both Chrome and Edge browsers. Once installed you can use it to scan the QR code from Pypi&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%2Ficak0kwqbdf8l7eadf8q.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%2Ficak0kwqbdf8l7eadf8q.png" alt="Screesnhot of authenticator web extension" width="800" height="180"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Get the API token:&lt;/strong&gt;&lt;br&gt;
Click on the Account settings tab in your dashboard. Scroll to the &lt;br&gt;
bottom of the page to get an API token to deploy your project (click &lt;br&gt;
the Add API token button)&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%2F43qt51k8r5ysshgyfrw7.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%2F43qt51k8r5ysshgyfrw7.png" alt="Screenshot of how to get an API token on Pypi" width="792" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;6. Open a terminal/command line in your project (if you are using &lt;br&gt;
VScode) or open your project in a new terminal and activate the &lt;br&gt;
virtual environment&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;7. Run the command below to build your package (This will generate a &lt;br&gt;
dist file in your project):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;8. Set your pypi api token with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;config&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pypi-token.pypi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;your&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pypi&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;api-token&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;9. Publish your library with the command below:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your Python package will be published on Pypi. You can always find the package in your dashboard or via Pypi’s search bar.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project maintenance
&lt;/h2&gt;

&lt;p&gt;It definitely does not end here. Overtime, you may need to refactor the codebase or make major changes in your project. After any update, ensure you push the changes to Github. &lt;/p&gt;

&lt;p&gt;Run the following commands in the terminal to update the library with the new changes:&lt;br&gt;
a. Activate virtual environment and build the package with the command &lt;br&gt;
   below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;build&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;b. Publish to Pypi:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;poetry&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;publish&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Note&lt;/strong&gt;: The &lt;code&gt;pyproject.toml&lt;/code&gt; file manages your package’s version with the version variable. You may need to update the &lt;code&gt;version&lt;/code&gt; when you make new updates. This is useful to track your package’s version history. &lt;br&gt;
For example, if your project’s version is &lt;code&gt;0.1.0&lt;/code&gt;. You can change it to &lt;code&gt;0.1.1&lt;/code&gt; or &lt;code&gt;1.0.0&lt;/code&gt;, depending on how you want the version’s progression to be.&lt;/p&gt;
&lt;h2&gt;
  
  
  Installation and usage
&lt;/h2&gt;

&lt;p&gt;Once the library is published, you can install it by running the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;pip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;install&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;your-python-library-name&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can now import the library in your project as seen below:&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;exchange_python&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ExchangeRatesAPI&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;In this tutorial, you learnt how to build and deploy a Python library to Pypi. You also learnt how to update your project and re-publish the changes. &lt;/p&gt;

&lt;p&gt;Building and deploying a Python library is an exciting process that allows you to share your code with the world. By following the steps outlined in this guide, you now have the knowledge to create, package, and publish your own libraries. &lt;/p&gt;

&lt;p&gt;Whether you're solving common problems, providing useful utilities, or simply sharing your unique approach, your contribution helps make Python development more accessible and robust. &lt;/p&gt;

</description>
      <category>python</category>
      <category>softwaredevelopment</category>
      <category>programming</category>
      <category>testing</category>
    </item>
    <item>
      <title>Wrap and Render Multiline Text on Images Using Python's Pillow Library</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Mon, 13 Jan 2025 12:27:00 +0000</pubDate>
      <link>https://forem.com/emiloju/wrap-and-render-multiline-text-on-images-using-pythons-pillow-library-2ppp</link>
      <guid>https://forem.com/emiloju/wrap-and-render-multiline-text-on-images-using-pythons-pillow-library-2ppp</guid>
      <description>&lt;p&gt;Python takes the lead as a top programming language used for image manipulation. This high-level programming language boasts a vast collection of open-source libraries used in image manipulation and processing. One of such libraries is &lt;strong&gt;Pillow&lt;/strong&gt;. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Pillow&lt;/strong&gt; is an image processing library in Python commonly used to resize, crop, brighten, or annotate an image. It is popular among developers because of its simplicity and extensive documentation.&lt;/p&gt;

&lt;p&gt;However, there is a particular problem with adding text annotations or labels on images using Pillow. Texts often do not wrap to the next line when it gets to the end of the text box. Also, there is no function or&lt;br&gt;
module within the Pillow library that allows you to do this. The only choice is to write the logic to achieve this functionality. &lt;/p&gt;

&lt;p&gt;This tutorial will show you how to add a text box where the text wraps to the next line automatically when it gets to the end of the box using Pillow in Python. This functionality will allow you to add text annotations or labels correctly to your images. Below is a final look of what you will build by the end of this tutorial:&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%2F4l9az6hprm4nxzatvks1.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%2F4l9az6hprm4nxzatvks1.png" alt="Screenshot of annotated Dev.to profile" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The image above is a screenshot of my &lt;em&gt;&lt;strong&gt;Dev.to&lt;/strong&gt;&lt;/em&gt; profile which will be used throughout this tutorial. The green text box is the text annotation&lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;This tutorial requires you to have a knowledge of basic Python concepts such as conditional statements(if, else), for loops, etc. You also need the following tools and software:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Python3+&lt;/strong&gt;: A python interpreter for running Python scripts &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pillow&lt;/strong&gt;: An image manipulation library in Python.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A code editor&lt;/strong&gt;: A code editor like Pycharm, VScode, etc. to write code. &lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Create a new project
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to create a new project:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Create a new folder using the terminal/command line with the &lt;br&gt;
     command below&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 mkdir image_annotation
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;2. Install virtualenv with pip&lt;/strong&gt;:&lt;br&gt;
     Run the command below to install virtualenv (skip the process if &lt;br&gt;
     you have it installed):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 pip install virtualenv
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;3. Change your working folder to the &lt;em&gt;image_annotation&lt;/em&gt; folder&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 cd image_annotation
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;4. Create a new virtual environment&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 virtualenv env
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;5. Activate the virtual environment with any of the commands below&lt;/strong&gt;:&lt;br&gt;
     &lt;strong&gt;On Windows (Command prompt):&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 .\env\Scripts\activate
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; **On Linux/macOS:**
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 source env/bin/activate
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;6. Install the Pillow library with the command below&lt;/strong&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; ```
 pip install pillow
 ```
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Open the project in a code editor, then create a new python file named &lt;code&gt;script.py&lt;/code&gt; in the project folder.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prepare base image
&lt;/h2&gt;

&lt;p&gt;The image you want to annotate is the base image. Open the image and prepare it for annotation using Pillow's &lt;code&gt;ImageDraw&lt;/code&gt; module. Write the code below to the &lt;code&gt;script.py&lt;/code&gt; file to prepare the base image:&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;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;
&lt;span class="n"&gt;image_file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;path_to_image&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="c1"&gt;# Open image
&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image_file&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Initialize ImageDraw
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Annotate the image
&lt;/h2&gt;

&lt;p&gt;Pillow can be used to add both &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#example-draw-partial-opacity-text" rel="noopener noreferrer"&gt;plain text&lt;/a&gt; and a text box with a background fill. Also, the text can either be &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.text" rel="noopener noreferrer"&gt;single line&lt;/a&gt; or &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.multiline_text" rel="noopener noreferrer"&gt;multiline&lt;/a&gt;. This tutorial focuses on how to add a multiline text box to an image. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;ImageDraw.multiline_text()&lt;/code&gt; method is used to add a multiline text to an image using Pillow. However, this only adds a plain text without a background fill. The &lt;code&gt;ImageDraw.rectangle()&lt;/code&gt; method is used to add a text box with a background fill that a body of text can be written on.&lt;/p&gt;

&lt;p&gt;Add the code below to the &lt;code&gt;script.py&lt;/code&gt; file to draw a multiline text box on your image:&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="bp"&gt;...&lt;/span&gt;

&lt;span class="c1"&gt;# Set text, font, and max width
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a user profile on dev.to.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;\
       &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;It contains the user name, profile picture, and bio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arial.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Use default font if you don't have this
&lt;/span&gt;&lt;span class="n"&gt;max_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt; &lt;span class="c1"&gt;# Width for the text box
&lt;/span&gt;
&lt;span class="c1"&gt;# Calculate positions
&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# Starting position for the text box
&lt;/span&gt;&lt;span class="n"&gt;end_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="c1"&gt;# Ending position for the text box
&lt;/span&gt;

&lt;span class="c1"&gt;# Dimensions for the background box
&lt;/span&gt;&lt;span class="n"&gt;background_box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;

&lt;span class="c1"&gt;# Draw background box
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;background_box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Draw multiline text
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiline_text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&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;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;black&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;The code above sets the text, text-font, and width for the text box. Also, the &lt;code&gt;x&lt;/code&gt; and &lt;code&gt;y&lt;/code&gt; variable represents the point from where the drawing should start, while end_x and end_y represents the bottom-right edge of the box. The width and height of the text box are 200 and 50 respectively. &lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;ImageDraw.rectangle()&lt;/code&gt; and &lt;code&gt;ImageDraw.multiline_text()&lt;/code&gt; method are used to draw the text box and a multiline text on the image respectively. The &lt;code&gt;Image.show()&lt;/code&gt; method is used to display the processed image. You can save it instead with: &lt;code&gt;image.save("new_image.png")&lt;/code&gt;. Below is the result:&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%2Fb4u1q60zcfirpvgs2a5e.jpeg" 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%2Fb4u1q60zcfirpvgs2a5e.jpeg" alt="Image description" width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;There are still some errors with the annotation above. The multiline text should automatically wrap to the next line when it gets to the end of the text box. See how to do this in the next section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Enable automatic text wrapping
&lt;/h2&gt;

&lt;p&gt;The new line &lt;em&gt;(\n)&lt;/em&gt; symbol is used to determine when to move the text to a new line. As seen in the previous example, the body of text after the newline &lt;em&gt;(\n)&lt;/em&gt; is moved to the next line. However, in a real life use case where the text length is usually dynamic, it can be difficult to determine where and when to add a newline &lt;em&gt;(\n)&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Pillow's &lt;code&gt;ImageDraw&lt;/code&gt; module has a &lt;code&gt;.textlength()&lt;/code&gt; attribute that can be used to calculate the length of a text. The value can then be compared against the width of the text box to determine where to add a &lt;br&gt;
newline &lt;em&gt;(\n)&lt;/em&gt;. &lt;/p&gt;

&lt;p&gt;Create a new function named &lt;code&gt;wrap_text()&lt;/code&gt; at the top of the &lt;code&gt;script.py&lt;/code&gt; file (immediately after the import statements). It will contain the logic for the automatic text wrapping. Below is the code for this function:&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="bp"&gt;...&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap_text&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;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Wraps text to fit within the max_width.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&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;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# Holds each line in the text box
&lt;/span&gt;    &lt;span class="n"&gt;current_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# Holds each word in the current line under evaluation.
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Check the width of the current line with the new word added
&lt;/span&gt;        &lt;span class="n"&gt;test_line&lt;/span&gt; &lt;span class="o"&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_line&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textlength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&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;width&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current_line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&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="c1"&gt;# If the line is too wide, finalize the current line and start a new one
&lt;/span&gt;            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;current_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Add the last line
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_line&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;lines&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;

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

&lt;/div&gt;



&lt;p&gt;In the &lt;code&gt;wrap_text()&lt;/code&gt; function, the body of text is split into its individual words. The length of the first word is checked against the text-box's width. If the word length is lesser, it is added to &lt;br&gt;
the &lt;code&gt;current_line&lt;/code&gt; list. Subsequently, each word is added to this list, combined with the existing ones, then everything is checked against the text-box's width. &lt;/p&gt;

&lt;p&gt;If a new word causes the entire text length to be greater than the box's width, the word is moved to the next line. The same logic is applied to the next line until the entire body of text is displayed on screen. &lt;/p&gt;

&lt;p&gt;Finally, the &lt;code&gt;lines&lt;/code&gt; list is returned after the last line has been added. Add the code below immediately after the &lt;code&gt;text&lt;/code&gt;, &lt;code&gt;font&lt;/code&gt;, and &lt;code&gt;max_width&lt;/code&gt; variables:&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="bp"&gt;...&lt;/span&gt;
&lt;span class="c1"&gt;# Wrap text into lines
&lt;/span&gt;&lt;span class="n"&gt;wrapped_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrap_text&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;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Where &lt;code&gt;text&lt;/code&gt; is the body of text to be added, &lt;code&gt;font&lt;/code&gt; is the text font, &lt;code&gt;max width&lt;/code&gt; is the box's width, and &lt;code&gt;draw&lt;/code&gt; is an instance of the &lt;code&gt;ImageDraw&lt;/code&gt; module. &lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;draw.multiline_text()&lt;/code&gt; method with the code below:&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="bp"&gt;...&lt;/span&gt;

&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wrapped_lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;+&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="c1"&gt;# Draw multiline text.
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiline_text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Remove the new line* (\n)* symbol from the text and run the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;text = "This is a user profile on dev.to. It contains the user name, profile picture, and bio"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The result of this code will be a text annotation that still overflows the box's height. While the text can now automatically adjust with the text box's width, the box's height remains fixed which causes the text to overflow outside the box.&lt;/p&gt;

&lt;h2&gt;
  
  
  Set up a dynamic box height
&lt;/h2&gt;

&lt;p&gt;A dynamic box height is one that is determined by the number of lines occupied by the text. The first step is to change the box's &lt;code&gt;end_y&lt;/code&gt; variable to a dynamic value as seen in the code below:&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="bp"&gt;...&lt;/span&gt;
&lt;span class="n"&gt;end_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_lines&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# Ending position for the text box
&lt;/span&gt;&lt;span class="bp"&gt;...&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The equation - &lt;code&gt;y + (24 * int(len(wrapped_lines)))&lt;/code&gt; was arrived at after a couple of trials. It appeared to be the best solution to get a dynamic box height for this use case. The &lt;code&gt;wrapped_lines&lt;/code&gt; list contains all the lines to be added to the text box, therefore, the length of this list equals the total lines of text for the text box.&lt;/p&gt;

&lt;p&gt;Below is the result: &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%2F951g9u78uwk4cngg2n8j.jpeg" 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%2F951g9u78uwk4cngg2n8j.jpeg" alt="Image description" width="800" height="273"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You may need to multiply the total lines by different values to get a perfect solution for your use case. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE: Difference in spacing, font size, etc. affects the equation. For example, this tutorial uses a font size of 16. If your project uses a different font-size, you will need to tweak the values used in the equation to get a dynamic box height that fits your use case.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add text padding
&lt;/h2&gt;

&lt;p&gt;Also, the text is too close to the box's corners affecting its readability and style. This can be corrected by padding the text within the box. Add a new &lt;code&gt;padding&lt;/code&gt; variable to the &lt;code&gt;script.py&lt;/code&gt; file and change the text box dimension. Here is the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
padding = 10

background_box = [(x - padding, y - padding), (end_x + padding, end_y + padding)]
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This code allows for spacing between the text and the box's corners as seen in the image below:&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%2Fyut042vigp4rob95srfy.jpeg" 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%2Fyut042vigp4rob95srfy.jpeg" alt="Image description" width="800" height="258"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Add a pointer
&lt;/h2&gt;

&lt;p&gt;A pointer makes it easy to determine which part of the image the annotation/label is meant for. The pointer should come before the annotation. This means the pointer will be drawn on the text box's current position, while the text box will shift to the right. &lt;/p&gt;

&lt;p&gt;Therefore, the x axis for the text box will be tied to a new &lt;code&gt;box_x&lt;/code&gt; variable. This change must also reflect in other variables where the box's x axis is used. Here is the updated code below:&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;# Calculate positions
&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# Pointer position
&lt;/span&gt;&lt;span class="n"&gt;box_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="c1"&gt;# Box starting x position
&lt;/span&gt;&lt;span class="n"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# Text padding
&lt;/span&gt;&lt;span class="n"&gt;end_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_lines&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# Ending position for the text box
&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate total height for the background
&lt;/span&gt;&lt;span class="n"&gt;background_box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;box_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;


&lt;span class="c1"&gt;# Draw Pointer
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Draw background rectangle
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;background_box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wrapped_lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;+&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="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiline_text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;box_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, the &lt;a href="https://pillow.readthedocs.io/en/stable/reference/ImageDraw.html#PIL.ImageDraw.ImageDraw.circle" rel="noopener noreferrer"&gt;&lt;code&gt;ImageDraw.circle()&lt;/code&gt;&lt;/a&gt; method, where 10 is the radius, is used to draw the pointer at the specified point. The &lt;code&gt;box_x&lt;/code&gt; variable is the new value for the box's x axis.&lt;/p&gt;

&lt;p&gt;Below is the final code for the script.py file:&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;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wrap_text&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;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;
    Wraps text to fit within the max_width.
    &lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="n"&gt;words&lt;/span&gt; &lt;span class="o"&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;split&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# Holds each line in the text box
&lt;/span&gt;    &lt;span class="n"&gt;current_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="c1"&gt;# Holds the current line under evaluation.
&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;word&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;words&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="c1"&gt;# Check the width of the current line with the new word added
&lt;/span&gt;        &lt;span class="n"&gt;test_line&lt;/span&gt; &lt;span class="o"&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_line&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="n"&gt;width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;textlength&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&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;width&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;current_line&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;word&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="c1"&gt;# If the line is too wide, finalize the current line and start a new one
&lt;/span&gt;            &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_line&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="n"&gt;current_line&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;word&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

    &lt;span class="c1"&gt;# Add the last line
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;current_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;lines&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&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="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;current_line&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;lines&lt;/span&gt;



&lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;practise.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;draw&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageDraw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;


&lt;span class="c1"&gt;# Set text, font, and max width
&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;This is a user profile on dev.to. It contains the user name, profile picture, and bio&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="n"&gt;font&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ImageFont&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;truetype&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;arial.ttf&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# Use default font if you don't have this
&lt;/span&gt;&lt;span class="n"&gt;max_width&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;

&lt;span class="n"&gt;wrapped_lines&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;wrap_text&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;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate positions
&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;  &lt;span class="c1"&gt;# Pointer position
&lt;/span&gt;&lt;span class="n"&gt;box_x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;780&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt; &lt;span class="c1"&gt;# Box starting x position
&lt;/span&gt;&lt;span class="n"&gt;padding&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="c1"&gt;# Text padding
&lt;/span&gt;&lt;span class="n"&gt;end_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;box_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;max_width&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;24&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;wrapped_lines&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt; &lt;span class="c1"&gt;# Ending position for the text box
&lt;/span&gt;

&lt;span class="c1"&gt;# Calculate total height for the background
&lt;/span&gt;&lt;span class="n"&gt;background_box&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[(&lt;/span&gt;&lt;span class="n"&gt;box_x&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;end_x&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_y&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;padding&lt;/span&gt;&lt;span class="p"&gt;)]&lt;/span&gt;


&lt;span class="c1"&gt;# Draw Pointer
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;circle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# Draw background rectangle
&lt;/span&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;rectangle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;background_box&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;green&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;=&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;line&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wrapped_lines&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;description&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt; &lt;span class="o"&gt;+&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="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;multiline_text&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;box_x&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;description&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;font&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;white&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;spacing&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



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

&lt;p&gt;Image manipulation is not always as difficult as it may seem. While some image manipulation libraries cannot directly solve your problem with their modules, you can implement a specific solution for your use case using the available modules. That is the beauty of coding - &lt;em&gt;The ability to solve problems with custom and specific solutions&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;In this tutorial, you learnt how to use Python's Pillow library to annotate an image, add a multiline text box that automatically wraps to the next line, etc. You also learnt how to write mathematical equations that can help you with image manipulation.&lt;/p&gt;

&lt;p&gt;Refer to the &lt;a href="https://pillow.readthedocs.io/en/stable/index.html" rel="noopener noreferrer"&gt;Pillow documentation&lt;/a&gt; for detailed explanation on each module used. &lt;/p&gt;

</description>
      <category>python</category>
      <category>pillow</category>
      <category>programming</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>How to view API request examples in a ReadMe documentation.</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Mon, 30 Dec 2024 19:07:16 +0000</pubDate>
      <link>https://forem.com/emiloju/how-to-view-api-request-examples-in-a-readme-documentation-1p4l</link>
      <guid>https://forem.com/emiloju/how-to-view-api-request-examples-in-a-readme-documentation-1p4l</guid>
      <description>&lt;p&gt;Request examples are more than a mere addition to API documentation. They are necessary to easily onboard developers and increase API adoption. They cut development time by helping developers discover how to make a successful request to your API endpoints. &lt;/p&gt;

&lt;p&gt;Imagine a Fintech API like &lt;a href="https://plaid.com/docs/api/products/auth/" rel="noopener noreferrer"&gt;Plaid&lt;/a&gt; without request examples in the API documentation. Their customer service will have to deal with complaints from frustrated developers each day. Eventually, developers will abandon their service for a better alternative.&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%2Fz5wxi8j5bq4rwekrygdg.jpeg" 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%2Fz5wxi8j5bq4rwekrygdg.jpeg" alt="PLAID API REQUEST EXAMPLE" width="602" height="490"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;My recent experience with &lt;a href="https://developer.themoviedb.org/reference/intro/getting-started" rel="noopener noreferrer"&gt;The Movie Database (TMDB)&lt;/a&gt; API documentation underscores the importance of request examples in API documentation. It took me a couple of hours to figure out how to make a successful request to an endpoint because I couldn't access a request sample. However, I eventually found it in an unexpected place. &lt;a href="https://readme.com/" rel="noopener noreferrer"&gt;ReadMe&lt;/a&gt; on the other hand didn't make it easy. &lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;Certain endpoints in the TMDB API require a &lt;code&gt;RAW_BODY&lt;/code&gt; body parameter as seen in the image below:&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%2F4h3vvdbcvbennvx7mmmt.jpeg" 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%2F4h3vvdbcvbennvx7mmmt.jpeg" alt="Raw body JSON parameter in the TMDB API" width="800" height="355"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Without a request sample, it is difficult to come up with the correct &lt;code&gt;json&lt;/code&gt; data. Although, you may end up with a solution after a few google search, which is not a good user experience. &lt;/p&gt;

&lt;h2&gt;
  
  
  The solution
&lt;/h2&gt;

&lt;p&gt;A glance at an endpoint page on ReadMe, there is no sight of a request sample. Despite being a very important part of API documentation, ReadMe didn't do a good job to make it easily accessible. &lt;/p&gt;

&lt;p&gt;You can view a request sample for an endpoint on ReadMe through the steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click on the &lt;strong&gt;EXAMPLES&lt;/strong&gt; field on the right and select the 
&lt;strong&gt;Request Example&lt;/strong&gt; option.
&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%2Fgv7erpkhzhopvanbm7sx.jpeg" alt="Image description" width="779" height="293"&gt;
&lt;/li&gt;
&lt;li&gt;The request sample will be displayed as seen in the image below:&lt;/li&gt;
&lt;/ol&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%2Frmspgni7610d8a2arlk2.jpeg" 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%2Frmspgni7610d8a2arlk2.jpeg" alt="Image description" width="770" height="362"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;That's all it takes to view a sample request in a ReadMe documentation. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE&lt;/strong&gt;: A request sample will be available provided that the API documentation writer(s) add it to the documentation.&lt;/p&gt;

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

&lt;p&gt;This short article is useful for developers who may have to use an API with its documentation on ReadMe. If you also plan to use the TMDB API, this article will help you view sample request for each endpoint.&lt;/p&gt;

</description>
      <category>api</category>
      <category>documentation</category>
    </item>
    <item>
      <title>Building SaaS Faster with Ercas for SaaS: A Template for Indie Hackers</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Tue, 17 Dec 2024 09:27:49 +0000</pubDate>
      <link>https://forem.com/emiloju/building-saas-faster-with-ercas-for-saas-a-template-for-indie-hackers-200h</link>
      <guid>https://forem.com/emiloju/building-saas-faster-with-ercas-for-saas-a-template-for-indie-hackers-200h</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Indie hacking thrives on launching ideas quickly, but backend developers, especially in Nigeria, face significant challenges: limited resources, complex payment integrations, and the steep learning curve of building robust subscription systems. These issues slow down the journey from concept to execution.&lt;/p&gt;

&lt;p&gt;Ercas for SaaS (EFS), a &lt;a href="https://reflex.dev/docs/getting-started/introduction/" rel="noopener noreferrer"&gt;Reflex.dev&lt;/a&gt; template is designed to simplify subscription and payment management for SaaS applications. This template empowers developers with pre-built authentication, subscription logic, and payment integration with the ErcasPay API, reducing development time and enabling faster launches.&lt;/p&gt;

&lt;p&gt;This article demonstrates how to use the template, highlighting its features and offering a step-by-step guide to help developers kick-start their SaaS projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  Key features
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Subscription Management: The plug-in supports monthly plans with start and end dates, renewal logic, and plan expiry handling.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Payment Integration: It also contains pre-configured payment handling using ErcasPay API for secure transactions.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User Authentication: Ready-made sign-up and login functionality for user management.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Customizable Pricing Pages: Functional pages that require minimal styling knowledge.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Scalability: The best part is the modular design that allows you to add features and customize the project as your SaaS grows.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Project requirements
&lt;/h2&gt;

&lt;p&gt;The following is required for this project:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python 3.8+:&lt;/strong&gt; It is required to run your Python code &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;reflex:&lt;/strong&gt; A Python framework for building full-stack web apps using Python code (installation steps will be provided in the tutorial)&lt;/li&gt;
&lt;li&gt;Basic knowledge of Python and usage of the reflex Python web framework.&lt;/li&gt;
&lt;li&gt;Access to the ErcasPay API and API token. Create an account on &lt;a href="https://ercaspay.com/" rel="noopener noreferrer"&gt;ErcasPay&lt;/a&gt; to get an API token. You can also access the &lt;a href="https://docs.ercaspay.com/" rel="noopener noreferrer"&gt;docs&lt;/a&gt; here.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to install EFS
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to install EFS to your local environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new folder using the terminal or command prompt with the &lt;br&gt;
command below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;mkdir saas_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open this folder with the command below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd saas_projectb
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Clone the Ercas-for-saas repository on Github with the command &lt;br&gt;
below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;git clone https://github.com/Omotunde2005/Ercas-for-saas.git
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This will create a new folder named &lt;strong&gt;&lt;em&gt;Ercas-for-saas&lt;/em&gt;&lt;/strong&gt; in the &lt;strong&gt;&lt;em&gt;saas_project&lt;/em&gt;&lt;/strong&gt; folder. Open the &lt;strong&gt;&lt;em&gt;Ercas-for-saas&lt;/em&gt;&lt;/strong&gt; folder in a code editor like Vscode or Pycharm (Preferrably Vscode).&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding the project files
&lt;/h2&gt;

&lt;p&gt;The &lt;strong&gt;&lt;em&gt;Ercas-for-saas&lt;/em&gt;&lt;/strong&gt; folder has a file structure like the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;assets/
   favicon.io
ercas_saas/
   components/
       __init__.py
       container.py
   pages/
       __init__.py
       dashboard.py
       home.py
       login.py
       pricing.py
       signup.py
   state/
       __init__.py
       app.py
       auth.py
       base.py
   __init__.py
   ercas_api.py
   ercas_saas.py
   models.py
.gitignore
ReadMe.md
requirements.txt
rxconfig.py
test.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These files are arranged according to the reflex app file structure. Read this &lt;a href="https://reflex.dev/docs/getting-started/project-structure/" rel="noopener noreferrer"&gt;guide&lt;/a&gt; to get a grasp of reflex file structure and an overview of the reflex framework. Each folder and their content are quite self-explanatory. But for clarity, let's explore the main folders and their files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;strong&gt;&lt;em&gt;ercas_saas&lt;/em&gt;&lt;/strong&gt; folder is where you will work with the most. It contains your app pages, database models, styling, etc. &lt;/li&gt;
&lt;li&gt;The &lt;em&gt;&lt;strong&gt;components&lt;/strong&gt;&lt;/em&gt; folder contains a reflex component that is used within the plug-in.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;em&gt;pages&lt;/em&gt;&lt;/strong&gt; folder contains the web templates frequently used in Saas applications (dashboard, home, login, pricing, signup). The code comments in each file will guide you on what each function or utility does.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;&lt;strong&gt;state&lt;/strong&gt;&lt;/em&gt; folder contains the States used in the app. Read more about states in Reflex here.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;&lt;strong&gt;ercas_saas.py&lt;/strong&gt;&lt;/em&gt; file is the main file for your app. You can add each page in your app directly in this file. This allows you to keep track of each web page in your app.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;em&gt;ercas_api.py&lt;/em&gt;&lt;/strong&gt; file is the API client for the ErcasPay API. It handles all requests to the ErcasPay API which allows you to receive payments from users and verify their subscriptions.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;&lt;em&gt;models.py&lt;/em&gt;&lt;/strong&gt; file contains information about the database model and schema.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  How to run the EFS plug-in locally
&lt;/h2&gt;

&lt;p&gt;Open the EFS project in the terminal. and follow the steps below to run the EFS plug-in locally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new virtual environment with &lt;strong&gt;&lt;em&gt;virtualenv&lt;/em&gt;&lt;/strong&gt; in Python &lt;br&gt;
(install virtualenv if you do not have it):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;virtualenv env
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Activate the virtual environment:&lt;br&gt;
Windows:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;.env\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;Linux/macOS&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;source/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install the project requirements with the command below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install -r requirements.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Install &lt;strong&gt;passlib[bcrypt]&lt;/strong&gt; package for password hashing with the &lt;br&gt;
command below (This was not included in the requirements.txt &lt;br&gt;
because it has a special installation process):&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install passlib[bycrypt]
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Initialize reflex with the command below&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reflex init
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This will create a new &lt;strong&gt;&lt;em&gt;.web&lt;/em&gt;&lt;/strong&gt; folder in the project. This &lt;br&gt;
folder will contain the frontend requirements for your app. In most &lt;br&gt;
cases, you won't have to work with this folder. Reflex handles the &lt;br&gt;
frontend for you.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Create a new database from the existing schema with the command &lt;br&gt;
below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reflex db init
&lt;/code&gt;&lt;/pre&gt;


&lt;p&gt;This will also create a new &lt;strong&gt;&lt;em&gt;saas.db&lt;/em&gt;&lt;/strong&gt; file in your project.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new &lt;strong&gt;&lt;em&gt;.env&lt;/em&gt;&lt;/strong&gt; file for environment variables like the &lt;br&gt;
ErcasPay API token. It is used within the app to authenticate with &lt;br&gt;
the API. For a start, get a test API token by setting up your &lt;br&gt;
account on ErcasPay. &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Finally, run the project with the command below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;reflex run
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The project will run on port 3000 by default so you can access it at &lt;code&gt;http://localhost:3000/&lt;/code&gt; on your pc.&lt;/p&gt;

&lt;h2&gt;
  
  
  Editing and customization
&lt;/h2&gt;

&lt;p&gt;The project contains ready-made templates which you can customize to your specific needs. Example customization includes, adding new pages, fonts, styling, etc. Checkout the reflex &lt;a href="https://reflex.dev/docs/getting-started/introduction/" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; for more ideas.&lt;/p&gt;

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

&lt;p&gt;Ercas for SaaS is a powerful tool for indie hackers and developers looking to launch SaaS applications quickly. By leveraging the Reflex.dev template, you can bypass the complexities of authentication, payment, and subscription management. This tutorial has shown you how to install, run, and customize the project, empowering you to focus on building great software rather than reinventing the wheel.&lt;/p&gt;

&lt;p&gt;Start using Ercas for SaaS today and accelerate your journey to SaaS success!&lt;/p&gt;

</description>
      <category>saas</category>
      <category>python</category>
      <category>webdev</category>
      <category>fullstack</category>
    </item>
    <item>
      <title>How to build a fully-fledged telegram bot in Python</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Fri, 31 May 2024 22:53:37 +0000</pubDate>
      <link>https://forem.com/emiloju/how-to-build-a-fully-fledged-telegram-bot-in-python-2al0</link>
      <guid>https://forem.com/emiloju/how-to-build-a-fully-fledged-telegram-bot-in-python-2al0</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Chatbots are gradually becoming an integral part of our lives. These automated agents allow users to solve problems quickly by engaging them in real-time. I personally refer to them as &lt;em&gt;online-buddies.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;One of the simplest ways of building a chatbot is through the Telegram messaging app. Telegram provides an easy way to build chatbots through their API.&lt;/p&gt;

&lt;p&gt;In this article, you will understand how &lt;a href="https://www.suffescom.com/product/telegram-bot-development" rel="noopener noreferrer"&gt;telegram bots&lt;/a&gt; work and how to create a Telegram bot using Python. &lt;/p&gt;

&lt;p&gt;By the end of this article, you would have built a fully-fledged currency exchange telegram bot with Python.&lt;/p&gt;

&lt;p&gt;This telegram bot allows users to get the exchange rate between two or more currencies in real time. It will also store registered users’ data in a SQL relational database.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;NB: This is not your regular kind of bot. You will build a fully-fledged telegram bot in Python that uses a relational database.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The following are the features of the bot:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The bot registers new users and stores their data in a relational database.&lt;/li&gt;
&lt;li&gt;Only registered users can get the exchange rate between two or more currencies.&lt;/li&gt;
&lt;li&gt;Users can select a favorite base currency and multiple target currencies they would like to get updates on.&lt;/li&gt;
&lt;li&gt;The bot sends daily updates on latest exchange rates based on a user's base currency and their target currencies.&lt;/li&gt;
&lt;li&gt;Users can activate or deactivate the daily updates.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The purpose of this tutorial is to help you learn how to build a fully-fledged Telegram bot in Python. In the process, you will also learn concepts such as OOP, decorators, etc. that will help you write cleaner and reusable code in Python.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;PS: This &lt;a href="https://www.suffescom.com/product/telegram-bot-development" rel="noopener noreferrer"&gt;article&lt;/a&gt; explores the conceptual aspect of Telegram bots in details.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Below is a live demo of the bot in action:&lt;br&gt;
&lt;iframe width="710" height="399" src="https://www.youtube.com/embed/7HuHGj3Sw6k"&gt;
&lt;/iframe&gt;
 &lt;/p&gt;
&lt;h2&gt;
  
  
  Requirements
&lt;/h2&gt;

&lt;p&gt;Below is a list of tools and libraries required for this tutorial:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Python&lt;/u&gt;&lt;/strong&gt;: Python must be installed on your computer. Preferably, Python 3.8+. You must also have a basic knowledge of Python.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;python-telegram-bot library (PTB)&lt;/u&gt;&lt;/strong&gt;: &lt;a href="https://docs.python-telegram-bot.org/en/v20.7/" rel="noopener noreferrer"&gt;PTB&lt;/a&gt; library would be used to build the telegram bot. It is a feature-rich wrapper for the Telegram bot API in Python. To install this library, run the command below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install python-telegram-bot
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;u&gt;Python-telegram-bot[job_queue]&lt;/u&gt;&lt;/strong&gt;: &lt;a href="https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions---JobQueue" rel="noopener noreferrer"&gt;job_queue&lt;/a&gt; is a package within the PTB library. It is used for setting up cron-jobs such as reminders, etc. in a telegram bot. Run the command below to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install python-telegram-bot[job_queue]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;u&gt;sqlalchemy library&lt;/u&gt;&lt;/strong&gt;: &lt;a href="https://docs.sqlalchemy.org/en/20/" rel="noopener noreferrer"&gt;sqlalchemy&lt;/a&gt; is a Python library used for creating a relational database. Run the command below to install this library:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install sqlalchemy
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;u&gt;Requests library&lt;/u&gt;&lt;/strong&gt;: This library is used for making HTTP requests in Python. In this tutorial, it would be used to send HTTP requests to the currency exchange API. Run the command below to install it:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install requests
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;u&gt;A telegram bot&lt;/u&gt;&lt;/strong&gt;: This can be created on the telegram app. It lets you create and customize the theme and design of your chatbot. Once created, you would be given an api-token that would let you authenticate with the API.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;u&gt;Abstract API api-key&lt;/u&gt;&lt;/strong&gt;: &lt;a href="https://www.abstractapi.com/" rel="noopener noreferrer"&gt;Abstract API&lt;/a&gt; offers a suite of 12+ REST APIs. The currency exchange API is one of these APIs. You need an api-key to be able to use the API. You can only get one when you sign up with Abstract API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Telegram bot
&lt;/h2&gt;

&lt;p&gt;As part of the requirements for this tutorial, the following is a step-by-step process to create and design a Telegram bot:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the Telegram app.&lt;/li&gt;
&lt;li&gt;Enter the name &lt;em&gt;BotFather&lt;/em&gt; in the search bar.&lt;/li&gt;
&lt;li&gt;Click on the &lt;em&gt;BotFather&lt;/em&gt; profile like the one in the image below:&lt;/li&gt;
&lt;/ol&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%2F5is5aimmktqdllfby309.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%2F5is5aimmktqdllfby309.png" alt="An Image of Telegram BotFather's profile" width="601" height="231"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the start button in the new chat that opens.&lt;/li&gt;
&lt;/ol&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%2Fms62y1cshwp7o3krs7ry.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%2Fms62y1cshwp7o3krs7ry.png" alt="A screenshot of the start command in a Telegram bot" width="800" height="199"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Follow the directions of the BotFather to create and design your Telegram bot.&lt;/li&gt;
&lt;/ol&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%2Fa2wsp8s72xay4y5f4x5l.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%2Fa2wsp8s72xay4y5f4x5l.png" alt="A screenshot of steps for editing a Telegram bot" width="800" height="488"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;BotFather is the father of all Telegram bots. It was created by Telegram to help developers create chatbots on the platform.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create an Abstract API account
&lt;/h2&gt;

&lt;p&gt;Abstract API offers a single api-key for registered users to authenticate with any of their APIs. Below is a step-by-step process to create an Abstract API account:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Proceed to their &lt;a href="https://app.abstractapi.com/users/signup" rel="noopener noreferrer"&gt;sign-up&lt;/a&gt; page.&lt;/li&gt;
&lt;li&gt;Enter your details and confirm you’re not a robot.&lt;/li&gt;
&lt;/ol&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%2F8tw6ostb15zwryhd3sge.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%2F8tw6ostb15zwryhd3sge.png" alt="Abstract API sign-up page" width="800" height="324"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Click the Continue button to complete your registration.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A confirmation link will be sent to your mail. This link will confirm your registration and redirect you to their login page. &lt;/p&gt;

&lt;p&gt;Once you login, you will have access to your dashboard.&lt;/p&gt;

&lt;h3&gt;
  
  
  Get your Abstract API key
&lt;/h3&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%2Fnmjghmh9si9pxal2lgks.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%2Fnmjghmh9si9pxal2lgks.png" alt="Image description on how to get Abstract API key" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow the steps below to get your api-key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Hover your cursor around the left side of your dashboard.&lt;/li&gt;
&lt;li&gt;Select Exchange Rates under &lt;strong&gt;&lt;em&gt;APIs to lookup&lt;/em&gt;&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;&lt;em&gt;Try it out&lt;/em&gt;&lt;/strong&gt; on the new page.&lt;/li&gt;
&lt;li&gt;Locate the &lt;strong&gt;&lt;em&gt;Primary key&lt;/em&gt;&lt;/strong&gt; label. You will find your api-key there.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Build the Telegram bot
&lt;/h2&gt;

&lt;p&gt;There are various libraries used for building a Telegram bot in Python. One of which is the python-telegram-bot (PTB) library.&lt;/p&gt;

&lt;p&gt;Its use of asynchronous programming enables your bot to handle multiple requests at a time without blocking the other.&lt;/p&gt;

&lt;p&gt;Also, it has a bunch of other features for building a fully functional Telegram bot. You will explore most of these features in this tutorial.&lt;/p&gt;

&lt;p&gt;Open your code editor. Ensure you have created a project and a virtual environment that has all the required libraries installed in it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial application setup
&lt;/h3&gt;

&lt;p&gt;The first step is to configure the bot settings. Below is the code for the application setup:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from telegram.ext import ApplicationBuilder


# To ensure errors are properly logged
logging.basicConfig(
   format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
   level=logging.INFO
)

# The "main block"
if __name__ == "__main__":

   TOKEN = 'your-api-token'

   # An application instance
   application = ApplicationBuilder().token(TOKEN).build()

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

&lt;/div&gt;



&lt;p&gt;In the code above, the &lt;code&gt;logging&lt;/code&gt;library is used to ensure that errors from the bot are logged with correct descriptions so users can easily track and resolve them. &lt;/p&gt;

&lt;p&gt;Also, an application instance is created with the &lt;code&gt;ApplicationBuilder()&lt;/code&gt; class of the PTB library. It uses the &lt;code&gt;.token()&lt;/code&gt; and &lt;code&gt;.build()&lt;/code&gt; method to create an application instance that authenticates your bot with Telegram API. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;.token()&lt;/code&gt; method accepts a compulsory &lt;strong&gt;TOKEN&lt;/strong&gt; argument, that is, your telegram api-token. &lt;/p&gt;

&lt;p&gt;This initial setup is required for every bot (also known as application) built with the PTB library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create the start command
&lt;/h3&gt;

&lt;p&gt;The first command available to users in a Telegram bot is the &lt;code&gt;/start&lt;/code&gt; command. Commands are instructions given by users to the bot. &lt;/p&gt;

&lt;p&gt;Therefore, you need to create a function that responds to this command. &lt;/p&gt;

&lt;p&gt;Follow the steps below to create a function for the &lt;code&gt;/start&lt;/code&gt; command:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Import the following modules from PTB library
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import telegram
from telegram import Update
from telegram.ext import ApplicationBuilder, ContextTypes, CommandHandler, filters
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Write the function for the start command
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):

   # Responds to a user
   await context.bot.send_message(
       chat_id=update.effective_chat.id,
       text="Welcome to the currency exchange bot.\n\n"
            "What is your base currency? Enter your answer using the example
below as a guide:\n\n"
            "/baseCurrency\n"
            "your base currency, e.g USD, GBP\n\n"
   )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;start()&lt;/code&gt; function is an asynchronous function that accepts two arguments namely:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;update:&lt;/strong&gt; This argument has a type hint of the &lt;code&gt;Update&lt;/code&gt; class in PTB library. It contains updates like new messages from the bot. It also includes data about the sender, such as: name, chat_id, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;context&lt;/strong&gt;: This argument has a type hint of the &lt;code&gt;ContextTypes.DEFAULT_TYPE&lt;/code&gt; module in PTB. It includes all actions the bot can perform, such as sending messages, audios, replying to messages, etc.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These two arguments are compulsory for every function in the PTB library.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;context.bot.send_message()&lt;/code&gt; method is used to send messages to users. It accepts two compulsory arguments which include the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;chat_id&lt;/strong&gt;: A unique identifier for the user/chat to send the message to.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;text&lt;/strong&gt;: The textual content of the message.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;start()&lt;/code&gt; function is invoked when a user starts the bot. In the case of the currency exchange bot, it asks a few questions from them to ensure a personalized experience. &lt;/p&gt;

&lt;p&gt;This question is added to the &lt;code&gt;.send_message()&lt;/code&gt; method in the &lt;code&gt;start()&lt;/code&gt; function above. &lt;/p&gt;

&lt;h4&gt;
  
  
  Register the start command
&lt;/h4&gt;

&lt;p&gt;Handlers in the PTB library provide a way of linking each command to a function. Each function is executed when its linked command is invoked. &lt;/p&gt;

&lt;p&gt;For example, when building a Django web app, you need to create url paths and a view function for each path. When a user visits any url, its associated view function is executed.&lt;/p&gt;

&lt;p&gt;Add a handler for the &lt;code&gt;start()&lt;/code&gt; function below the application instance and start the bot:&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__":

   TOKEN = 'your_api_token'
   application = ApplicationBuilder().token(TOKEN).build()

   # Creates a command handler
   start_handler = CommandHandler('start', start)

   # Adds the handler to the bot
   application.add_handler(start_handler)

   # start the bot. 
   application.run_polling()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CommandHandler()&lt;/code&gt; class is used to link a command and a function together. It accepts two arguments — the command name and the function. &lt;/p&gt;

&lt;p&gt;You register the handler with the application using the &lt;code&gt;.add_handler()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;application.run_polling()&lt;/code&gt; method keeps the bot running and constantly polls the Telegram bot API for new alerts on your bot. It should be at the bottom end in the main block.&lt;/p&gt;

&lt;p&gt;Finally, run the script file to start the bot. &lt;/p&gt;

&lt;p&gt;Open your Telegram bot and click on the start button in the chat. The bot will respond with the message that was set earlier in the &lt;code&gt;start()&lt;/code&gt; function. This means the bot setup was successful. Below is an example:&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%2Feji6xsma4594rhx3ma99.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%2Feji6xsma4594rhx3ma99.png" alt="Using the start button in a Telegram bot" width="800" height="156"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Learn more about setting up an application and creating functions in this sample &lt;a href="https://github.com/python-telegram-bot/python-telegram-bot/wiki/Extensions---Your-first-Bot" rel="noopener noreferrer"&gt;tutorial&lt;/a&gt; of the PTB documentation. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create a database connection
&lt;/h3&gt;

&lt;p&gt;A database is required to store users' data. The sqlalchemy library in Python will be used to set up the database. &lt;/p&gt;

&lt;p&gt;For this tutorial, you will use a local database file which is suitable for a development environment.&lt;/p&gt;

&lt;p&gt;Follow the steps below to set up a database:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new file named &lt;code&gt;database.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Open the file and add the code below:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from sqlalchemy import create_engine
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import sessionmaker
import os
# The database url
SQLALCHEMY_DATABASE_URL = "sqlite:///users.db"
# Creates a database engine
engine = create_engine(
   SQLALCHEMY_DATABASE_URL
)

# Handles database sessions
SessionLocal = sessionmaker(bind=engine)

Base = declarative_base()

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

&lt;/div&gt;



&lt;p&gt;The following is an analysis of the &lt;code&gt;database.py&lt;/code&gt; file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SQLALCHEMY_DATABASE_URL&lt;/strong&gt;: This refers to the database link or URL. Since you would be using a local database file, the URL would be a local file name. For this tutorial, the database file is titled &lt;code&gt;users.db&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;create_engine()&lt;/strong&gt;: This is a method in sqlalchemy that is used to create connections with a database. It takes the database URL as an argument.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;SessionLocal&lt;/strong&gt;: SessionLocal object is used for creating sessions in SQLAlchemy. It is used to manage transactions and serve as a gateway to the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Base&lt;/strong&gt;: This line creates a base class for declarative class definitions. In SQLAlchemy, a declarative base is a base class for declarative class definitions, which are used to define database models.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a database model
&lt;/h3&gt;

&lt;p&gt;The next step is to create a &lt;em&gt;User&lt;/em&gt; database model. Follow the steps below to create a database model:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new file named &lt;code&gt;models.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Add the code below to this file:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from sqlalchemy import Boolean, Column, Integer, String
from database import Base

class User(Base):
   __tablename__ = "users"

   id = Column(Integer, primary_key=True, index=True)
   chat_id = Column(Integer, unique=True, nullable=False)
   base_currency = Column(String, unique=False, nullable=False)
   currency_pairs = Column(String, unique=False, nullable=False)
   receive_updates = Column(Boolean, nullable=False, unique=False, default=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The _User _model contains the following columns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;chat_id&lt;/strong&gt;: A long set of integers such as, 257945683925 that is unique to each chat or user.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;base_currency&lt;/strong&gt;: This column is for a user's favorite base currency such as, USD, CAD, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;target_currencies&lt;/strong&gt;: This refers to a list of other currencies the user would like to get daily updates on, relative to their base currency. For example, a user can select USD as their base currency and GBP, CAD, EUR as their target currencies. This means that the user would like to get daily updates on how much 1USD is, when converted to GBP, CAD, and EUR.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;receive_updates&lt;/strong&gt;: This is a boolean field that records if the user would like to receive updates or not.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create the database file
&lt;/h3&gt;

&lt;p&gt;Follow the steps below to create a new database file:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open the &lt;code&gt;main.py&lt;/code&gt; file.&lt;/li&gt;
&lt;li&gt;Add the following import statements:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from database import Base, SessionLocal, engine
from models import User
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the code below after the &lt;code&gt;logging.basicConfig()&lt;/code&gt; function.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
Base.metadata.create_all(bind=engine)
db = SessionLocal()
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Run the script file.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The steps above will create a database file and add a _user _ table to it. An instance of the database session is assigned to the &lt;code&gt;db&lt;/code&gt; variable. It is used to perform CRUD (create, read, update, delete) operations with the database.&lt;/p&gt;

&lt;p&gt;A new file named &lt;code&gt;users.db&lt;/code&gt; would be created in your project folder.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a &lt;em&gt;CurrencyExchange&lt;/em&gt; class
&lt;/h3&gt;

&lt;p&gt;So far, you have learnt the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to create a telegram bot and get an api-token.&lt;/li&gt;
&lt;li&gt;How to create a command and a corresponding function in PTB.&lt;/li&gt;
&lt;li&gt;How to register a command with a function through handlers.&lt;/li&gt;
&lt;li&gt;How to create a database connection.&lt;/li&gt;
&lt;li&gt;How to add a database model in Python.&lt;/li&gt;
&lt;li&gt;How to create a relational database file in Python.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://docs.abstractapi.com/exchange-rates" rel="noopener noreferrer"&gt;currency exchange API&lt;/a&gt; is a REST API with different endpoints. In order to prevent repetition and enhance readability, it is necessary that you bundle all the endpoints into a Python class. &lt;/p&gt;

&lt;p&gt;This is a concept in &lt;strong&gt;Object-Oriented Programming (OOP)&lt;/strong&gt;. You can check out my &lt;a href="https://superbemiloju.hashnode.dev/object-oriented-programming-oop-concepts-in-python-with-practical-examples" rel="noopener noreferrer"&gt;complete guide on OOP for beginners&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;The class will be named &lt;code&gt;CurrencyExchange&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Follow the steps below to create the CurrencyExchange class:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new file named exchange.py&lt;/li&gt;
&lt;li&gt;Add the code below to create the class:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import requests

class CurrencyExchange:

   def __init__(self, api_key):
       self.key = api_key
       self.codes = ["ARS", "AUD", "BCH", "BGN", "BNB", "BRL", "BTC", 
                     "CAD", "CHF", "CNY", "CZK", "DKK", "DOGE", "DZD", 
                     "ETH", "EUR", "GBP", "HKD", "HRK", "HUF", "IDR", 
                     "ILS", "INR", "ISK", "JPY", "KRW", "LTC", "MAD", 
                     "MXN", "MYR", "NOK", "NZD", "PHP", "PLN", "RON", 
                     "RUB", "SEK", "SGD", "THB", "TRY", "TWD", "XRP", 
                     "ZAR", "USD"]

   # Performs one-to-one exchange
   def single_exchange(self, params):
       url = "https://exchange-rates.abstractapi.com/v1/convert"
       params['api_key'] = self.key
       res = requests.request("GET", url, params=params)
       return res.json()

   # Performs one-to-many exchange
   def multiple_exchange(self, params):
       url = "https://exchange-rates.abstractapi.com/v1/live"
       params['api_key'] = self.key
       res = requests.request("GET", url, params=params)
       return res.json()

   # validates a single currency code
   def is_valid_currency(self, currency):
       if currency in self.codes:
           return True
       else:
           return False

   # validates multiple currencies
   def is_valid_currencies(self, currencies_list):

       # uses all() function to loop through currencies list
       if all(currency in self.codes for currency in currencies_list):
           return True
       else:
           return False

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

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;CurrencyExchange&lt;/code&gt;class has two attributes - an api_key and currency codes which are supported by the bot. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;single_exchange()&lt;/code&gt; method is for exchange rate between two currency pairs. It uses the convert endpoint of the currency exchange API. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;multiple_exchange()&lt;/code&gt; method is for rates between a single base currency and multiple target currencies. It uses the live endpoint of the currency exchange API.&lt;/p&gt;

&lt;p&gt;The last two methods namely &lt;code&gt;is_valid_currencies()&lt;/code&gt; and &lt;code&gt;is_valid_currency()&lt;/code&gt; will be used to validate user inputs. That is, single and multiple currency codes respectively.&lt;/p&gt;

&lt;p&gt;Read the API &lt;a href="https://docs.abstractapi.com/exchange-rates" rel="noopener noreferrer"&gt;documentation &lt;/a&gt;to learn more about these endpoints.&lt;/p&gt;

&lt;h3&gt;
  
  
  Add new functions and commands
&lt;/h3&gt;

&lt;p&gt;The next step is to add new commands and their corresponding functions to the bot. &lt;/p&gt;

&lt;h4&gt;
  
  
  Create function for base currency input
&lt;/h4&gt;

&lt;p&gt;This function receives and records users' base currency. Users have been instructed in the &lt;code&gt;start()&lt;/code&gt; function to submit a base currency code through the &lt;em&gt;/baseCurrency&lt;/em&gt; command.&lt;/p&gt;

&lt;p&gt;Commands in the PTB library accept arguments as user inputs. It is like a URL with query parameters. &lt;/p&gt;

&lt;p&gt;Below is the function for the &lt;em&gt;/baseCurrency&lt;/em&gt; command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...

async def record_base_currency(update: Update, context: ContextTypes.DEFAULT_TYPE):
   """ Record Base Currency """

   # user input
   argument = "".join(context.args)
   base_currency = str(argument).upper()

   # validates the user input(currency code)
   if currency_exchange.is_valid_currency(base_currency):
       context.user_data["base_currency"] = base_currency
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text="Kindly list your favorite target currencies. That is, a list of currencies you want to get updates on relative to your base currency."
                " You can select as many as you want\n"
                "&amp;lt;b&amp;gt;Each currency should be separated by a comma(,)&amp;lt;/b&amp;gt;\n\n"
                "Use the example below as a guide:\n\n"
                "/targetCurrencies\n"
                "USD,CAD,GBP",
           parse_mode=telegram.constants.ParseMode.HTML
           )
   else:
       # alerts user if currency is not supported 
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text=f"This bot does not support {base_currency} currency"
       )

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

&lt;/div&gt;



&lt;p&gt;Arguments in PTB are stored in a Python list called &lt;code&gt;context.args&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;In the &lt;code&gt;record_base_currency()&lt;/code&gt; function, this list is unpacked and converted to a string. This string, which is the user's base currency, is converted to uppercase and stored in the context dictionary. &lt;/p&gt;

&lt;p&gt;A &lt;a href="https://github.com/python-telegram-bot/python-telegram-bot/wiki/Storing-bot%2C-user-and-chat-related-data" rel="noopener noreferrer"&gt;context dictionary&lt;/a&gt; is used to temporarily store information within the bot’s memory. It is of two types:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The bot context dictionary:&lt;/strong&gt; It stores information temporarily in memory and it can be accessed from each user's chat. More like a private storage.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;The user context dictionary:&lt;/strong&gt; It stores information temporarily in memory, and it can only be accessed in a single user's chat. More like a private storage. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Instead of adding user's input directly to the database, you can easily add it to the context dictionary. This helps in the following ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;It helps to limit frequently queries to the database.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;In case the user fails to complete registration, you do not have a redundant or incomplete record in the database.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the user successfully completes his/her registration, you can then add everything to the database.&lt;/p&gt;

&lt;p&gt;The bot queries the user for their target currencies. Each user input is verified to ensure that it is supported by the API.&lt;/p&gt;

&lt;p&gt;PTB library allows the bot to send HTML messages. Telegram API has a list of supported &lt;a href="https://core.telegram.org/api/entities" rel="noopener noreferrer"&gt;HTML tags&lt;/a&gt;. &lt;/p&gt;

&lt;p&gt;When sending HTML messages, it is important that you parse the message by adding the &lt;code&gt;parse_mode&lt;/code&gt; argument to the &lt;code&gt;context.bot.send_message()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Below is an example of the &lt;em&gt;/baseCurrency&lt;/em&gt; command:&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%2Fsp4ci6wevsj6zldgi12c.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%2Fsp4ci6wevsj6zldgi12c.png" alt="Screenshot that shows how to submit a base currency" width="800" height="218"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Create function for target currencies input
&lt;/h4&gt;

&lt;p&gt;In the previous step, the user is prompted to submit their target currencies. The &lt;code&gt;record_target_currencies()&lt;/code&gt; function will be used to validate and record the data submitted by the user.&lt;/p&gt;

&lt;p&gt;Follow the steps below to create this function:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Open the &lt;code&gt;main.py&lt;/code&gt; file&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Import the following classes&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Add the new function below:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
async def record_target_currencies(update: Update, context: ContextTypes.DEFAULT_TYPE):
   # user input
   arguments = "".join(context.args)

   currency_pairs = arguments.split(",")

   # validates the target currencies
   if currency_exchange.is_valid_currencies(currency_pairs):
       context.user_data["target_currencies"] = arguments

       keyboard = [[InlineKeyboardButton('Yes', callback_data='yes')],
                   [InlineKeyboardButton('No', callback_data='no')]]

       await update.message.reply_text(
           text="&amp;lt;b&amp;gt;Would you like to receive daily updates on selected currencies?&amp;lt;/b&amp;gt;",
           parse_mode=telegram.constants.ParseMode.HTML,
           reply_markup=InlineKeyboardMarkup(keyboard)
       )

   else:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text=f"Error occurred! Ensure that each currency is supported by the bot."
       )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, the &lt;code&gt;record_target_currency()&lt;/code&gt; function adds the user input directly to the context dictionary. The function also validates a user input before adding it to the context dictionary. &lt;/p&gt;

&lt;p&gt;If submission is successful, the user is asked if they want to receive daily updates. The &lt;code&gt;InlineKeyboardButton()&lt;/code&gt; and &lt;code&gt;InlineKeyboardMarkup()&lt;/code&gt; classes are used to create a list of clickable options for a user to select from. &lt;/p&gt;

&lt;p&gt;Below is a sample of the &lt;em&gt;/targetCurrencies&lt;/em&gt; command and the bot's response:&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%2F8yociul3i4571crs5z9t.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%2F8yociul3i4571crs5z9t.png" alt="Screenshot that shows how to submit target currencies" width="800" height="196"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Create function to complete user registration
&lt;/h4&gt;

&lt;p&gt;In the previous function, the user is presented with a list of options to choose from. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;complete_registration()&lt;/code&gt; function receives the user's selection. It enters this selection alongside the previous inputs submitted by the user into the database.&lt;/p&gt;

&lt;p&gt;This marks the end of the user registration. Below is the code for this function:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def complete_registration(update: Update, context: ContextTypes.DEFAULT_TYPE):
   """ User completes registration """
   query = update.callback_query.data

   # checks for incomplete registration
   try:
       target_currencies = context.user_data.get("target_currencies")
       base_currency = context.user_data.get("base_currency")
   except KeyError:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text="&amp;lt;b&amp;gt;There's an error with your registration. Kindly restart by submitting your base currency&amp;lt;/b&amp;gt;\n"
                "Use the example below as a guide:\n\n"
                "/baseCurrency\n"
                "your base currency, e.g USD, GBP\n\n",
           parse_mode=telegram.constants.ParseMode.HTML
       )
   else:
       if query == "yes":
           receive_updates = True
       else:
           receive_updates = False

       new_user = User(
           chat_id=update.effective_chat.id,
           base_currency=base_currency,
           currency_pairs=target_currencies,
           receive_updates=receive_updates
       )
       db.add(new_user)
       db.commit()

       options = [["Activate Updates 🚀", 'Deactivate updates'], ['Bot Manual 📗']]

       key_markup = ReplyKeyboardMarkup(options, resize_keyboard=True)
       await context.bot.send_message(text="&amp;lt;b&amp;gt;You have successfully completed your registration&amp;lt;/b&amp;gt;",
                                      reply_markup=key_markup,
                                      chat_id=update.effective_chat.id,
                                      parse_mode=telegram.constants.ParseMode.HTML)

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

&lt;/div&gt;



&lt;p&gt;In the code above, all the previous entries are retrieved and added to the database. The user gets a success message and three buttons are added to the bot. This would allow the user to perform certain tasks quickly. Below is an image of the three buttons:&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%2Fbo93xr0sx5zv9ej8ran9.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%2Fbo93xr0sx5zv9ej8ran9.png" alt="Screenshot of buttons in a Telebgram bot" width="800" height="171"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In some cases, it is possible the user omitted a step in the registration process. &lt;/p&gt;

&lt;p&gt;For example, if a user has a prior knowledge of how this bot works, he/she could decide to start the bot and enter the &lt;code&gt;target_currencies&lt;/code&gt; first and skip the &lt;code&gt;base_currency&lt;/code&gt; part. &lt;/p&gt;

&lt;p&gt;If by the end of the registration, you try to access the value for the &lt;code&gt;base_currency&lt;/code&gt; from the context dictionary, it will trigger a &lt;code&gt;KeyError&lt;/code&gt; because it doesn't exist. This function handles this scenario. &lt;/p&gt;

&lt;h4&gt;
  
  
  Create function for messages
&lt;/h4&gt;

&lt;p&gt;Users now have full access to the bot. You need to add a function that handles the user's direct messages to the bot.&lt;/p&gt;

&lt;p&gt;For this tutorial, the only direct messages the bot can respond to are those from the 3 buttons. If you click on any of them, a direct message will be sent to the bot.&lt;/p&gt;

&lt;p&gt;Below is the function that handles direct messages to the bot:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
async def direct_messages(update: Update, context: ContextTypes.DEFAULT_TYPE):
   """ Direct messages """
   message = update.message.text
   user = db.query(User).filter_by(chat_id=update.effective_chat.id).first()

   if message == "Bot Manual 📗":
       await context.bot.send_message(
           text="&amp;lt;i&amp;gt;Click &amp;lt;b&amp;gt;Activate Updates&amp;lt;/b&amp;gt; to activate daily updates&amp;lt;/i&amp;gt;\n\n"
                "&amp;lt;i&amp;gt;Click &amp;lt;b&amp;gt;Deactivate Updates&amp;lt;/b&amp;gt; to deactivate updates&amp;lt;/i&amp;gt;\n\n"
                "&amp;lt;i&amp;gt;Click &amp;lt;b&amp;gt;Bot Manual&amp;lt;/b&amp;gt; to learn how to use the bot&amp;lt;/i&amp;gt;\n\n"
                ""
                "&amp;lt;i&amp;gt;To find the exchange rate between a base currency and multiple target currencies, use the command "
                "below:\n\n&amp;lt;/i&amp;gt;"
                "/multipleExchange\n"
                "USD/CAD/EUR\n\n"
                "Put your base currency first and other currencies should follow. Separate them with a forward "
                "slash(/)\n\n"
                ""
                "&amp;lt;i&amp;gt;To find the exchange rate between a base currency a single target currency, use the command "
                "below:&amp;lt;/i&amp;gt;\n\n"
                "/singleExchange\n"
                "USD/GBP\n\n"
                "Put your base currency first and the target currency should follow. Separate them with a forward "
                "slash(/).\n\n"
                ""
                "&amp;lt;i&amp;gt;To find the exchange rate between a base currency a single target currency with a base amount, "
                "use the command below:&amp;lt;/i&amp;gt;\n\n"
                "/exchangeRate\n"
                "USD/CAD @ 50\n\n"
                "Put your base and target currency together and signify the base amount with the @ symbol",
           chat_id=update.effective_chat.id,
           parse_mode=telegram.constants.ParseMode.HTML)

   elif message == "Activate Updates 🚀":
       user.receive_updates = True
       db.commit()
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text="&amp;lt;i&amp;gt;You have successfully activated daily exchange rate updates&amp;lt;/i&amp;gt;",
           parse_mode=telegram.constants.ParseMode.HTML
       )

   elif message == 'Deactivate updates':
       user.receive_updates = False
       db.commit()
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text="&amp;lt;i&amp;gt;You have successfully deactivated daily exchange rate updates&amp;lt;/i&amp;gt;",
           parse_mode=telegram.constants.ParseMode.HTML
       )

   else:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text="&amp;lt;i&amp;gt;This bot is not able to respond to your messages for now&amp;lt;/i&amp;gt;",
           parse_mode=telegram.constants.ParseMode.HTML
       )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the code above, each button click is handled by the bot. The &lt;em&gt;&lt;strong&gt;Bot manual&lt;/strong&gt;&lt;/em&gt; sends a guide on how to use the bot, the &lt;em&gt;&lt;strong&gt;Activate updates&lt;/strong&gt;&lt;/em&gt; activate daily updates, and &lt;strong&gt;&lt;em&gt;Deactivate updates&lt;/em&gt;&lt;/strong&gt; deactivates daily updates from the bot. You can add as many buttons as you like.&lt;/p&gt;

&lt;h4&gt;
  
  
  Create function for single exchanges
&lt;/h4&gt;

&lt;p&gt;The function of this bot is to help users get exchange rates in real time. It does this in 3 ways. They include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;single exchange - that is, between two currencies. For example: USD-CAD.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;multiple exchange - that is, between one currency and other multiple currencies. For example: USD to CAD/BTC/AUD, etc.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;arbitrary exchange - that is, exchange between two currencies while stating a base amount. For example: 50 USD to CAD.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Using the &lt;code&gt;single_exchange()&lt;/code&gt; method of the &lt;code&gt;CurrencyExchange&lt;/code&gt; class, the function below allows users to find single exchange rates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
async def single_exchange_rate(update: Update, context: ContextTypes.DEFAULT_TYPE):
   """ Exchange Currencies """
   currency_pairs = "".join(context.args)

   # Ensures the currency codes are split by a slash(/)
   try:
       base_currency = currency_pairs.split("/")[0].upper()
       target_currency = currency_pairs.split("/")[1].upper()
   except IndexError:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text=f"Error occurred! You must enter two currency codes separated by a forward slash(/)"
       )
   else:
       # validates currency codes
       if currency_exchange.is_valid_currency(base_currency) and currency_exchange.is_valid_currency(target_currency):
           params = {
               "base": base_currency,
               "target": target_currency
           }

           response = currency_exchange.single_exchange(params=params)
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text=f"&amp;lt;b&amp;gt;Base Currency:&amp;lt;/b&amp;gt; {base_currency}\n"
                    f"&amp;lt;b&amp;gt;Target Currency:&amp;lt;/b&amp;gt; {target_currency}\n"
                    f"&amp;lt;b&amp;gt;Exchange Rate&amp;lt;/b&amp;gt;: {response['exchange_rate']}\n\n"
                    f"&amp;lt;i&amp;gt;This means that 1 {base_currency} is equal to {response['exchange_rate']} {target_currency}&amp;lt;/i&amp;gt;",
               parse_mode=telegram.constants.ParseMode.HTML
           )

       else:
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text=f"Error occurred! Ensure that both currencies are supported by the bot."
           )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The user is instructed to use the &lt;em&gt;/singleExchange&lt;/em&gt; command to make a request and separate the two currency codes by a slash (/). This input is split by the slash and assigned to two different variables.&lt;/p&gt;

&lt;p&gt;They are passed as parameters into the &lt;code&gt;single_exchange()&lt;/code&gt; method. The response is styled with HTML and sent to the user. &lt;/p&gt;

&lt;p&gt;Using error handling, the function ensures that the user input is in the right format and contains currency codes that are supported by the API.&lt;/p&gt;

&lt;p&gt;The image below shows how to send a single exchange command:&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%2Fvv9bullrcjeswvaqyt24.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%2Fvv9bullrcjeswvaqyt24.png" alt="Screenshot on how to find a one-to-one exchange rate using Telegram bots" width="800" height="153"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Create function for multiple exchanges
&lt;/h4&gt;

&lt;p&gt;This section shows you how to handle multiple or one-to-many exchange rates for users. Users are to separate the currency codes by a comma, with the base currency added first. &lt;/p&gt;

&lt;p&gt;Below is the code below for this fucntion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
async def multiple_exchange_rate(update: Update, context: ContextTypes.DEFAULT_TYPE):
   """ Exchange Currencies """

   # user input
   argument = "".join(context.args)
   currency_pairs = argument.split("/")


   # ensures currency codes are split 
   try:
       base_currency = currency_pairs[0]
       target_currencies_list = currency_pairs[1:]
   except IndexError:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text=f"Error occurred! Ensure that there are more than two currencies separated by a forward slash(/)."
       )
   else:
       # validates currency codes.
       if currency_exchange.is_valid_currencies(target_currencies_list):
           print(target_currencies_list)
           target_currencies = ",".join(target_currencies_list)

           # API request parameters
           params = {
               "base": base_currency,
               "target": target_currencies
           }
           # API response
           response = currency_exchange.multiple_exchange(params=params)
           result = []
           for currency in target_currencies_list:
               rate = response["exchange_rates"][currency]
               result.append(f"&amp;lt;b&amp;gt;{currency}&amp;lt;/b&amp;gt; = {rate}\n")

           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text="".join(result),
               parse_mode=telegram.constants.ParseMode.HTML
           )

       else:
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text=f"Error occurred! Ensure this bot supports the currencies you entered."
           )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function is similar to the &lt;code&gt;single_exchange()&lt;/code&gt; function, except that it queries multiple currency codes at a time. They handle exceptions the same way but with different methods of the &lt;code&gt;CurrencyExchange&lt;/code&gt; class.&lt;/p&gt;

&lt;p&gt;Below is an example of how to find multiple exchange rates:&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%2Ffvu8oe79hkfu2v3b7nb7.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%2Ffvu8oe79hkfu2v3b7nb7.png" alt="Screenshot of how to find multiple exchange rates using a Telegram bot" width="800" height="116"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Create function for arbitrary exchanges
&lt;/h4&gt;

&lt;p&gt;This is the final exchange type. It allows users to state a base amount for an exchange. &lt;/p&gt;

&lt;p&gt;The function below allows users to perform arbitrary exchange:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
async def arbitrary_exchange(update: Update, context: ContextTypes.DEFAULT_TYPE):
   """ Exchange Currencies """
   arguments = "".join(context.args)
   split_arguments = arguments.split("@")

   try:
       base_currency = split_arguments[0].split("/")[0]
       target_currency = split_arguments[0].split("/")[1]
       base_amount = float(split_arguments[1])

   except IndexError:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text=f"Please enter values in the correct format"
       )

   except ValueError:
       await context.bot.send_message(
           chat_id=update.effective_chat.id,
           text=f"Please enter a valid number"
       )
   else:
       if currency_exchange.is_valid_currency(base_currency) and currency_exchange.is_valid_currency(target_currency):
           params = {
               "base": base_currency,
               "target": target_currency,
               "base_amount": base_amount
           }

           response = currency_exchange.single_exchange(params=params)
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text=f"&amp;lt;b&amp;gt;Base Currency:&amp;lt;/b&amp;gt; {base_currency}\n"
                    f"&amp;lt;b&amp;gt;Target Currency:&amp;lt;/b&amp;gt; {target_currency}\n"
                    f"&amp;lt;b&amp;gt;Exchange Rate:&amp;lt;/b&amp;gt; {response['exchange_rate']}\n\n"
                    f"&amp;lt;i&amp;gt;This means that {response['base_amount']} {base_currency} is equal to "
                    f"{response['converted_amount']} {target_currency}&amp;lt;/i&amp;gt;",
               parse_mode=telegram.constants.ParseMode.HTML
           )
       else:
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text=f"Error occurred! Ensure this bot supports the currencies you entered."
           )
...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above handles two exceptions. The first one is an &lt;code&gt;IndexError&lt;/code&gt; which may occur if the user did not enter two currency codes split by a slash (/), and the other which handles a &lt;code&gt;ValueError&lt;/code&gt; if the &lt;code&gt;base_amount&lt;/code&gt; is not a valid number.&lt;/p&gt;

&lt;p&gt;If there are no errors, the currency codes are validated to ensure they are acceptable by the API.&lt;/p&gt;

&lt;p&gt;Below is an image that shows how a user can perform an arbitrary exchange:&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%2Ffx5dj9jia6l6hc5tmd94.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%2Ffx5dj9jia6l6hc5tmd94.png" alt=" " width="781" height="98"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h4&gt;
  
  
  Create and register all handlers
&lt;/h4&gt;

&lt;p&gt;Congratulations on getting this far. The next step is to add each function to a handler and test the bot.&lt;/p&gt;

&lt;p&gt;The code below shows you how to achieve this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
# The "main block"
if __name__ == "__main__":

   # direct messages handler
   message_handler = MessageHandler(filters.TEXT &amp;amp; (~filters.COMMAND), 
   direct_messages)

   base_currency_handler = CommandHandler('baseCurrency', 
   record_base_currency)
   target_currency_handler = CommandHandler('targetCurrencies', 
   record_target_currencies)

   exchange_handlers = [CommandHandler('singleExchange', 
                              single_exchange_rate),
                        CommandHandler("multipleExchange", 
                              multiple_exchange_rate),
                        CommandHandler("exchangeRate", 
                              arbitrary_exchange)]

   # Option list handlers
   callback_handlers = [CallbackQueryHandler(complete_registration, 
                          'yes'),
                       CallbackQueryHandler(complete_registration, 
                          'no')]

   # Add handlers to the bot
   application.add_handler(message_handler)
   application.add_handler(base_currency_handler)
   application.add_handler(target_currency_handler)
   application.add_handlers(callback_handlers)
   application.add_handlers(exchange_handlers)
   ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each function is added to a handler. The &lt;code&gt;CallbackQueryHandler()&lt;/code&gt; is used to handle the select options created earlier in this tutorial. &lt;/p&gt;

&lt;p&gt;Run the bot to test the progress so far. The bot should respond to all your messages and queries. &lt;/p&gt;

&lt;h4&gt;
  
  
  Protect the bot from unauthorized access.
&lt;/h4&gt;

&lt;p&gt;Currently, the bot is accessible by anyone, both registered and unregistered users. This is the final defect of the bot.&lt;/p&gt;

&lt;p&gt;If an unregistered user tries to enter a command in the bot, the bot should respond by telling them to register first. &lt;/p&gt;

&lt;h4&gt;
  
  
  Python decorators to the rescue
&lt;/h4&gt;

&lt;p&gt;Python &lt;a href="https://www.freecodecamp.org/news/python-decorators-explained/" rel="noopener noreferrer"&gt;decorators&lt;/a&gt; are wrapper functions that accept other functions or classes as arguments. They are useful for validations in web apps.&lt;/p&gt;

&lt;p&gt;For example, if a condition is satisfied, the wrapped function is returned and executed. Else, a warning message is sent or any other action performed.&lt;/p&gt;

&lt;p&gt;For this tutorial, two decorators will be created. They are as follows:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. A decorator for new users:&lt;/strong&gt;&lt;br&gt;
This decorator protects a function and makes it accessible by new users only. This is the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def for_new_users(f):
   @wraps(f)
   async def wrapper_function(update: Update, context: ContextTypes.DEFAULT_TYPE):
       # Get a single user by chat_id
       user = db.query(User).filter_by(chat_id=update.effective_chat.id).first()

       # Checks if user exists
       if user:
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text="This endpoint is for new and unregistered users."
           )
       else:
           await f(update, context)
   return wrapper_function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this decorator, the code checks a user's chat_id against the database. If the chat_id exists, the user is sent a warning message and denied access to the function. If otherwise, the user is allowed to continue with his/her registration.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. A decorator for registered users:&lt;/strong&gt;&lt;br&gt;
This decorator protects a function and makes it accessible by registered users only. This is the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;def for_registered_users(f):
   @wraps(f)
   async def wrapper_function(update: Update, context: ContextTypes.DEFAULT_TYPE):
       # Get a single user by chat_id
       user = db.query(User).filter_by(chat_id=update.effective_chat.id).first()

       # Checks if user exists
       if not user:
           await context.bot.send_message(
               chat_id=update.effective_chat.id,
               text="This endpoint is for registered users. Kindly register to use this function"
           )
       else:
           await f(update, context)

   return wrapper_function
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In this decorator, the code checks a user's &lt;code&gt;chat_id&lt;/code&gt; against the database. If the &lt;code&gt;chat_id&lt;/code&gt; exists, the user is allowed to continue with the bot. If otherwise, the user is sent a warning and instructed to complete his/her registration to access the function.&lt;/p&gt;

&lt;p&gt;This way, registered users do not have to recreate a profile with the same account again.&lt;/p&gt;

&lt;h5&gt;
  
  
  Add the decorators to each function
&lt;/h5&gt;

&lt;p&gt;Protect each function by adding the decorators as below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@for_new_users
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
# Rest of the code
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Assign a suitable decorator for each function. Check the &lt;a href="https://github.com/Omotunde2005/currency_exchange_bot" rel="noopener noreferrer"&gt;full code&lt;/a&gt; on GitHub to see how the decorators are added to each function.&lt;/p&gt;

&lt;h4&gt;
  
  
  Set up daily updates
&lt;/h4&gt;

&lt;p&gt;The final feature of the bot is to send personalized daily updates to users who opt-in for daily updates. &lt;/p&gt;

&lt;p&gt;The &lt;code&gt;job_queue&lt;/code&gt; package of the PTB library is used to set up cron jobs in Telegram bots. &lt;/p&gt;

&lt;p&gt;Below is a function named &lt;code&gt;daily_updates()&lt;/code&gt; that sends the personalized updates:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;async def daily_updates(context: ContextTypes.DEFAULT_TYPE):
   all_users = db.User.all()

   # List of users who opt-in for daily updates
   will_receive_updates = [user for user in all_users if user.receive_updates is True]

   context.bot_data['cached_rates'] = {}

   # Function that sends updates
   async def send_update(user):
       base_currency = user.base_currency.upper()
       params = {
           "base": base_currency
       }
       chat_id = user.chat_id
       currency_pairs = user.currency_pairs.split(",")
       response = ""

       # Checks if an updates exists in the context dictionary
       try:
           cached_response = context.bot_data['cached_rates'][base_currency]
       except KeyError:
           response = currency_exchange.multiple_exchange(params)
       else:
           response = cached_response

       update_message = f"&amp;lt;b&amp;gt;Latest update on exchange rates relative to {base_currency}&amp;lt;/b&amp;gt;\n" \
                        f"This means that 1 {base_currency} is equal to the following in different currencies:\n\n" \
                        f""
       for currency in currency_pairs:
           exchange_rate = response['exchange_rates'][currency.upper()]
           update_message += f"&amp;lt;b&amp;gt;{currency}&amp;lt;/b&amp;gt;: {exchange_rate}\n"

       context.bot_data['recent_exchange_rates'][base_currency] = response
       await context.bot.send_message(
           chat_id=chat_id,
           text=update_message,
           parse_mode=telegram.constants.ParseMode.HTML
       )
   for user in will_receive_updates:
       await send_update(user)
       time.sleep(2)

   # Clears cached updates
   context.bot_data.clear()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the function above, users who want daily updates are separated from others and added to a list.&lt;/p&gt;

&lt;p&gt;Within this function, there is another asynchronous function that queries the API based on each user’s base and target currencies. &lt;/p&gt;

&lt;p&gt;Each successful query is temporarily stored(cached) in the context dictionary of the bot. This is useful in situations where users might have similar base currency.&lt;/p&gt;

&lt;p&gt;Instead of sending a request to the API every time, you can easily access it from within the bot and render results to users.&lt;/p&gt;

&lt;p&gt;For example — John and Janet both have the same base currency which is USD. If John receives his update first, instead of sending a new request to the API for Janet’s update, you can easily use John’s own which has been saved to memory since they both have the same base currency. &lt;/p&gt;

&lt;p&gt;If the next user has a different base currency, a new request is sent to the API and the result is also saved to memory in case there’s another user with the same base currency.&lt;/p&gt;

&lt;p&gt;Finally, the cached data is cleared after all updates have been sent.&lt;/p&gt;

&lt;p&gt;Caching API responses are not accepted by all APIs. In fact, it is considered a crime for some. In the case of the Exchange API, cached responses are not useful after a long time. This is because exchange rates fluctuate and change periodically. Therefore, using cached results would mean that your bot will not provide accurate exchange rates. It is only useful for situations like this where you have to send updates to users at once.&lt;/p&gt;

&lt;p&gt;Create a cron job and register the updates function to the application by following the steps below:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Import the datetime module.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add the code below in the “main block".&lt;br&gt;
&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;...
# The "main block"
if __name__ == "__main__":
   ...

   time_format = '%H:%M'
   reminder_time_string = '21:13'
   daily_job = application.job_queue
   datetime_obj = datetime.strptime(reminder_time_string, time_format)
   daily_job.run_daily(daily_updates, time=datetime_obj.time(), 
   days=tuple(range(7)))

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

&lt;/div&gt;



&lt;p&gt;In the code above, a daily cron job is created with the &lt;code&gt;application.job_queue&lt;/code&gt; instance. &lt;/p&gt;

&lt;p&gt;It is automatically set at UTC timing and it runs based on the specified time of the &lt;code&gt;reminder_time_string&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;It uses the Hours:Minutes(e.g 05:00) time format.&lt;/p&gt;

&lt;h4&gt;
  
  
  Conclusion: Next steps
&lt;/h4&gt;

&lt;p&gt;The bot is up and running and delivers real time exchange rates to users. I'm sure you enjoyed building this project. &lt;/p&gt;

&lt;p&gt;You learned how to apply some python concepts that help you write a clear and organized code. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Next?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;What's the joy of building something this beautiful without it being used by people?&lt;/p&gt;

&lt;p&gt;That's why I’ve decided to create a new tutorial that will show you how to add a live postgresql database to the bot and host it live for others to use.&lt;/p&gt;

&lt;p&gt;Follow me on &lt;a href="https://www.linkedin.com/in/rilwan-edun-4960082aa?utm_source=share&amp;amp;utm_campaign=share_via&amp;amp;utm_content=profile&amp;amp;utm_medium=android_app" rel="noopener noreferrer"&gt;LinkedIn&lt;/a&gt; to be the first to know when the new tutorial is out.&lt;/p&gt;

&lt;p&gt;Telegram bots have a wide range of possibilities. &lt;a href="https://www.suffescom.com/product/telegram-bot-development" rel="noopener noreferrer"&gt;This article&lt;/a&gt; explores the different kind of Telegram bots you can build to automate manual workflows.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions (FAQs)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How can I host the Telegram bot for free?
&lt;/h3&gt;

&lt;p&gt;Yes. There are free hosting services where you can host your Telegram bot. However, this particular bot requires a persistent memory and state persistence. It also runs and stores cron-jobs on them, hence you will need a more sophisticated solution. &lt;/p&gt;

&lt;p&gt;A new article on how to solve this will be ready soon.&lt;/p&gt;

&lt;h3&gt;
  
  
  Can I host this Telegram bot on Google Cloud functions?
&lt;/h3&gt;

&lt;p&gt;No. Google Cloud functions is suitable for hosting applications that does not require persistence or are stateless. They are only triggered when an action or event occurs in an application.&lt;/p&gt;

&lt;h3&gt;
  
  
  Which other Python libraries can be used to build a Telegram bot?
&lt;/h3&gt;

&lt;p&gt;There are numerous Python libraries that can be used to build telegram bots. &lt;a href="https://blog.finxter.com/top-10-python-libraries-to-create-your-telegram-bot-easily-github/" rel="noopener noreferrer"&gt;Here&lt;/a&gt; is a list of top 10 Python libraries for developing Telegram bots&lt;/p&gt;

</description>
      <category>python</category>
      <category>telegram</category>
      <category>chatbot</category>
      <category>database</category>
    </item>
    <item>
      <title>How to handle media uploads in Django</title>
      <dc:creator>Edun Rilwan</dc:creator>
      <pubDate>Sun, 12 May 2024 21:47:37 +0000</pubDate>
      <link>https://forem.com/emiloju/how-to-handle-media-uploads-in-django-1kpc</link>
      <guid>https://forem.com/emiloju/how-to-handle-media-uploads-in-django-1kpc</guid>
      <description>&lt;p&gt;It is a common feature in most web apps to prompt users to upload files or documents. These files include image files (&lt;strong&gt;.jpeg, .jpg, .png&lt;/strong&gt;) or other document files like PDFs, audio files (.mp3), etc.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://docs.djangoproject.com/en/5.0/" rel="noopener noreferrer"&gt;Django&lt;/a&gt;, a very popular and powerful web framework in Python provides built-in methods and utilities that enables seamless upload and storage of files in web applications.&lt;/p&gt;

&lt;p&gt;In Django, there are 3 ways to accept media uploads from users. They include the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Django ModelForms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Django Forms&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pure HTML forms&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In this tutorial, you will learn how to handle media uploads with Django ModelForms using a local storage. Django's &lt;a href="https://docs.djangoproject.com/en/5.0/topics/forms/modelforms/" rel="noopener noreferrer"&gt;ModelForm&lt;/a&gt; provides a convenient and faster way to create forms in Django. They are also very customizable.&lt;/p&gt;

&lt;p&gt;By the end of this tutorial, you would have learned the following:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How to receive media files via a ModelForm &lt;/li&gt;
&lt;li&gt;How to handle and save the received file object&lt;/li&gt;
&lt;li&gt;How to display saved image files on a web page&lt;/li&gt;
&lt;li&gt;How to make saved files available for download by users. &lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Project requirements
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;: You need a Python interpreter installed on your computer. Preferably python&amp;gt;=3.8&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Django&lt;/strong&gt;: The latest version of Django is required. &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Create a new virtual environment using the &lt;code&gt;virtualenv&lt;/code&gt; package in Python:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;virtualenv env
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use the command below to activate the virtual environment:&lt;/p&gt;

&lt;p&gt;Windows:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env\Scripts\activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Linux/macOS:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;env/bin/activate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Install Django in the new virtual environment:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install Django
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;&lt;strong&gt;Note&lt;/strong&gt;&lt;/em&gt;: To confirm your virtual environment (VE) is activated, check to see the name of your VE in a bracket like the one below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;(env) &amp;gt;&amp;gt;&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Getting started with Django file upload
&lt;/h2&gt;

&lt;p&gt;You need to create a new Django project and app to learn how Django handles media uploads. &lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Django project
&lt;/h3&gt;

&lt;p&gt;Follow the steps below to create a Django project:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Open your terminal(check that your VE is activated).&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Run the command below to create a Django project&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;django-admin startproject my_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Enter the command below to move into the new &lt;em&gt;my_project&lt;/em&gt; folder&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;cd my_project
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Next step is to create an app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Create a Django app
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Run the command below in the terminal to create an app named &lt;br&gt;
&lt;strong&gt;file_upload&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py startapp file_upload

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

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open the settings.py file in your project folder and add the app to &lt;br&gt;
the INSTALLED_APPS list as seen below:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#my_project/settings.py

INSTALLED_APPS = [
   ...
   "file_upload"
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Django MEDIA ROOT and MEDIA URL
&lt;/h2&gt;

&lt;p&gt;MEDIA_ROOT and MEDIA_URL variables are crucial in handling media uploads in a local filesystem storage. They provide a way of accessing and storing saved files in a Django app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Django MEDIA ROOT
&lt;/h3&gt;

&lt;p&gt;MEDIA ROOT refers to the root folder where all uploaded files are stored in a Django web app. It refers to the folder where all files can be accessed from.&lt;/p&gt;

&lt;h4&gt;
  
  
  How to set up a MEDIA ROOT
&lt;/h4&gt;

&lt;p&gt;To set up a MEDIA ROOT, do the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Create a new folder named &lt;em&gt;uploads&lt;/em&gt; in your project folder&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;
#file_arrangement
my_project
   \__ my_project
   \__ file_upload
   \__ uploads
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Open settings.py file and include the following line of code in it:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MEDIA_ROOT = BASE_DIR / "uploads"
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This setting instructs Django to save uploaded files to the uploads folder within your project, that is &lt;strong&gt;BASE_DIR&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;BASE_DIR is a variable automatically set by Django. It contains the file path to your current project, which is also known as the base directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  Django MEDIA URL
&lt;/h3&gt;

&lt;p&gt;MEDIA URL refers to the root URL that renders user-uploaded files present in the MEDIA ROOT of a web app. &lt;/p&gt;

&lt;p&gt;For example, if you uploaded an image file titled &lt;em&gt;example.jpg&lt;/em&gt;, it is rendered on the web via a url like the one below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://mysite/%5BMEDIA_URL%5D/%5BMEDIA_ROOT%5D/images/example.jpg" rel="noopener noreferrer"&gt;https://mysite/[MEDIA_URL]/[MEDIA_ROOT]/images/example.jpg&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The URL above is analyzed as follows:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;mysite is the domain name of your website.&lt;/li&gt;
&lt;li&gt;media_url is the base/root url for uploaded media.&lt;/li&gt;
&lt;li&gt;media_root is the root folder for uploaded media.&lt;/li&gt;
&lt;li&gt;images is another folder inside the MEDIA ROOT that stores image files only&lt;/li&gt;
&lt;li&gt;example.jpg is the file name.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  How to set up a MEDIA URL
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;Open your project's settings.py file and add the code below:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;   MEDIA_URL = "files/"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Open the urls.py file in your project folder&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add the following import statements:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.conf import settings
from django.conf.urls.static import static
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Write the following code below the urlpatterns list in the urls.py &lt;br&gt;
file:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;urlpatterns += static(settings.MEDIA_URL, 
    document_root=settings.MEDIA_ROOT)
&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In the steps above, a MEDIA URL, named &lt;em&gt;files&lt;/em&gt; is created. This root url for media uploads is finally added to the list of URLs. &lt;/p&gt;

&lt;h2&gt;
  
  
  Create a Database Model
&lt;/h2&gt;

&lt;p&gt;The next step is to create a database model named Uploads. This model will have three field types which includes: A Charfield,  &lt;a href="https://docs.djangoproject.com/en/5.0/ref/models/fields/#django.db.models.FileField" rel="noopener noreferrer"&gt;FileField&lt;/a&gt; and an &lt;a href="https://docs.djangoproject.com/en/5.0/ref/models/fields/#django.db.models.ImageField" rel="noopener noreferrer"&gt;ImageField&lt;/a&gt;. Follow the steps below to create the &lt;code&gt;Uploads&lt;/code&gt; model:&lt;/p&gt;

&lt;p&gt;a. Go to the models.py file in your app&lt;br&gt;
b. Create a simple database model titled &lt;code&gt;Uploads&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.db import models

# Create your models here.
class Uploads(models.Model):
   caption = models.CharField(unique=False, null=False, blank=False, max_length=20, default="An image")
   file_field = models.FileField(upload_to="files", null=False, blank=False)
   image_field = models.ImageField(upload_to="images", null=False, blank=False)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The functions of each field are listed below:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;CharField&lt;/code&gt; stores textual data.&lt;br&gt;
&lt;code&gt;FileField&lt;/code&gt; stores files such as PDFs, docx, etc.&lt;br&gt;
&lt;code&gt;ImagField&lt;/code&gt; stores image files.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;FileField&lt;/code&gt; and &lt;code&gt;ImageField&lt;/code&gt; have an &lt;code&gt;upload_to&lt;/code&gt; argument. This refers to a new folder inside your project's MEDIA ROOT where the content of each file field will be stored. It is created automatically when a file is uploaded to each field. &lt;/p&gt;
&lt;h3&gt;
  
  
  Run migrations for the database
&lt;/h3&gt;

&lt;p&gt;Run database migrations via the steps below:&lt;/p&gt;

&lt;p&gt;a. Open the terminal and run this command&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py makemigrations

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

&lt;/div&gt;



&lt;p&gt;b. Run the command below to add the migrations to a file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py migrate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Migrations help to track changes made to the database.&lt;/p&gt;

&lt;h2&gt;
  
  
  Build a ModelForm
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to build a ModelForm:&lt;/p&gt;

&lt;p&gt;a. Create a &lt;code&gt;forms.py&lt;/code&gt; file in your app folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project
   \___ file_upload
          \___ ...
          \___ forms.py
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;b. Add the code below to the file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django import forms
from .models import Uploads

class FileUploadForm(forms.ModelForm):
   class Meta:
       model = Uploads
       fields = "__all__"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The code above creates a &lt;code&gt;ModelForm&lt;/code&gt;based on the structure of the Uploads model. The &lt;code&gt;__all__&lt;/code&gt; method means that the form should contain all the fields in the database model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Create a view for media uploads
&lt;/h2&gt;

&lt;p&gt;Follow the steps below to create a view function:&lt;/p&gt;

&lt;p&gt;a. Open the views.py file&lt;br&gt;
b. Add the code below&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.shortcuts import render, redirect
from .forms import FileUploadForm
from .models import Uploads


# Create your views here.

def handle_upload(request):
   form = FileUploadForm()
   if request.method == "POST":
       form = FileUploadForm(request.POST, request.FILES)
       if form.is_valid():
           form.save()

   return render(request, "form.html", context={"form": form})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The view function renders the ModelForm through a &lt;code&gt;form.html&lt;/code&gt; file that will be created in the next step. &lt;/p&gt;

&lt;p&gt;This view also accepts POST requests from the ModelForm when the user submits their uploads. It verifies that there are no form errors before saving the user input. &lt;/p&gt;

&lt;p&gt;It is necessary to include the &lt;code&gt;request.FILES&lt;/code&gt; and &lt;code&gt;request.POST&lt;/code&gt;dictionary in the form class for each POST request. &lt;/p&gt;

&lt;p&gt;POST requests are usually sent with a &lt;code&gt;requests.POST&lt;/code&gt; dictionary. When files are involved, a &lt;code&gt;request.FILES&lt;/code&gt; dictionary is added. &lt;/p&gt;

&lt;p&gt;Uploaded files are stored temporarily in a &lt;code&gt;request.FILES&lt;/code&gt; dictionary. Each key in this dictionary holds the data of a particular file field in the form submitted.&lt;/p&gt;

&lt;p&gt;For example, if you created a form with a FileField named "images", you can access the file data in your views function via the code below:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;request.FILES['images']
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Create the homepage file
&lt;/h2&gt;

&lt;p&gt;You need to create an HTML file named &lt;code&gt;form.html&lt;/code&gt; that'll render the form. Follow the steps below to do create a web page:&lt;/p&gt;

&lt;p&gt;a. Create a new folder named templates in your project&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project
   \___ my_project
   \___ ...
   \___ templates
   \___ ...

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

&lt;/div&gt;



&lt;p&gt;b. Create a new HTML file named &lt;code&gt;form.html&lt;/code&gt; inside this folder&lt;br&gt;
c. Add the code below inside this file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;head&amp;gt;
   &amp;lt;meta charset="UTF-8"&amp;gt;
   &amp;lt;title&amp;gt;Forms practise&amp;lt;/title&amp;gt;
&amp;lt;/head&amp;gt;


&amp;lt;body&amp;gt;

&amp;lt;form method="post" action="" enctype="multipart/form-data"&amp;gt;
   {% csrf_token %}

   {{ form.as_p }}
   &amp;lt;input type="submit" name="submit"&amp;gt;

&amp;lt;/form&amp;gt;

&amp;lt;/body&amp;gt; 
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The HTML code above displays the form to the user through &lt;code&gt;form.as_p&lt;/code&gt;, while the &lt;code&gt;{% csrf_token %}&lt;/code&gt; enables secured transfer of form data&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;enctype="multipart/form-data"&lt;/code&gt; variable enables the form to send file data.&lt;/p&gt;

&lt;p&gt;Finally, you need to register the templates folder with Django so it can access it. &lt;/p&gt;

&lt;p&gt;To do this, open the &lt;code&gt;settings.py&lt;/code&gt; file in your project folder and add the code below to the &lt;code&gt;TEMPLATES&lt;/code&gt;list in this file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TEMPLATES = [
    {
        '...',
        'DIRS': [os.path.join(BASE_DIR, 'templates')],
        '...'
    }
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Configure app urls.
&lt;/h2&gt;

&lt;p&gt;The next step is to bind the view function to a url. Follow the steps below to do this:&lt;/p&gt;

&lt;p&gt;a. Open the urls.py file in your app folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project
   \___ file_upload
      \___ ...
      \___ urls.py
      \___ ...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;b. Add the code below to this file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.urls import path
from file_upload import views

urlpatterns = [path("", views.handle_upload, name="home")]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;c. Open the urls.py file in your project folder&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;my_project
  \___ my_project
     \___ ...
     \___ urls.py
     \___ ...

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

&lt;/div&gt;



&lt;p&gt;d. Register your app URLs to the urlpatterns list in your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.contrib import admin
from django.urls import path, include
from django.conf import settings
from django.conf.urls.static import static

urlpatterns = [
    "...",
    path("", include("file_upload.urls")),
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Test the app
&lt;/h2&gt;

&lt;p&gt;a. Run the command below in your terminal&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;python manage.py runserver
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;b. Click on the link to open the web page&lt;br&gt;
c. Click on the &lt;strong&gt;_Choose file _&lt;/strong&gt;button to upload your files.&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%2Fccqlj81cn2x8fwsb379u.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%2Fccqlj81cn2x8fwsb379u.png" alt="Web page showing how to upload files in Django" width="800" height="122"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;c. Enter a caption of your choice.&lt;br&gt;
d. Submit the form.&lt;br&gt;
e. Proceed to the &lt;code&gt;upload&lt;/code&gt;folder, you would see two new folders named &lt;code&gt;files&lt;/code&gt;and &lt;code&gt;images&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the files folder, you would see the document you uploaded.&lt;br&gt;
In the images folder, you would see the image file you uploaded.&lt;/p&gt;
&lt;h2&gt;
  
  
  Display user uploads
&lt;/h2&gt;

&lt;p&gt;In most cases, users would need access to their files. You can display media uploads to users using a simple &lt;code&gt;.url&lt;/code&gt; attribute. The steps below show you how to display uploaded files to users:&lt;/p&gt;

&lt;p&gt;a. Open the views.py file&lt;br&gt;
b. Update the view function to contain the &lt;code&gt;Uploads&lt;/code&gt; model and pass it to the render function.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;from django.shortcuts import render, redirect
from .forms import FileUploadForm
from .models import Uploads

# Create your views here.
def handle_upload(request):
   form = FileUploadForm()
   uploads = Uploads.objects.all()
   if request.method == "POST":
       form = FileUploadForm(request.POST, request.FILES)
       if form.is_valid():
           form.save()
           return redirect("home")

   return render(request, "form.html", context={"form": form, "uploads": uploads})
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;c. Proceed to the &lt;code&gt;form.html&lt;/code&gt; file&lt;br&gt;
d. Add this code below the form in the HTML file&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{% for media in uploads %}
{% if media %}

&amp;lt;div style="margin: 20px 0px;"&amp;gt;

   &amp;lt;img src="{{ media.image_field.url }}" height="200px;" width="200px;"&amp;gt;
   &amp;lt;p&amp;gt;{{ media.caption }}&amp;lt;/p&amp;gt;
   &amp;lt;p&amp;gt;Download your file &amp;lt;a href="{{ media.file_field.url }}"&amp;gt;here&amp;lt;/a&amp;gt;&amp;lt;/p&amp;gt;

&amp;lt;/div&amp;gt;

{% endif %}
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;e. Wait for the server to reload&lt;br&gt;
f. Open the browser and refresh the web page&lt;/p&gt;

&lt;p&gt;This process simply queries all records in the Uploads model, adds it to the context dictionary so it can be accessed and used in the HTML file.&lt;/p&gt;

&lt;p&gt;The image you uploaded will be displayed on the web page and your document will be available for download. Here is an example below:&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%2F4enm63sl7y3cb7wruz6k.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%2F4enm63sl7y3cb7wruz6k.png" alt="An image displaying user uploads in Django" width="467" height="319"&gt;&lt;/a&gt;&lt;/p&gt;

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

&lt;p&gt;Leveraging Django's ModelForms for file uploads simplifies the process of handling user-submitted files. Whether it's for pictures, documents or any other type of file, ModelForms provides an efficient and structured way to manage file uploads in Django.&lt;/p&gt;

&lt;h2&gt;
  
  
  Frequently asked questions (FAQs)
&lt;/h2&gt;

&lt;h4&gt;
  
  
  What type of media files can be uploaded in Django?
&lt;/h4&gt;

&lt;p&gt;You can upload files of different sizes and formats in Django. However, if you are using a local file-storage, you will get to a point where the memory gets full and you will need to use a cloud service like AWS, Google clouds, etc.&lt;/p&gt;

&lt;p&gt;An overloaded memory will affect site performance.&lt;/p&gt;

&lt;h4&gt;
  
  
  How can I secure media uploads in Django?
&lt;/h4&gt;

&lt;p&gt;There are different security measures you can apply to media uploads in Django. They include:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verifying the file formats before saving them. That is, by checking the file extensions, such as .png, .jpg, .pdf, etc. to be sure they are safe&lt;/li&gt;
&lt;li&gt; Sometimes, reading file metadata before saving is also important. However, you may need to use third-party Python libraries to like Pillow to implement this. &lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  How to handle media uploads in a production environment?
&lt;/h4&gt;

&lt;p&gt;It is not advisable to use a local file storage in a production environment. This is because such implementation may have security implications on stored files.&lt;/p&gt;

&lt;p&gt;Using cloud services enhance application performance and provide more flexibility and security to uploaded files.&lt;/p&gt;

&lt;h4&gt;
  
  
  Can I resize or manipulate uploaded images in Django?
&lt;/h4&gt;

&lt;p&gt;Yes, you can. &lt;/p&gt;

&lt;p&gt;Before saving an uploaded file, you can get the file object from the &lt;code&gt;request.FILES&lt;/code&gt; dictionary and use it directly with a file manipulation library like Pillow, OpenCV, etc. in Python.&lt;/p&gt;

&lt;p&gt;After editing the image, you can then save it to a file of your choice. &lt;/p&gt;

&lt;h4&gt;
  
  
  How does the Django Filesystem work?
&lt;/h4&gt;

&lt;p&gt;When a user uploads a file in Django, the Django &lt;a href="https://docs.djangoproject.com/en/5.0/ref/files/" rel="noopener noreferrer"&gt;filesystem &lt;/a&gt;has two handlers that handles file uploads. For small files, they are saved to memory using the (handler) handler while the (handler) handler handles large files by saving them to a temporary file. &lt;/p&gt;

&lt;h4&gt;
  
  
  What happens when I delete saved files in Django?
&lt;/h4&gt;

&lt;p&gt;Deleted files are immediately removed from the MEDIA ROOT of your app. Therefore, trying to access a deleted file through the &lt;code&gt;.url&lt;/code&gt; attribute will throw an error.&lt;/p&gt;

</description>
      <category>django</category>
      <category>python</category>
      <category>webdev</category>
      <category>files</category>
    </item>
  </channel>
</rss>
