[{"data":1,"prerenderedAt":20024},["ShallowReactive",2],{"article_list_python_":3},[4,249,2530,5326,7261,8393,9467,12105,13770,17142],{"_path":5,"_dir":6,"_draft":7,"_partial":7,"_locale":8,"title":9,"description":10,"publishDate":11,"image":12,"tags":13,"excerpt":10,"body":17,"_type":240,"_id":241,"_source":242,"_file":243,"_stem":244,"_extension":245,"author":246},"/ewahl/2025-06/argued_with_ai","2025-06",false,"","I Argued With an AI for 20 Minutes About Async Code &mdash; And I'm Surprisingly Happy","If you have ever spent twenty minutes debating an obscure AWS Lambda invocation pattern with an AI, you might question your life choices. But here I am: amused by the wasted time but ultimately happy with the outcome and understanding I gained.","2026-06-01","/ewahl/2025-06/img/argued_with_ai.png",[14,15,16],"devops","aws","python",{"type":18,"children":19,"toc":231},"root",[20,27,32,37,59,81,86,93,98,103,109,114,119,124,129,134,139,144,149,155,160,173,179,184,189,200,216,221,226],{"type":21,"tag":22,"props":23,"children":24},"element","p",{},[25],{"type":26,"value":10},"text",{"type":21,"tag":22,"props":28,"children":29},{},[30],{"type":26,"value":31},"Let's rewind.",{"type":21,"tag":22,"props":33,"children":34},{},[35],{"type":26,"value":36},"I was refining some Python code for a Lambda function. My goal was straightforward: apply internal coding standards properly — minimal comments, clear docstrings, manageable complexity — nothing wild. The AI handled it well, refining my code with consistency.",{"type":21,"tag":22,"props":38,"children":39},{},[40,42,49,51,57],{"type":26,"value":41},"But things went sideways when we reached a particular function named ",{"type":21,"tag":43,"props":44,"children":46},"code",{"className":45},[],[47],{"type":26,"value":48},"_invoke_webhook_sender",{"type":26,"value":50},". The innocent-looking function asynchronously invokes another Lambda, ",{"type":21,"tag":43,"props":52,"children":54},{"className":53},[],[55],{"type":26,"value":56},"send_result_to_webhook",{"type":26,"value":58},". In theory, straightforward enough:",{"type":21,"tag":60,"props":61,"children":62},"ul",{},[63,76],{"type":21,"tag":64,"props":65,"children":66},"li",{},[67,69,74],{"type":26,"value":68},"Invoke ",{"type":21,"tag":43,"props":70,"children":72},{"className":71},[],[73],{"type":26,"value":56},{"type":26,"value":75}," asynchronously.",{"type":21,"tag":64,"props":77,"children":78},{},[79],{"type":26,"value":80},"Let the downstream Lambda handle retries, failures, and all the messy reality of hitting an external webhook.",{"type":21,"tag":22,"props":82,"children":83},{},[84],{"type":26,"value":85},"Simple, right? Well, not so fast. Asynchronous programming often feels deceptively straightforward, but it's notoriously subtle and complex for developers. Its inherent complexity stems from concurrency, race conditions, and error handling intricacies — situations where timing and order aren't guaranteed. LLMs struggle particularly with this because their expertise is fundamentally pattern-driven, relying heavily on recognizing familiar structures. When nuances or edge cases arise that aren't widely represented in their training data, such as subtle failures in asynchronous handoffs, they can miss critical details.",{"type":21,"tag":87,"props":88,"children":90},"h3",{"id":89},"enter-the-rabbit-hole",[91],{"type":26,"value":92},"Enter the Rabbit Hole",{"type":21,"tag":22,"props":94,"children":95},{},[96],{"type":26,"value":97},"The AI, ever helpful, confidently suggested logging any invocation failures, but then quietly swallowing the exceptions. \"Best practice!\" it cheerfully asserted. After all, async invocation means you don't need to wait around. Fire and forget.",{"type":21,"tag":22,"props":99,"children":100},{},[101],{"type":26,"value":102},"But here's the rub: what if the \"fire\" part itself never actually happens? What if AWS rejects your call outright — wrong permissions, malformed request, throttling? In that scenario, the downstream Lambda never even gets invoked. Its dead-letter queue (DLQ) remains empty. Your upstream DLQ doesn't notice, because technically your handler succeeded (it swallowed the exception, remember?). Suddenly, you've got a silent failure on your hands.",{"type":21,"tag":87,"props":104,"children":106},{"id":105},"thus-began-my-20-minute-debate",[107],{"type":26,"value":108},"Thus Began My 20-Minute Debate",{"type":21,"tag":22,"props":110,"children":111},{},[112],{"type":26,"value":113},"\"This isn't right,\" I told the AI.",{"type":21,"tag":22,"props":115,"children":116},{},[117],{"type":26,"value":118},"\"It adheres to serverless best practices,\" it replied calmly.",{"type":21,"tag":22,"props":120,"children":121},{},[122],{"type":26,"value":123},"\"But it can fail silently!\"",{"type":21,"tag":22,"props":125,"children":126},{},[127],{"type":26,"value":128},"\"Decoupling is generally beneficial.\"",{"type":21,"tag":22,"props":130,"children":131},{},[132],{"type":26,"value":133},"\"Decoupling doesn't mean ignoring failures,\" I insisted.",{"type":21,"tag":22,"props":135,"children":136},{},[137],{"type":26,"value":138},"\"You should have a DLQ downstream.\"",{"type":21,"tag":22,"props":140,"children":141},{},[142],{"type":26,"value":143},"\"But the invocation itself can fail — then the downstream DLQ is useless!\"",{"type":21,"tag":22,"props":145,"children":146},{},[147],{"type":26,"value":148},"After several rounds of politely helpful but increasingly unproductive exchanges, I realized something critical: the LLM was logically consistent and yet stuck in a completely wrong pattern it was unable recover from. It followed best practice guidelines blindly but could not understand the real context of passing off an asynchronous action.",{"type":21,"tag":87,"props":150,"children":152},{"id":151},"why-ai-needs-humans-still",[153],{"type":26,"value":154},"Why AI Needs Humans (Still)",{"type":21,"tag":22,"props":156,"children":157},{},[158],{"type":26,"value":159},"Current Large Language Models (LLMs) — or \"AI\" if you prefer — demonstrate a powerful ability to discern and replicate intricate statistical patterns from vast datasets. They are becoming adept at tasks like enforcing coding standards by identifying deviations from common practices, suggesting code completions aligned with established styles, and refactoring code based on learned optimal solutions.",{"type":21,"tag":22,"props":161,"children":162},{},[163,165,171],{"type":26,"value":164},"However, they don't (and, in their current architectural paradigm, likely ",{"type":21,"tag":166,"props":167,"children":168},"em",{},[169],{"type":26,"value":170},"will not ever",{"type":26,"value":172},") possess true \"developer understanding\" of a codebase. This understanding isn't mystical; it's a complex amalgamation of hard-earned experiential knowledge, nuanced contextual understanding, and an appreciation for the tangible consequences of system behavior, including the understanding that two correctly-constructed lambdas can combine to silently drop important notifications.",{"type":21,"tag":87,"props":174,"children":176},{"id":175},"did-i-waste-20-minutes-of-my-life",[177],{"type":26,"value":178},"Did I Waste 20 Minutes of My Life?",{"type":21,"tag":22,"props":180,"children":181},{},[182],{"type":26,"value":183},"In the short term, yes. Once I realized the problem, I could have fixed it quickly myself and didn't really benefit from trying to \"explain\" the problem to an LLM.",{"type":21,"tag":22,"props":185,"children":186},{},[187],{"type":26,"value":188},"In the larger scope, though, I don't think this was a waste. Agentic coding is a new tool for developers, and this helped me understand how to use the tool effectively and recognize its limitations. In the future, I expect to produce better quality, more efficient code with the help of an agentic coding agent. This experiment helped transform my theoretical understanding of how an LLM works into practical experience of how to more effectively integrate agentic coding into my workflow.",{"type":21,"tag":190,"props":191,"children":193},"h2",{"id":192},"working-strategically-coding-using-agentic-ai-tools",[194],{"type":21,"tag":195,"props":196,"children":197},"strong",{},[198],{"type":26,"value":199},"Working Strategically: Coding Using Agentic AI Tools",{"type":21,"tag":22,"props":201,"children":202},{},[203,205,214],{"type":26,"value":204},"The hype/fear around AI is loud: \"Create software without developers!\" \"AI's taking our jobs!\" A recent ",{"type":21,"tag":206,"props":207,"children":211},"a",{"href":208,"rel":209},"https://www.npr.org/2025/05/30/nx-s1-5413387/vibe-coding-ai-software-development",[210],"nofollow",[212],{"type":26,"value":213},"NPR article",{"type":26,"value":215}," quotes a tech leader predicting radical change: \"Instead of having coding assistance, we're going to have actual AI coders and then an AI project manager, an AI designer and, over time, an AI manager of all of this. And we're going to have swarms of these things.\"",{"type":21,"tag":22,"props":217,"children":218},{},[219],{"type":26,"value":220},"I see things differently. Agentic coding tools streamline our workflow, letting us skip mundane tasks. But AI still lacks that intuitive leap of understanding and ability to work through chains of logic cultivated through years of tackling complex systems and subtle bugs.",{"type":21,"tag":22,"props":222,"children":223},{},[224],{"type":26,"value":225},"Scaling current LLMs with more computational power won't magically enable chains of AI agents to operate fully autonomously without human oversight. Looking at how LLM technology actually works, such a leap will require fundamental advancements in AI technology, not just more compute.",{"type":21,"tag":22,"props":227,"children":228},{},[229],{"type":26,"value":230},"AI isn't replacing me anytime soon. Instead, it's becoming part of my coding toolkit, allowing me to focus on deeper architectural challenges where human insight really shines. That's the part of development I enjoy most — and it's a future I'm genuinely looking forward to.",{"title":8,"searchDepth":232,"depth":232,"links":233},3,[234,235,236,237,238],{"id":89,"depth":232,"text":92},{"id":105,"depth":232,"text":108},{"id":151,"depth":232,"text":154},{"id":175,"depth":232,"text":178},{"id":192,"depth":239,"text":199},2,"markdown","content:ewahl:2025-06:argued_with_ai.md","content","ewahl/2025-06/argued_with_ai.md","ewahl/2025-06/argued_with_ai","md",{"user":247,"name":248},"ewahl","Edward F. Wahl",{"_path":250,"_dir":251,"_draft":7,"_partial":7,"_locale":8,"title":252,"description":253,"tags":254,"excerpt":253,"image":255,"publishDate":256,"body":257,"_type":240,"_id":2526,"_source":242,"_file":2527,"_stem":2528,"_extension":245,"author":2529},"/ewahl/2025-05/escape_deployment_hell","2025-05","Escape Deployment Hell: IaC, CDK, Ephemeral Environments, and the Pragmatic Path to Platform Power","Another Friday afternoon, another deployment fire. If this sounds familiar, you're not alone. On too many projects, the chasm between application code and infrastructure management breeds manual configuration nightmares, crippling complexity, and agonizingly slow development cycles. But what if your team could sidestep this chaos, focusing on building features instead of constantly battling deployment gremlins?",[14,15,16],"/ewahl/2025-05/img/deployment_hell.png","2025-05-13",{"type":18,"children":258,"toc":2500},[259,263,268,291,305,338,344,349,361,366,378,384,415,421,435,448,453,459,530,569,574,580,594,1653,1658,1663,1687,1693,1707,1713,1754,1760,1790,1796,1826,1832,1878,1884,1914,1920,1950,1956,1986,1991,1997,2002,2007,2012,2017,2022,2028,2033,2038,2044,2049,2055,2060,2390,2396,2494],{"type":21,"tag":22,"props":260,"children":261},{},[262],{"type":26,"value":253},{"type":21,"tag":22,"props":264,"children":265},{},[266],{"type":26,"value":267},"The solution lies in integrating Infrastructure as Code (IaC) with your application code, to support ephemeral environments. When every feature can be realistically tested in isolation and infrastructure changes are transparent, tested, and automated the benefits are transformative:",{"type":21,"tag":60,"props":269,"children":270},{},[271,276,281,286],{"type":21,"tag":64,"props":272,"children":273},{},[274],{"type":26,"value":275},"Slash Debugging Time: Catch infrastructure and compatibility issues immediately.",{"type":21,"tag":64,"props":277,"children":278},{},[279],{"type":26,"value":280},"Boost Team Velocity: Eliminate bottlenecks and enable parallel development without conflicts.",{"type":21,"tag":64,"props":282,"children":283},{},[284],{"type":26,"value":285},"Keep Crises Small: Switch focus easily without disrupting existing goals.",{"type":21,"tag":64,"props":287,"children":288},{},[289],{"type":26,"value":290},"Enhance Release Confidence: Test so that \"it passed QA\" actually means \"it will work in production\" without creating a QA bottleneck.",{"type":21,"tag":22,"props":292,"children":293},{},[294,296,303],{"type":26,"value":295},"This approach is more than just good DevOps; it's the heart of modern platform engineering. Platform engineering is essential to prevent agile methodologies from becoming chaotic, as it equips teams with the framework and discipline to build and manage ephemeral environments vital for isolated testing and rapid, controlled iteration. That impact is why Gartner identifies Platform Engineering as a top technology trend (Gartner, 2023: ",{"type":21,"tag":206,"props":297,"children":300},{"href":298,"rel":299},"https://www.gartner.com/en/articles/gartner-top-10-strategic-technology-trends-for-2024",[210],[301],{"type":26,"value":302},"Top 10 Strategic Technology Trends for 2024",{"type":26,"value":304},"). By leveraging tools like AWS CDK, you can achieve many of the coveted benefits of a full-scale Internal Developer Platform (IDP) without the overwhelming cost and complexity, putting true platform power within reach.",{"type":21,"tag":306,"props":307,"children":308},"blockquote",{},[309,315],{"type":21,"tag":190,"props":310,"children":312},{"id":311},"key-ideas",[313],{"type":26,"value":314},"Key Ideas",{"type":21,"tag":60,"props":316,"children":317},{},[318,323,328,333],{"type":21,"tag":64,"props":319,"children":320},{},[321],{"type":26,"value":322},"End Frustrating Software Releases: Many teams struggle with slow, error-prone software releases because their testing setups create bottlnecks and don't match the real, live system.",{"type":21,"tag":64,"props":324,"children":325},{},[326],{"type":26,"value":327},"Develop and Test New Features Perfectly with \"Pop-Up\" Systems: Called \"Ephemeral Environments\" built using Infrastructure as Code (IaC) these are a temporary replica of the live system created for every new feature. This allows each update to be tested thoroughly and safely in isolation before it goes live, after which the temporary system disappears.",{"type":21,"tag":64,"props":329,"children":330},{},[331],{"type":26,"value":332},"Build Your System Setup with Code, Not Clicks: The magic behind these pop-up systems is defining all the necessary IT infrastructure (servers, databases, networks) using code that lives alongside application code in the same repo — called \"Infrastructure as Code\" or IaC. This automates the creation of consistent, reliable development and testing environments every time.",{"type":21,"tag":64,"props":334,"children":335},{},[336],{"type":26,"value":337},"A Practical Path to Power: Instead of building or buying a massive, complex internal system — often called an Internal Developer Platform — tools like AWS Cloud Development Kit (CDK) allow DevOps teams and app developers to use their existing coding skills to set up and manage these automated environments. This makes the benefits of faster, more reliable deployments accessible to more teams without overwhelming cost or effort.",{"type":21,"tag":190,"props":339,"children":341},{"id":340},"the-painful-path-to-ephemeral-environments",[342],{"type":26,"value":343},"The Painful Path to Ephemeral Environments",{"type":21,"tag":22,"props":345,"children":346},{},[347],{"type":26,"value":348},"My passion for ephemeral environments isn't just theoretical; it's forged from real-world deployment pain early in my career. I vividly recall working on a Python web application where our traditional approach to testing using a single, IT maintained, shared staging environment led to constant headaches, stemming from two core, intertwined problems.",{"type":21,"tag":22,"props":350,"children":351},{},[352,354,359],{"type":26,"value":353},"First, there is the issue of environment divergence. Our staging environment was ",{"type":21,"tag":166,"props":355,"children":356},{},[357],{"type":26,"value":358},"supposed",{"type":26,"value":360}," to be a twin of the production configuration, the place where we could confidently say, \"If it works here, it'll work live.\" But because both staging and, at times, production received manual tweaks or patches that weren't always replicated across both, they would inevitably drift apart. These subtle inconsistencies were a minefield. So, as developers, we'd push our code from our local setups to the mainline branch, and it would be deployed to staging for \"final\" validation. But occasionally, tests would pass on staging, and everything would seem fine. The real pain would come later: we'd promote that 'staging-approved' code to production, only for critical bugs to suddenly surface. These were the issues that left us scrambling to figure out a hotfix or rollback to rescue production.",{"type":21,"tag":22,"props":362,"children":363},{},[364],{"type":26,"value":365},"The other issue is the chaos and bottlneck invited by our single, mainline-focused testing envrionment. We would merge multiple, distinct features together into the mainline branch for testing. If a problem surfaced there — and it often did — everything was entangled, leading to frantic pre-release 'cherry-picking' sessions or just outright delaying the whole batch. Yet, the relentless pressure to ship sometimes meant we would need to push those changes to production with less testing than they really needed. I still feel that knot of anxiety tightening in my stomach, knowing we were rolling the dice. And the 'day-after' scrambles: as panicked messages came in about 'Users are seeing unexpected errors!'. It was always all hands on deck—a blur of late nights and urgent calls, trying to diagnose issues that had been masked by our process, then facing the horrible choice: attempt a risky, rushed hotfix, or execute a full, rollback that took working features down with the bad. This exhausting cycle shattered client trust and turned release schedule into guesswork.",{"type":21,"tag":22,"props":367,"children":368},{},[369,371,376],{"type":26,"value":370},"After enduring these twin challenges, the migration to ephermeral environments created wonderful new results. 'Works on my machine' finally translated reliably to 'works in integration,' because each test environment was an automated replica of production. And because every feature was tested thoroughly in its own dedicated space ",{"type":21,"tag":166,"props":372,"children":373},{},[374],{"type":26,"value":375},"before",{"type":26,"value":377}," merging to mainline, the bottlenecks and release fire-drills vanished. Our release predictability dramatically improved, restoring client trust and confidence. I have no interest in ever returning to the stress of unreliable staging, mainline blockages, and anxious stakeholder conversations.",{"type":21,"tag":190,"props":379,"children":381},{"id":380},"the-allure-and-cost-of-the-internal-developer-platform-idp",[382],{"type":26,"value":383},"The Allure — and Cost — of the Internal Developer Platform (IDP)",{"type":21,"tag":22,"props":385,"children":386},{},[387,389,396,398,404,406,413],{"type":26,"value":388},"The desire to solve these kinds of problems at scale often leads organizations to consider creating an Internal Developer Platform (IDP). IDPs aim to streamline developer workflows by providing centralized tooling, self-service capabilities for infrastructure, standardized deployment pathways, and reduced cognitive load, thereby significantly improving Developer Experience (DevEx) — aligning nicely with ",{"type":21,"tag":206,"props":390,"children":393},{"href":391,"rel":392},"https://tag-app-delivery.cncf.io/whitepapers/platform-eng-maturity-model/",[210],[394],{"type":26,"value":395},"CNCF's Platform Engineering Maturity Model",{"type":26,"value":397},". However, building and maintaining a truly comprehensive IDP is a massive undertaking. As Atlassian describes, an IDP is \"a self-service interface between developers and the underlying infrastructure, tools, and processes required to build, deploy, and manage software applications... ",{"type":21,"tag":399,"props":400,"children":401},"span",{},[402],{"type":26,"value":403},"providing",{"type":26,"value":405}," developers with a unified interface or portal where they can access everything they need to build, test, and deploy applications. This platform is typically tailored to a company's specific needs, integrating various tools, services, and workflows into a cohesive, user-friendly system.\" (Source: ",{"type":21,"tag":206,"props":407,"children":410},{"href":408,"rel":409},"https://www.atlassian.com/developer-experience/internal-developer-platform",[210],[411],{"type":26,"value":412},"Atlassian - Internal Developer Platform",{"type":26,"value":414},"). This vision of a unified, all-encompassing system often requires dedicated platform teams, significant upfront investment, and ongoing operational effort, making it a substantial commitment. This is where a more pragmatic, developer-centric approach can offer a compelling alternative.",{"type":21,"tag":190,"props":416,"children":418},{"id":417},"a-pragmatic-path-aws-cdk-for-developer-empowered-platforms",[419],{"type":26,"value":420},"A Pragmatic Path: AWS CDK for Developer-Empowered Platforms",{"type":21,"tag":22,"props":422,"children":423},{},[424,426,433],{"type":26,"value":425},"You don't always need a heavyweight IDP to achieve many of its core benefits. The philosophy behind tools like AWS CDK is to empower teams and developers by leveraging their existing skill sets. As highlighted in ",{"type":21,"tag":206,"props":427,"children":430},{"href":428,"rel":429},"https://aws.amazon.com/blogs/opensource/working-backwards-the-story-behind-the-aws-cloud-development-kit/",[210],[431],{"type":26,"value":432},"Working backwards: The story behind the AWS Cloud Development Kit",{"type":26,"value":434},":",{"type":21,"tag":306,"props":436,"children":437},{},[438],{"type":21,"tag":22,"props":439,"children":440},{},[441,446],{"type":21,"tag":399,"props":442,"children":443},{},[444],{"type":26,"value":445},"CDK",{"type":26,"value":447}," is an open source software development framework that lets developers define cloud application resources using familiar programming languages, so they do not have to learn a new domain-specific language. The AWS CDK lets developers manage those resources in the same way that they manage their application code, ensuring visibility, reproducibility, and repeatability, and it is part of how modern applications treat their infrastructure as code.",{"type":21,"tag":22,"props":449,"children":450},{},[451],{"type":26,"value":452},"This approach is key to making Infrastructure as Code truly accessible and is a game-changer for practices like creating dynamic, ephemeral deployment environments. This drastically reduces the boilerplate and complexity compared to writing raw AWS CloudFormation or other declarative IaC tools like Terraform or Kubernetes.",{"type":21,"tag":87,"props":454,"children":456},{"id":455},"heres-how-aws-cloud-development-kit-cdk-helps-achieve-this",[457],{"type":26,"value":458},"Here's how AWS Cloud Development Kit (CDK) helps achieve this:",{"type":21,"tag":60,"props":460,"children":461},{},[462,472,490,500,510,520],{"type":21,"tag":64,"props":463,"children":464},{},[465,470],{"type":21,"tag":195,"props":466,"children":467},{},[468],{"type":26,"value":469},"Familiar Languages",{"type":26,"value":471},": Developers define cloud infrastructure using languages like TypeScript, Python, Java, C#, or Go, complete with the benefits of their existing IDEs, linters, and testing frameworks.",{"type":21,"tag":64,"props":473,"children":474},{},[475,480,482,488],{"type":21,"tag":195,"props":476,"children":477},{},[478],{"type":26,"value":479},"High-Level Constructs",{"type":26,"value":481},": CDK provides well-architected building blocks (L2/L3 constructs) like ",{"type":21,"tag":43,"props":483,"children":485},{"className":484},[],[486],{"type":26,"value":487},"ApplicationLoadBalancedFargateService",{"type":26,"value":489}," which can deploy a containerized application behind a load balancer with just a few lines of code. This drastically reduces the boilerplate and complexity compared to writing raw AWS CloudFormation or Kubernetes YAML.",{"type":21,"tag":64,"props":491,"children":492},{},[493,498],{"type":21,"tag":195,"props":494,"children":495},{},[496],{"type":26,"value":497},"Reusability and Standardization",{"type":26,"value":499},": Teams can create their own custom CDK constructs that encapsulate organizational best practices for security, networking, and application deployment. For example, an \"OurStandardWebService\" construct could pre-configure logging, monitoring, and IAM permissions.",{"type":21,"tag":64,"props":501,"children":502},{},[503,508],{"type":21,"tag":195,"props":504,"children":505},{},[506],{"type":26,"value":507},"Full Programming Power",{"type":26,"value":509},": Because it's \"just code,\" you can use loops, conditionals, and software design patterns to create dynamic and sophisticated infrastructure definitions.",{"type":21,"tag":64,"props":511,"children":512},{},[513,518],{"type":21,"tag":195,"props":514,"children":515},{},[516],{"type":26,"value":517},"Synthesis to CloudFormation",{"type":26,"value":519},": Under the hood, CDK applications synthesize into AWS CloudFormation templates. This means you get the robust, declarative provisioning engine of CloudFormation, ensuring predictable and repeatable deployments, but with a far superior developer experience.",{"type":21,"tag":64,"props":521,"children":522},{},[523,528],{"type":21,"tag":195,"props":524,"children":525},{},[526],{"type":26,"value":527},"Managed Services",{"type":26,"value":529},": Committing to a certain amount of AWS platform lockin lets the team leverage all the engineering AWS puts into the platform making security, scalability and reliabilty easier to achieve at a smaller scale.",{"type":21,"tag":306,"props":531,"children":532},{},[533,546],{"type":21,"tag":87,"props":534,"children":536},{"id":535},"sidebar-industry-insights-from-thoughtworks-technology-radar-vol-32-2025",[537,539],{"type":26,"value":538},"Sidebar: Industry Insights from ",{"type":21,"tag":206,"props":540,"children":543},{"href":541,"rel":542},"https://www.thoughtworks.com/content/dam/thoughtworks/documents/radar/2025/04/tr_technology_radar_vol_32_en.pdf",[210],[544],{"type":26,"value":545},"ThoughtWorks Technology Radar Vol. 32, 2025",{"type":21,"tag":60,"props":547,"children":548},{},[549,559],{"type":21,"tag":64,"props":550,"children":551},{},[552,557],{"type":21,"tag":166,"props":553,"children":554},{},[555],{"type":26,"value":556},"Branch-per-Environment Approaches:",{"type":26,"value":558},"\n\"Branch-per-environment strategies offer teams the ability to manage complex deployments by maintaining distinct code branches for each environment, facilitating isolated testing and improved reliability.\"",{"type":21,"tag":64,"props":560,"children":561},{},[562,567],{"type":21,"tag":166,"props":563,"children":564},{},[565],{"type":26,"value":566},"Internal Developer Platforms (IDPs):",{"type":26,"value":568},"\n\"IDPs streamline developer workflows by providing centralized tooling and self-service capabilities, reducing cognitive load, and standardizing deployments.\"",{"type":21,"tag":22,"props":570,"children":571},{},[572],{"type":26,"value":573},"While this article isn't a tutorial on using AWS CDK (plenty of excellent resources exist for that, which you can explore in further reading below), the practical application of this approach transforms the development workflow. When a developer initiates a new feature by creating a Git branch, the CDK deployment (IaC) code, which lives directly alongside the application code within that same branch, is deployed. Immediately the ephemeral environment is created. This means QA, product managers, or other stakeholders can \"take a quick look\" at any point, fostering early feedback and collaboration without waiting for a developer to manually provision or deem it \"ready\" — while letting the developer validate his code in a production-like environment immediately. I advocate strongly for this approach where the environment is created and destroyed automatically. However, a more manual process where the developer decides when to create and destroy the ephemeral environment is also easy and viable with this technology.",{"type":21,"tag":87,"props":575,"children":577},{"id":576},"bundle-and-deploy-containerized-backend-quickly",[578],{"type":26,"value":579},"Bundle and Deploy Containerized Backend Quickly",{"type":21,"tag":22,"props":581,"children":582},{},[583,585,592],{"type":26,"value":584},"In CDK, a small block of python code defines, builds and deploys a containerized, load balanced web backend. This code can be in any of a ",{"type":21,"tag":206,"props":586,"children":589},{"href":587,"rel":588},"https://docs.aws.amazon.com/cdk/v2/guide/languages.html",[210],[590],{"type":26,"value":591},"variety of different languages",{"type":26,"value":593},". For our example, we'll be using Python:",{"type":21,"tag":595,"props":596,"children":599},"pre",{"className":597,"code":598,"language":16,"meta":8,"style":8},"language-python shiki shiki-themes github-light github-dark","fargate = ecs_patterns.ApplicationLoadBalancedFargateService(\n    self,\n    'apiFargate',\n    cpu=self.config.get('fargate_cpu', 256),\n    desired_count=self.config.get('fargate_desired_count', 2),\n    task_image_options=ecs_patterns.ApplicationLoadBalancedTaskImageOptions(\n        image=ecs.ContainerImage.from_asset(\n            directory=self.project_dir,                    \n        ),\n        container_port=self.config.get('fargate_container_port', 80),\n        enable_logging=self.config.get('fargate_enable_logging', True),\n        environment={\n            'FRONTEND_URL': self.url,\n            'DB_URI': f'{self.rds_url}',\n            'COMPANY_ID': '9999999999999',\n            'DB_NAME': 'tracker',\n            'QBO_CLIENT_ID': 'XXXXXXXXXXXXXXXXXXXXXXXXXX',\n            'ENVIRONMENT': 'production' if self.stage == 'production' else 'sandbox',\n            'GITLAB_URL': 'https://xxx.xxx.net',\n            'GITLAB_CLIENT_SECRET_NAME': 'gitlab-client-secret',\n            'GITLAB_AUTH_SECRET_NAME': 'gitlab-auth-secret',\n            'QBO_SECRET_NAME': 'qbo-auth-secret',\n            'KEYCLOAK_USERNAME_MIMIC': 'gitlabdev_dev',\n        },\n        secrets={\n            'DB_PASSWORD': ecs.Secret.from_secrets_manager(self.rds_secret, field='password'),\n            'DB_USERNAME': ecs.Secret.from_secrets_manager(self.rds_secret, field='username'),\n            'QBO_CLIENT_SECRET': ecs.Secret.from_secrets_manager(self.qbo_client_secret, field='client_secret'),\n        }\n    ),\n    memory_limit_mib=self.config.get('fargate_memory_limit_mib', 512),\n    public_load_balancer=self.config.get('fargate_public_load_balancer', False),\n    redirect_http=False,\n    enable_execute_command=self.config.get('fargate_enable_execute_command', False),\n    health_check_grace_period=Duration.seconds(self.config.get('fargate_health_check_grace_period', 60)),\n    max_healthy_percent=self.config.get('fargate_max_healthy_percent', 200),\n    min_healthy_percent=self.config.get('fargate_min_healthy_percent', 50),\n    vpc=self.vpc,            \n)\n",[600],{"type":21,"tag":43,"props":601,"children":602},{"__ignoreMap":8},[603,625,639,652,696,735,753,771,793,802,841,880,898,921,967,989,1011,1033,1090,1112,1134,1156,1178,1200,1209,1226,1267,1305,1344,1353,1362,1401,1440,1461,1499,1544,1583,1622,1644],{"type":21,"tag":399,"props":604,"children":607},{"class":605,"line":606},"line",1,[608,614,620],{"type":21,"tag":399,"props":609,"children":611},{"style":610},"--shiki-default:#24292E;--shiki-dark:#E1E4E8",[612],{"type":26,"value":613},"fargate ",{"type":21,"tag":399,"props":615,"children":617},{"style":616},"--shiki-default:#D73A49;--shiki-dark:#F97583",[618],{"type":26,"value":619},"=",{"type":21,"tag":399,"props":621,"children":622},{"style":610},[623],{"type":26,"value":624}," ecs_patterns.ApplicationLoadBalancedFargateService(\n",{"type":21,"tag":399,"props":626,"children":627},{"class":605,"line":239},[628,634],{"type":21,"tag":399,"props":629,"children":631},{"style":630},"--shiki-default:#005CC5;--shiki-dark:#79B8FF",[632],{"type":26,"value":633},"    self",{"type":21,"tag":399,"props":635,"children":636},{"style":610},[637],{"type":26,"value":638},",\n",{"type":21,"tag":399,"props":640,"children":641},{"class":605,"line":232},[642,648],{"type":21,"tag":399,"props":643,"children":645},{"style":644},"--shiki-default:#032F62;--shiki-dark:#9ECBFF",[646],{"type":26,"value":647},"    'apiFargate'",{"type":21,"tag":399,"props":649,"children":650},{"style":610},[651],{"type":26,"value":638},{"type":21,"tag":399,"props":653,"children":655},{"class":605,"line":654},4,[656,662,666,671,676,681,686,691],{"type":21,"tag":399,"props":657,"children":659},{"style":658},"--shiki-default:#E36209;--shiki-dark:#FFAB70",[660],{"type":26,"value":661},"    cpu",{"type":21,"tag":399,"props":663,"children":664},{"style":616},[665],{"type":26,"value":619},{"type":21,"tag":399,"props":667,"children":668},{"style":630},[669],{"type":26,"value":670},"self",{"type":21,"tag":399,"props":672,"children":673},{"style":610},[674],{"type":26,"value":675},".config.get(",{"type":21,"tag":399,"props":677,"children":678},{"style":644},[679],{"type":26,"value":680},"'fargate_cpu'",{"type":21,"tag":399,"props":682,"children":683},{"style":610},[684],{"type":26,"value":685},", ",{"type":21,"tag":399,"props":687,"children":688},{"style":630},[689],{"type":26,"value":690},"256",{"type":21,"tag":399,"props":692,"children":693},{"style":610},[694],{"type":26,"value":695},"),\n",{"type":21,"tag":399,"props":697,"children":699},{"class":605,"line":698},5,[700,705,709,713,717,722,726,731],{"type":21,"tag":399,"props":701,"children":702},{"style":658},[703],{"type":26,"value":704},"    desired_count",{"type":21,"tag":399,"props":706,"children":707},{"style":616},[708],{"type":26,"value":619},{"type":21,"tag":399,"props":710,"children":711},{"style":630},[712],{"type":26,"value":670},{"type":21,"tag":399,"props":714,"children":715},{"style":610},[716],{"type":26,"value":675},{"type":21,"tag":399,"props":718,"children":719},{"style":644},[720],{"type":26,"value":721},"'fargate_desired_count'",{"type":21,"tag":399,"props":723,"children":724},{"style":610},[725],{"type":26,"value":685},{"type":21,"tag":399,"props":727,"children":728},{"style":630},[729],{"type":26,"value":730},"2",{"type":21,"tag":399,"props":732,"children":733},{"style":610},[734],{"type":26,"value":695},{"type":21,"tag":399,"props":736,"children":738},{"class":605,"line":737},6,[739,744,748],{"type":21,"tag":399,"props":740,"children":741},{"style":658},[742],{"type":26,"value":743},"    task_image_options",{"type":21,"tag":399,"props":745,"children":746},{"style":616},[747],{"type":26,"value":619},{"type":21,"tag":399,"props":749,"children":750},{"style":610},[751],{"type":26,"value":752},"ecs_patterns.ApplicationLoadBalancedTaskImageOptions(\n",{"type":21,"tag":399,"props":754,"children":756},{"class":605,"line":755},7,[757,762,766],{"type":21,"tag":399,"props":758,"children":759},{"style":658},[760],{"type":26,"value":761},"        image",{"type":21,"tag":399,"props":763,"children":764},{"style":616},[765],{"type":26,"value":619},{"type":21,"tag":399,"props":767,"children":768},{"style":610},[769],{"type":26,"value":770},"ecs.ContainerImage.from_asset(\n",{"type":21,"tag":399,"props":772,"children":774},{"class":605,"line":773},8,[775,780,784,788],{"type":21,"tag":399,"props":776,"children":777},{"style":658},[778],{"type":26,"value":779},"            directory",{"type":21,"tag":399,"props":781,"children":782},{"style":616},[783],{"type":26,"value":619},{"type":21,"tag":399,"props":785,"children":786},{"style":630},[787],{"type":26,"value":670},{"type":21,"tag":399,"props":789,"children":790},{"style":610},[791],{"type":26,"value":792},".project_dir,                    \n",{"type":21,"tag":399,"props":794,"children":796},{"class":605,"line":795},9,[797],{"type":21,"tag":399,"props":798,"children":799},{"style":610},[800],{"type":26,"value":801},"        ),\n",{"type":21,"tag":399,"props":803,"children":805},{"class":605,"line":804},10,[806,811,815,819,823,828,832,837],{"type":21,"tag":399,"props":807,"children":808},{"style":658},[809],{"type":26,"value":810},"        container_port",{"type":21,"tag":399,"props":812,"children":813},{"style":616},[814],{"type":26,"value":619},{"type":21,"tag":399,"props":816,"children":817},{"style":630},[818],{"type":26,"value":670},{"type":21,"tag":399,"props":820,"children":821},{"style":610},[822],{"type":26,"value":675},{"type":21,"tag":399,"props":824,"children":825},{"style":644},[826],{"type":26,"value":827},"'fargate_container_port'",{"type":21,"tag":399,"props":829,"children":830},{"style":610},[831],{"type":26,"value":685},{"type":21,"tag":399,"props":833,"children":834},{"style":630},[835],{"type":26,"value":836},"80",{"type":21,"tag":399,"props":838,"children":839},{"style":610},[840],{"type":26,"value":695},{"type":21,"tag":399,"props":842,"children":844},{"class":605,"line":843},11,[845,850,854,858,862,867,871,876],{"type":21,"tag":399,"props":846,"children":847},{"style":658},[848],{"type":26,"value":849},"        enable_logging",{"type":21,"tag":399,"props":851,"children":852},{"style":616},[853],{"type":26,"value":619},{"type":21,"tag":399,"props":855,"children":856},{"style":630},[857],{"type":26,"value":670},{"type":21,"tag":399,"props":859,"children":860},{"style":610},[861],{"type":26,"value":675},{"type":21,"tag":399,"props":863,"children":864},{"style":644},[865],{"type":26,"value":866},"'fargate_enable_logging'",{"type":21,"tag":399,"props":868,"children":869},{"style":610},[870],{"type":26,"value":685},{"type":21,"tag":399,"props":872,"children":873},{"style":630},[874],{"type":26,"value":875},"True",{"type":21,"tag":399,"props":877,"children":878},{"style":610},[879],{"type":26,"value":695},{"type":21,"tag":399,"props":881,"children":883},{"class":605,"line":882},12,[884,889,893],{"type":21,"tag":399,"props":885,"children":886},{"style":658},[887],{"type":26,"value":888},"        environment",{"type":21,"tag":399,"props":890,"children":891},{"style":616},[892],{"type":26,"value":619},{"type":21,"tag":399,"props":894,"children":895},{"style":610},[896],{"type":26,"value":897},"{\n",{"type":21,"tag":399,"props":899,"children":901},{"class":605,"line":900},13,[902,907,912,916],{"type":21,"tag":399,"props":903,"children":904},{"style":644},[905],{"type":26,"value":906},"            'FRONTEND_URL'",{"type":21,"tag":399,"props":908,"children":909},{"style":610},[910],{"type":26,"value":911},": ",{"type":21,"tag":399,"props":913,"children":914},{"style":630},[915],{"type":26,"value":670},{"type":21,"tag":399,"props":917,"children":918},{"style":610},[919],{"type":26,"value":920},".url,\n",{"type":21,"tag":399,"props":922,"children":924},{"class":605,"line":923},14,[925,930,934,939,944,949,954,959,963],{"type":21,"tag":399,"props":926,"children":927},{"style":644},[928],{"type":26,"value":929},"            'DB_URI'",{"type":21,"tag":399,"props":931,"children":932},{"style":610},[933],{"type":26,"value":911},{"type":21,"tag":399,"props":935,"children":936},{"style":616},[937],{"type":26,"value":938},"f",{"type":21,"tag":399,"props":940,"children":941},{"style":644},[942],{"type":26,"value":943},"'",{"type":21,"tag":399,"props":945,"children":946},{"style":630},[947],{"type":26,"value":948},"{self",{"type":21,"tag":399,"props":950,"children":951},{"style":610},[952],{"type":26,"value":953},".rds_url",{"type":21,"tag":399,"props":955,"children":956},{"style":630},[957],{"type":26,"value":958},"}",{"type":21,"tag":399,"props":960,"children":961},{"style":644},[962],{"type":26,"value":943},{"type":21,"tag":399,"props":964,"children":965},{"style":610},[966],{"type":26,"value":638},{"type":21,"tag":399,"props":968,"children":970},{"class":605,"line":969},15,[971,976,980,985],{"type":21,"tag":399,"props":972,"children":973},{"style":644},[974],{"type":26,"value":975},"            'COMPANY_ID'",{"type":21,"tag":399,"props":977,"children":978},{"style":610},[979],{"type":26,"value":911},{"type":21,"tag":399,"props":981,"children":982},{"style":644},[983],{"type":26,"value":984},"'9999999999999'",{"type":21,"tag":399,"props":986,"children":987},{"style":610},[988],{"type":26,"value":638},{"type":21,"tag":399,"props":990,"children":992},{"class":605,"line":991},16,[993,998,1002,1007],{"type":21,"tag":399,"props":994,"children":995},{"style":644},[996],{"type":26,"value":997},"            'DB_NAME'",{"type":21,"tag":399,"props":999,"children":1000},{"style":610},[1001],{"type":26,"value":911},{"type":21,"tag":399,"props":1003,"children":1004},{"style":644},[1005],{"type":26,"value":1006},"'tracker'",{"type":21,"tag":399,"props":1008,"children":1009},{"style":610},[1010],{"type":26,"value":638},{"type":21,"tag":399,"props":1012,"children":1014},{"class":605,"line":1013},17,[1015,1020,1024,1029],{"type":21,"tag":399,"props":1016,"children":1017},{"style":644},[1018],{"type":26,"value":1019},"            'QBO_CLIENT_ID'",{"type":21,"tag":399,"props":1021,"children":1022},{"style":610},[1023],{"type":26,"value":911},{"type":21,"tag":399,"props":1025,"children":1026},{"style":644},[1027],{"type":26,"value":1028},"'XXXXXXXXXXXXXXXXXXXXXXXXXX'",{"type":21,"tag":399,"props":1030,"children":1031},{"style":610},[1032],{"type":26,"value":638},{"type":21,"tag":399,"props":1034,"children":1036},{"class":605,"line":1035},18,[1037,1042,1046,1051,1056,1061,1066,1071,1076,1081,1086],{"type":21,"tag":399,"props":1038,"children":1039},{"style":644},[1040],{"type":26,"value":1041},"            'ENVIRONMENT'",{"type":21,"tag":399,"props":1043,"children":1044},{"style":610},[1045],{"type":26,"value":911},{"type":21,"tag":399,"props":1047,"children":1048},{"style":644},[1049],{"type":26,"value":1050},"'production'",{"type":21,"tag":399,"props":1052,"children":1053},{"style":616},[1054],{"type":26,"value":1055}," if",{"type":21,"tag":399,"props":1057,"children":1058},{"style":630},[1059],{"type":26,"value":1060}," self",{"type":21,"tag":399,"props":1062,"children":1063},{"style":610},[1064],{"type":26,"value":1065},".stage ",{"type":21,"tag":399,"props":1067,"children":1068},{"style":616},[1069],{"type":26,"value":1070},"==",{"type":21,"tag":399,"props":1072,"children":1073},{"style":644},[1074],{"type":26,"value":1075}," 'production'",{"type":21,"tag":399,"props":1077,"children":1078},{"style":616},[1079],{"type":26,"value":1080}," else",{"type":21,"tag":399,"props":1082,"children":1083},{"style":644},[1084],{"type":26,"value":1085}," 'sandbox'",{"type":21,"tag":399,"props":1087,"children":1088},{"style":610},[1089],{"type":26,"value":638},{"type":21,"tag":399,"props":1091,"children":1093},{"class":605,"line":1092},19,[1094,1099,1103,1108],{"type":21,"tag":399,"props":1095,"children":1096},{"style":644},[1097],{"type":26,"value":1098},"            'GITLAB_URL'",{"type":21,"tag":399,"props":1100,"children":1101},{"style":610},[1102],{"type":26,"value":911},{"type":21,"tag":399,"props":1104,"children":1105},{"style":644},[1106],{"type":26,"value":1107},"'https://xxx.xxx.net'",{"type":21,"tag":399,"props":1109,"children":1110},{"style":610},[1111],{"type":26,"value":638},{"type":21,"tag":399,"props":1113,"children":1115},{"class":605,"line":1114},20,[1116,1121,1125,1130],{"type":21,"tag":399,"props":1117,"children":1118},{"style":644},[1119],{"type":26,"value":1120},"            'GITLAB_CLIENT_SECRET_NAME'",{"type":21,"tag":399,"props":1122,"children":1123},{"style":610},[1124],{"type":26,"value":911},{"type":21,"tag":399,"props":1126,"children":1127},{"style":644},[1128],{"type":26,"value":1129},"'gitlab-client-secret'",{"type":21,"tag":399,"props":1131,"children":1132},{"style":610},[1133],{"type":26,"value":638},{"type":21,"tag":399,"props":1135,"children":1137},{"class":605,"line":1136},21,[1138,1143,1147,1152],{"type":21,"tag":399,"props":1139,"children":1140},{"style":644},[1141],{"type":26,"value":1142},"            'GITLAB_AUTH_SECRET_NAME'",{"type":21,"tag":399,"props":1144,"children":1145},{"style":610},[1146],{"type":26,"value":911},{"type":21,"tag":399,"props":1148,"children":1149},{"style":644},[1150],{"type":26,"value":1151},"'gitlab-auth-secret'",{"type":21,"tag":399,"props":1153,"children":1154},{"style":610},[1155],{"type":26,"value":638},{"type":21,"tag":399,"props":1157,"children":1159},{"class":605,"line":1158},22,[1160,1165,1169,1174],{"type":21,"tag":399,"props":1161,"children":1162},{"style":644},[1163],{"type":26,"value":1164},"            'QBO_SECRET_NAME'",{"type":21,"tag":399,"props":1166,"children":1167},{"style":610},[1168],{"type":26,"value":911},{"type":21,"tag":399,"props":1170,"children":1171},{"style":644},[1172],{"type":26,"value":1173},"'qbo-auth-secret'",{"type":21,"tag":399,"props":1175,"children":1176},{"style":610},[1177],{"type":26,"value":638},{"type":21,"tag":399,"props":1179,"children":1181},{"class":605,"line":1180},23,[1182,1187,1191,1196],{"type":21,"tag":399,"props":1183,"children":1184},{"style":644},[1185],{"type":26,"value":1186},"            'KEYCLOAK_USERNAME_MIMIC'",{"type":21,"tag":399,"props":1188,"children":1189},{"style":610},[1190],{"type":26,"value":911},{"type":21,"tag":399,"props":1192,"children":1193},{"style":644},[1194],{"type":26,"value":1195},"'gitlabdev_dev'",{"type":21,"tag":399,"props":1197,"children":1198},{"style":610},[1199],{"type":26,"value":638},{"type":21,"tag":399,"props":1201,"children":1203},{"class":605,"line":1202},24,[1204],{"type":21,"tag":399,"props":1205,"children":1206},{"style":610},[1207],{"type":26,"value":1208},"        },\n",{"type":21,"tag":399,"props":1210,"children":1212},{"class":605,"line":1211},25,[1213,1218,1222],{"type":21,"tag":399,"props":1214,"children":1215},{"style":658},[1216],{"type":26,"value":1217},"        secrets",{"type":21,"tag":399,"props":1219,"children":1220},{"style":616},[1221],{"type":26,"value":619},{"type":21,"tag":399,"props":1223,"children":1224},{"style":610},[1225],{"type":26,"value":897},{"type":21,"tag":399,"props":1227,"children":1229},{"class":605,"line":1228},26,[1230,1235,1240,1244,1249,1254,1258,1263],{"type":21,"tag":399,"props":1231,"children":1232},{"style":644},[1233],{"type":26,"value":1234},"            'DB_PASSWORD'",{"type":21,"tag":399,"props":1236,"children":1237},{"style":610},[1238],{"type":26,"value":1239},": ecs.Secret.from_secrets_manager(",{"type":21,"tag":399,"props":1241,"children":1242},{"style":630},[1243],{"type":26,"value":670},{"type":21,"tag":399,"props":1245,"children":1246},{"style":610},[1247],{"type":26,"value":1248},".rds_secret, ",{"type":21,"tag":399,"props":1250,"children":1251},{"style":658},[1252],{"type":26,"value":1253},"field",{"type":21,"tag":399,"props":1255,"children":1256},{"style":616},[1257],{"type":26,"value":619},{"type":21,"tag":399,"props":1259,"children":1260},{"style":644},[1261],{"type":26,"value":1262},"'password'",{"type":21,"tag":399,"props":1264,"children":1265},{"style":610},[1266],{"type":26,"value":695},{"type":21,"tag":399,"props":1268,"children":1270},{"class":605,"line":1269},27,[1271,1276,1280,1284,1288,1292,1296,1301],{"type":21,"tag":399,"props":1272,"children":1273},{"style":644},[1274],{"type":26,"value":1275},"            'DB_USERNAME'",{"type":21,"tag":399,"props":1277,"children":1278},{"style":610},[1279],{"type":26,"value":1239},{"type":21,"tag":399,"props":1281,"children":1282},{"style":630},[1283],{"type":26,"value":670},{"type":21,"tag":399,"props":1285,"children":1286},{"style":610},[1287],{"type":26,"value":1248},{"type":21,"tag":399,"props":1289,"children":1290},{"style":658},[1291],{"type":26,"value":1253},{"type":21,"tag":399,"props":1293,"children":1294},{"style":616},[1295],{"type":26,"value":619},{"type":21,"tag":399,"props":1297,"children":1298},{"style":644},[1299],{"type":26,"value":1300},"'username'",{"type":21,"tag":399,"props":1302,"children":1303},{"style":610},[1304],{"type":26,"value":695},{"type":21,"tag":399,"props":1306,"children":1308},{"class":605,"line":1307},28,[1309,1314,1318,1322,1327,1331,1335,1340],{"type":21,"tag":399,"props":1310,"children":1311},{"style":644},[1312],{"type":26,"value":1313},"            'QBO_CLIENT_SECRET'",{"type":21,"tag":399,"props":1315,"children":1316},{"style":610},[1317],{"type":26,"value":1239},{"type":21,"tag":399,"props":1319,"children":1320},{"style":630},[1321],{"type":26,"value":670},{"type":21,"tag":399,"props":1323,"children":1324},{"style":610},[1325],{"type":26,"value":1326},".qbo_client_secret, ",{"type":21,"tag":399,"props":1328,"children":1329},{"style":658},[1330],{"type":26,"value":1253},{"type":21,"tag":399,"props":1332,"children":1333},{"style":616},[1334],{"type":26,"value":619},{"type":21,"tag":399,"props":1336,"children":1337},{"style":644},[1338],{"type":26,"value":1339},"'client_secret'",{"type":21,"tag":399,"props":1341,"children":1342},{"style":610},[1343],{"type":26,"value":695},{"type":21,"tag":399,"props":1345,"children":1347},{"class":605,"line":1346},29,[1348],{"type":21,"tag":399,"props":1349,"children":1350},{"style":610},[1351],{"type":26,"value":1352},"        }\n",{"type":21,"tag":399,"props":1354,"children":1356},{"class":605,"line":1355},30,[1357],{"type":21,"tag":399,"props":1358,"children":1359},{"style":610},[1360],{"type":26,"value":1361},"    ),\n",{"type":21,"tag":399,"props":1363,"children":1365},{"class":605,"line":1364},31,[1366,1371,1375,1379,1383,1388,1392,1397],{"type":21,"tag":399,"props":1367,"children":1368},{"style":658},[1369],{"type":26,"value":1370},"    memory_limit_mib",{"type":21,"tag":399,"props":1372,"children":1373},{"style":616},[1374],{"type":26,"value":619},{"type":21,"tag":399,"props":1376,"children":1377},{"style":630},[1378],{"type":26,"value":670},{"type":21,"tag":399,"props":1380,"children":1381},{"style":610},[1382],{"type":26,"value":675},{"type":21,"tag":399,"props":1384,"children":1385},{"style":644},[1386],{"type":26,"value":1387},"'fargate_memory_limit_mib'",{"type":21,"tag":399,"props":1389,"children":1390},{"style":610},[1391],{"type":26,"value":685},{"type":21,"tag":399,"props":1393,"children":1394},{"style":630},[1395],{"type":26,"value":1396},"512",{"type":21,"tag":399,"props":1398,"children":1399},{"style":610},[1400],{"type":26,"value":695},{"type":21,"tag":399,"props":1402,"children":1404},{"class":605,"line":1403},32,[1405,1410,1414,1418,1422,1427,1431,1436],{"type":21,"tag":399,"props":1406,"children":1407},{"style":658},[1408],{"type":26,"value":1409},"    public_load_balancer",{"type":21,"tag":399,"props":1411,"children":1412},{"style":616},[1413],{"type":26,"value":619},{"type":21,"tag":399,"props":1415,"children":1416},{"style":630},[1417],{"type":26,"value":670},{"type":21,"tag":399,"props":1419,"children":1420},{"style":610},[1421],{"type":26,"value":675},{"type":21,"tag":399,"props":1423,"children":1424},{"style":644},[1425],{"type":26,"value":1426},"'fargate_public_load_balancer'",{"type":21,"tag":399,"props":1428,"children":1429},{"style":610},[1430],{"type":26,"value":685},{"type":21,"tag":399,"props":1432,"children":1433},{"style":630},[1434],{"type":26,"value":1435},"False",{"type":21,"tag":399,"props":1437,"children":1438},{"style":610},[1439],{"type":26,"value":695},{"type":21,"tag":399,"props":1441,"children":1443},{"class":605,"line":1442},33,[1444,1449,1453,1457],{"type":21,"tag":399,"props":1445,"children":1446},{"style":658},[1447],{"type":26,"value":1448},"    redirect_http",{"type":21,"tag":399,"props":1450,"children":1451},{"style":616},[1452],{"type":26,"value":619},{"type":21,"tag":399,"props":1454,"children":1455},{"style":630},[1456],{"type":26,"value":1435},{"type":21,"tag":399,"props":1458,"children":1459},{"style":610},[1460],{"type":26,"value":638},{"type":21,"tag":399,"props":1462,"children":1464},{"class":605,"line":1463},34,[1465,1470,1474,1478,1482,1487,1491,1495],{"type":21,"tag":399,"props":1466,"children":1467},{"style":658},[1468],{"type":26,"value":1469},"    enable_execute_command",{"type":21,"tag":399,"props":1471,"children":1472},{"style":616},[1473],{"type":26,"value":619},{"type":21,"tag":399,"props":1475,"children":1476},{"style":630},[1477],{"type":26,"value":670},{"type":21,"tag":399,"props":1479,"children":1480},{"style":610},[1481],{"type":26,"value":675},{"type":21,"tag":399,"props":1483,"children":1484},{"style":644},[1485],{"type":26,"value":1486},"'fargate_enable_execute_command'",{"type":21,"tag":399,"props":1488,"children":1489},{"style":610},[1490],{"type":26,"value":685},{"type":21,"tag":399,"props":1492,"children":1493},{"style":630},[1494],{"type":26,"value":1435},{"type":21,"tag":399,"props":1496,"children":1497},{"style":610},[1498],{"type":26,"value":695},{"type":21,"tag":399,"props":1500,"children":1502},{"class":605,"line":1501},35,[1503,1508,1512,1517,1521,1525,1530,1534,1539],{"type":21,"tag":399,"props":1504,"children":1505},{"style":658},[1506],{"type":26,"value":1507},"    health_check_grace_period",{"type":21,"tag":399,"props":1509,"children":1510},{"style":616},[1511],{"type":26,"value":619},{"type":21,"tag":399,"props":1513,"children":1514},{"style":610},[1515],{"type":26,"value":1516},"Duration.seconds(",{"type":21,"tag":399,"props":1518,"children":1519},{"style":630},[1520],{"type":26,"value":670},{"type":21,"tag":399,"props":1522,"children":1523},{"style":610},[1524],{"type":26,"value":675},{"type":21,"tag":399,"props":1526,"children":1527},{"style":644},[1528],{"type":26,"value":1529},"'fargate_health_check_grace_period'",{"type":21,"tag":399,"props":1531,"children":1532},{"style":610},[1533],{"type":26,"value":685},{"type":21,"tag":399,"props":1535,"children":1536},{"style":630},[1537],{"type":26,"value":1538},"60",{"type":21,"tag":399,"props":1540,"children":1541},{"style":610},[1542],{"type":26,"value":1543},")),\n",{"type":21,"tag":399,"props":1545,"children":1547},{"class":605,"line":1546},36,[1548,1553,1557,1561,1565,1570,1574,1579],{"type":21,"tag":399,"props":1549,"children":1550},{"style":658},[1551],{"type":26,"value":1552},"    max_healthy_percent",{"type":21,"tag":399,"props":1554,"children":1555},{"style":616},[1556],{"type":26,"value":619},{"type":21,"tag":399,"props":1558,"children":1559},{"style":630},[1560],{"type":26,"value":670},{"type":21,"tag":399,"props":1562,"children":1563},{"style":610},[1564],{"type":26,"value":675},{"type":21,"tag":399,"props":1566,"children":1567},{"style":644},[1568],{"type":26,"value":1569},"'fargate_max_healthy_percent'",{"type":21,"tag":399,"props":1571,"children":1572},{"style":610},[1573],{"type":26,"value":685},{"type":21,"tag":399,"props":1575,"children":1576},{"style":630},[1577],{"type":26,"value":1578},"200",{"type":21,"tag":399,"props":1580,"children":1581},{"style":610},[1582],{"type":26,"value":695},{"type":21,"tag":399,"props":1584,"children":1586},{"class":605,"line":1585},37,[1587,1592,1596,1600,1604,1609,1613,1618],{"type":21,"tag":399,"props":1588,"children":1589},{"style":658},[1590],{"type":26,"value":1591},"    min_healthy_percent",{"type":21,"tag":399,"props":1593,"children":1594},{"style":616},[1595],{"type":26,"value":619},{"type":21,"tag":399,"props":1597,"children":1598},{"style":630},[1599],{"type":26,"value":670},{"type":21,"tag":399,"props":1601,"children":1602},{"style":610},[1603],{"type":26,"value":675},{"type":21,"tag":399,"props":1605,"children":1606},{"style":644},[1607],{"type":26,"value":1608},"'fargate_min_healthy_percent'",{"type":21,"tag":399,"props":1610,"children":1611},{"style":610},[1612],{"type":26,"value":685},{"type":21,"tag":399,"props":1614,"children":1615},{"style":630},[1616],{"type":26,"value":1617},"50",{"type":21,"tag":399,"props":1619,"children":1620},{"style":610},[1621],{"type":26,"value":695},{"type":21,"tag":399,"props":1623,"children":1625},{"class":605,"line":1624},38,[1626,1631,1635,1639],{"type":21,"tag":399,"props":1627,"children":1628},{"style":658},[1629],{"type":26,"value":1630},"    vpc",{"type":21,"tag":399,"props":1632,"children":1633},{"style":616},[1634],{"type":26,"value":619},{"type":21,"tag":399,"props":1636,"children":1637},{"style":630},[1638],{"type":26,"value":670},{"type":21,"tag":399,"props":1640,"children":1641},{"style":610},[1642],{"type":26,"value":1643},".vpc,            \n",{"type":21,"tag":399,"props":1645,"children":1647},{"class":605,"line":1646},39,[1648],{"type":21,"tag":399,"props":1649,"children":1650},{"style":610},[1651],{"type":26,"value":1652},")\n",{"type":21,"tag":22,"props":1654,"children":1655},{},[1656],{"type":26,"value":1657},"A significant advantage emerges when infrastructure needs to evolve for a new feature: Any necessary changes to the IaC — perhaps a new database, a different type of compute resource, or adjusted network configurations — are made right there in the feature branch, directly coupled with the application code changes they support. This keeps infrastructure modifications perfectly synchronized and isolated until they are ready for review by DevOps, IT, and SecOps/DevSecOps stakeholders. Once approved, these integrated changes are pushed upstream. This principle of colocation extends to security and configuration management; by leveraging AWS IAM for precise permissions and defining infrastrucure configurations as code within the same repository (Morris 2021), the entire environment setup becomes version-controlled, transparent, and consistently reproducible. Furthermore, for robust separation, production deployments are typically targeted to an entirely separate AWS account, providing strong isolation from development and staging activities—a best practice easily facilitated by this CDK methodology.",{"type":21,"tag":22,"props":1659,"children":1660},{},[1661],{"type":26,"value":1662},"Committing to a vendor integrated technology stack like AWS and CDK allows teams to achieve a rich, deeply integrated deployment system with notably lower initial setup, ongoing maintenance, and operational costs. The inherent tradeoff is, of course, a degree of vendor lockin. However, for many organizations, particularly those operating at a scale where the agility, speed, and managed service benefits outweigh concerns about portability, this is often a pragmatic and highly effective strategic decision. Ultimately, this tight coupling of application code, IaC, configuration, and an automated environment lifecycle, all managed within the developer's familiar toolkit, is what empowers teams to build, test, and release software with greater confidence and velocity. This practice, where Git serves as the single source of truth for both application and infrastructure, and automated processes manage deployments, is a core tenet of GitOps, a key modern continuous delivery pattern.",{"type":21,"tag":306,"props":1664,"children":1665},{},[1666,1672,1677],{"type":21,"tag":87,"props":1667,"children":1669},{"id":1668},"sidebar-does-an-onprem-deployment-mean-this-wont-work-for-me",[1670],{"type":26,"value":1671},"Sidebar: Does an Onprem Deployment Mean This Won't Work for Me?",{"type":21,"tag":22,"props":1673,"children":1674},{},[1675],{"type":26,"value":1676},"This pipeline — built using all the practices discussed above — is for a project where production deployments are to a secure network environment with no connection to AWS and a requirement for manual production deployments. Despite that, the pipeline completes a complex build and test process validating the build for the unique operational and security needs of the project. Thus providing the team with reliable, on demand, ephemeral test environments and ensures that production releases are reliable and verified.",{"type":21,"tag":22,"props":1678,"children":1679},{},[1680],{"type":21,"tag":1681,"props":1682,"children":1686},"img",{"alt":1683,"src":1684,"title":1685},"Pipeline Diagram","/ewahl/2025-05/img/AwsPipeline.png","AWS Pipeline",[],{"type":21,"tag":190,"props":1688,"children":1690},{"id":1689},"addressing-common-ephemeral-environment-idp-challenges-with-a-cdk-based-approach",[1691],{"type":26,"value":1692},"Addressing Common Ephemeral Environment & IDP Challenges with a CDK-Based Approach",{"type":21,"tag":22,"props":1694,"children":1695},{},[1696,1698,1705],{"type":26,"value":1697},"While full-fledged IDPs aim to solve many operational complexities, the path to their implementation, or even to robust ephemeral environment strategies, presents significant hurdles to organizations. For instance, Qovery's article, '",{"type":21,"tag":206,"props":1699,"children":1702},{"href":1700,"rel":1701},"https://www.qovery.com/blog/challenges-to-anticipate-when-transitioning-to-an-internal-developer-platform/",[210],[1703],{"type":26,"value":1704},"Challenges to Anticipate When Transitioning to an Internal Developer Platform",{"type":26,"value":1706},"', outlines several such difficulties. A well-implemented CDK strategy for ephemeral environments, however, can addresses many of these challenges at a fraction of the cost and effort.",{"type":21,"tag":87,"props":1708,"children":1710},{"id":1709},"_1-challenge-environment-variables-and-configuration-management",[1711],{"type":26,"value":1712},"1.  Challenge: Environment Variables and Configuration Management",{"type":21,"tag":60,"props":1714,"children":1715},{},[1716,1726,1744],{"type":21,"tag":64,"props":1717,"children":1718},{},[1719,1724],{"type":21,"tag":166,"props":1720,"children":1721},{},[1722],{"type":26,"value":1723},"The Hurdle",{"type":26,"value":1725},": Managing distinct configurations, secrets, and dynamic URLs across numerous ephemeral environments can be complex.",{"type":21,"tag":64,"props":1727,"children":1728},{},[1729,1734,1736,1742],{"type":21,"tag":166,"props":1730,"children":1731},{},[1732],{"type":26,"value":1733},"CDK Mitigation",{"type":26,"value":1735},": AWS CDK can seamlessly integrate with services like AWS Systems Manager Parameter Store or AWS Secrets Manager. For each ephemeral environment, CDK code can create and populate uniquely named parameters/secrets (e.g., ",{"type":21,"tag":43,"props":1737,"children":1739},{"className":1738},[],[1740],{"type":26,"value":1741},"DATABASE_URL_feature_xyz",{"type":26,"value":1743},"). The application code within that environment is then configured to read these specific parameters. Dynamic values, like the DNS name of a load balancer created by CDK for that environment, can be directly injected as environment variables into the application's container definition or written to Parameter Store for the application to fetch.",{"type":21,"tag":64,"props":1745,"children":1746},{},[1747,1752],{"type":21,"tag":166,"props":1748,"children":1749},{},[1750],{"type":26,"value":1751},"Low Cost",{"type":26,"value":1753},": This leverages robust, built-in AWS services, managed as code within the CDK app, requiring minimal additional tooling.",{"type":21,"tag":87,"props":1755,"children":1757},{"id":1756},"_2-challenge-technical-implementation-challenges",[1758],{"type":26,"value":1759},"2.  Challenge: Technical Implementation Challenges",{"type":21,"tag":60,"props":1761,"children":1762},{},[1763,1772,1781],{"type":21,"tag":64,"props":1764,"children":1765},{},[1766,1770],{"type":21,"tag":166,"props":1767,"children":1768},{},[1769],{"type":26,"value":1723},{"type":26,"value":1771},": Setting up the initial automation for ephemeral environments can require significant effort and expertise in containerization and infrastructure tools.",{"type":21,"tag":64,"props":1773,"children":1774},{},[1775,1779],{"type":21,"tag":166,"props":1776,"children":1777},{},[1778],{"type":26,"value":1733},{"type":26,"value":1780},": CDK and its close coupling with AWS managed services significantly lowers the barrier. Instead of wrestling with verbose YAML or JSON, developers use familiar programming languages and high-level abstractions. While cloud and IaC knowledge is still needed, for a Python or TypeScript developer, learning to use CDK with the support of a DevOps team and leveraging AWS managed services often presents a gentler learning curve and operational model than becoming a Kubernetes or raw CloudFormation expert. Reusable constructs can encapsulate complex patterns, further simplifying adoption.",{"type":21,"tag":64,"props":1782,"children":1783},{},[1784,1788],{"type":21,"tag":166,"props":1785,"children":1786},{},[1787],{"type":26,"value":1751},{"type":26,"value":1789},": Capitalizes on existing developer skill sets. The need for a large, specialized platform team is reduced, especially for initial setup, as developers can own more of their infrastructure story.",{"type":21,"tag":87,"props":1791,"children":1793},{"id":1792},"_3-challenge-security-and-compliance-challenges",[1794],{"type":26,"value":1795},"3.  Challenge: Security and Compliance Challenges",{"type":21,"tag":60,"props":1797,"children":1798},{},[1799,1808,1817],{"type":21,"tag":64,"props":1800,"children":1801},{},[1802,1806],{"type":21,"tag":166,"props":1803,"children":1804},{},[1805],{"type":26,"value":1723},{"type":26,"value":1807},": Ensuring data protection, implementing role-based access control (RBAC), meeting compliance standards (GDPR, HIPAA, PCI DSS), and maintaining audit trails in transient environments.",{"type":21,"tag":64,"props":1809,"children":1810},{},[1811,1815],{"type":21,"tag":166,"props":1812,"children":1813},{},[1814],{"type":26,"value":1733},{"type":26,"value":1816},": Security is defined in code aka \"Policy as Code\". IAM roles and policies granting least-privilege access can be crafted with precision for each ephemeral stack. Security groups, network ACLs, VPC configurations, and data encryption settings (e.g., KMS keys for S3 buckets or RDS instances) are all explicitly declared in CDK are created and destroyed as needed alongside that environment. When needed, DevOps experts can step in alongside the dev team to implement appropriate security and permissions strategies, with SecOps stakeholders reviewing the results, all seamlessly before the changes get pushed upstream. This makes meeting compliance goals significantly easeir. All changes are version-controlled, and AWS CloudTrail provides an audit log of all API calls made by CDK (via CloudFormation).",{"type":21,"tag":64,"props":1818,"children":1819},{},[1820,1824],{"type":21,"tag":166,"props":1821,"children":1822},{},[1823],{"type":26,"value":1751},{"type":26,"value":1825},": Leverages AWS's robust security infrastructure and compliance certifications. \"Security as code\" makes policies transparent, reviewable, and repeatable. This 'security as code' approach embodies the shift left security principle, integrating security considerations early in the development lifecycle.",{"type":21,"tag":87,"props":1827,"children":1829},{"id":1828},"_4-challenge-resource-management",[1830],{"type":26,"value":1831},"4.  Challenge: Resource Management",{"type":21,"tag":60,"props":1833,"children":1834},{},[1835,1844,1869],{"type":21,"tag":64,"props":1836,"children":1837},{},[1838,1842],{"type":21,"tag":166,"props":1839,"children":1840},{},[1841],{"type":26,"value":1723},{"type":26,"value":1843},": Potential for resource wastage due to orphaned resources if environments aren't torn down properly, and the need to balance resource allocation.",{"type":21,"tag":64,"props":1845,"children":1846},{},[1847,1851,1853,1859,1861,1867],{"type":21,"tag":166,"props":1848,"children":1849},{},[1850],{"type":26,"value":1733},{"type":26,"value":1852},": The ",{"type":21,"tag":43,"props":1854,"children":1856},{"className":1855},[],[1857],{"type":26,"value":1858},"cdk destroy",{"type":26,"value":1860}," command is the counterpart to ",{"type":21,"tag":43,"props":1862,"children":1864},{"className":1863},[],[1865],{"type":26,"value":1866},"cdk deploy",{"type":26,"value":1868},". By integrating this command into CI/CD pipelines (e.g., on branch merge/delete), automated cleanup is enforced. CDK applications should also systematically apply tags (e.g., environment:feature-xyz, created-by:pipeline) to all resources, facilitating cost tracking and identification of any stray resources. Event Bridge and Dead Letter Queues ensure that DevOps will be notified of automation failures.",{"type":21,"tag":64,"props":1870,"children":1871},{},[1872,1876],{"type":21,"tag":166,"props":1873,"children":1874},{},[1875],{"type":26,"value":1751},{"type":26,"value":1877},": Automation is the key. The risk of manual error leading to forgotten resources is greatly diminished.",{"type":21,"tag":87,"props":1879,"children":1881},{"id":1880},"_5-challenge-team-adoption",[1882],{"type":26,"value":1883},"5.  Challenge: Team Adoption",{"type":21,"tag":60,"props":1885,"children":1886},{},[1887,1896,1905],{"type":21,"tag":64,"props":1888,"children":1889},{},[1890,1894],{"type":21,"tag":166,"props":1891,"children":1892},{},[1893],{"type":26,"value":1723},{"type":26,"value":1895},": Training teams on new ephemeral workflows and fostering a cultural shift from persistent to disposable environments.",{"type":21,"tag":64,"props":1897,"children":1898},{},[1899,1903],{"type":21,"tag":166,"props":1900,"children":1901},{},[1902],{"type":26,"value":1733},{"type":26,"value":1904},": Because CDK uses familiar programming languages, developers are often more comfortable and quicker to adopt it. They can understand, and even contribute to, the infrastructure code more readily than if it were in a DSL or complex configuration format. The \"it just works\" experience for spinning up an environment for their branch significantly smooths adoption. With DevOps experts, who are specialists in this strategy, setting up the initial project with a ready-to-use pipeline and supporting any complex infrastructure changes, the learning curve for the development team is minimal.",{"type":21,"tag":64,"props":1906,"children":1907},{},[1908,1912],{"type":21,"tag":166,"props":1909,"children":1910},{},[1911],{"type":26,"value":1751},{"type":26,"value":1913},": Reduced learning curve for developers who already code. The infrastructure becomes less of a \"black box.\"",{"type":21,"tag":87,"props":1915,"children":1917},{"id":1916},"_6-challenge-integration-complexity",[1918],{"type":26,"value":1919},"6.  Challenge: Integration Complexity",{"type":21,"tag":60,"props":1921,"children":1922},{},[1923,1932,1941],{"type":21,"tag":64,"props":1924,"children":1925},{},[1926,1930],{"type":21,"tag":166,"props":1927,"children":1928},{},[1929],{"type":26,"value":1723},{"type":26,"value":1931},": Integrating ephemeral environment provisioning into existing Continuous Integration/Continuous Delivery (CI/CD) pipelines and ensuring compatibility with development tools.",{"type":21,"tag":64,"props":1933,"children":1934},{},[1935,1939],{"type":21,"tag":166,"props":1936,"children":1937},{},[1938],{"type":26,"value":1733},{"type":26,"value":1940},": While AWS CDK integrates seamlessly with AWS native CI/CD services like CodePipeline, its command-line interface (CLI) is also designed for broader automation. This means it can be readily incorporated into established CI/CD systems your team might already use, such as GitHub Actions, GitLab CI, or Jenkins, typically by invoking shell commands. Stack outputs can then be consumed by subsequent pipeline stages, ensuring a smooth workflow regardless of your chosen CI/CD tooling.",{"type":21,"tag":64,"props":1942,"children":1943},{},[1944,1948],{"type":21,"tag":166,"props":1945,"children":1946},{},[1947],{"type":26,"value":1751},{"type":26,"value":1949},": Strong native integration with the AWS ecosystem and straightforward CLI-based integration for other systems, offering flexibility.",{"type":21,"tag":87,"props":1951,"children":1953},{"id":1952},"_7-challenge-build-vs-buy-decision-point",[1954],{"type":26,"value":1955},"7.  Challenge: Build vs. Buy Decision Point",{"type":21,"tag":60,"props":1957,"children":1958},{},[1959,1968,1977],{"type":21,"tag":64,"props":1960,"children":1961},{},[1962,1966],{"type":21,"tag":166,"props":1963,"children":1964},{},[1965],{"type":26,"value":1723},{"type":26,"value":1967},": Deciding between building a custom ephemeral environment solution (resource-intensive) or using third-party services (potential external dependencies and less customization).",{"type":21,"tag":64,"props":1969,"children":1970},{},[1971,1975],{"type":21,"tag":166,"props":1972,"children":1973},{},[1974],{"type":26,"value":1733},{"type":26,"value":1976},": CDK offers a compelling middle ground. By committing to AWS, you are essentially \"building\" your platform definition with the powerful, managed services and abstractions provided by AWS and the CDK framework. This allows you to leverage AWS's vast engineering investment directly, meaning there's significantly less undifferentiated heavy lifting for your team to fund and build from scratch when composing and configuring high-level AWS services.",{"type":21,"tag":64,"props":1978,"children":1979},{},[1980,1984],{"type":21,"tag":166,"props":1981,"children":1982},{},[1983],{"type":26,"value":1751},{"type":26,"value":1985},": You leverage AWS's massive investment in its cloud services and the open-source CDK, getting a significant head start compared to a pure custom build, while retaining more control and customization than many off-the-shelf IDP products.",{"type":21,"tag":22,"props":1987,"children":1988},{},[1989],{"type":26,"value":1990},"By thoughtfully applying CDK, many of the advantages of an IDP—developer self-service, automation, consistency, and speed—become accessible without the monumental effort of building or buying a full-scale platform from day one.",{"type":21,"tag":190,"props":1992,"children":1994},{"id":1993},"enhanced-testing-and-collaboration-with-cdk-driven-ephemeral-environments",[1995],{"type":26,"value":1996},"Enhanced Testing and Collaboration with CDK-Driven Ephemeral Environments",{"type":21,"tag":22,"props":1998,"children":1999},{},[2000],{"type":26,"value":2001},"The CDK-driven approach to ephemeral environments isn't merely a technical exercise; it delivers substantial, practical benefits that directly enhance both the quality of testing and the efficiency of team collaboration. By defining each environment as code, directly alongside the application, teams can test early, often, and with a high degree of realism.",{"type":21,"tag":22,"props":2003,"children":2004},{},[2005],{"type":26,"value":2006},"One of the most immediate advantages is the early detection of incompatibilities between the application and its underlying infrastructure. Because each feature branch can have its own distinct, CDK-defined environment, any infrastructure changes required for that feature are tested in isolation. This means issues that might arise from, say, a new database version or an altered network configuration are identified and resolved within the context of that feature's development, long before they could impact a shared staging environment or, worse, production. Furthermore, this isolation ensures that individual bugs or incomplete features on one branch don't derail the work of others. Developers can confidently experiment and iterate, knowing their environment is a safe sandbox, preventing the all-too-common scenario where one person's unstable code destabilizes a shared testing ground and blocks overall team progress. This is a practical application of shift left testing.",{"type":21,"tag":22,"props":2008,"children":2009},{},[2010],{"type":26,"value":2011},"This ability to spin up and tear down environments on demand also translates to greater agility when priorities shift. If a new, urgent task arises, a dedicated environment for it can be provisioned quickly via CDK without disrupting ongoing work or requiring complex manual reconfiguration of existing test setups. While development or feature-branch environments might sometimes run lighter configurations to optimize costs, the core software stack and its interaction with the CDK-defined infrastructure remain consistent, ensuring that testing efforts are meaningful and translate reliably to production.",{"type":21,"tag":22,"props":2013,"children":2014},{},[2015],{"type":26,"value":2016},"The power of this approach truly shines during high-pressure situations, as illustrated by a common scenario: deploying a critical hotfix. As a team lead, I vividly recall the stress of needing to push an urgent fix to production. In the past, deploying this to a shared staging environment was fraught with risk, potentially destabilizing ongoing testing for the next major release. Coordinating a \"quiet period\" with everyone anxiously watching was never pleasant. With CDK-managed ephemeral environments, the experience is entirely different. QA can spin up a pristine, production-like environment in minutes, validate the hotfix in complete isolation, and confidently give the green light, all while the main testing tracks for upcoming features remain undisturbed. This agility and safety proved invaluable for managing risk and has now become a cornerstone of our everyday development. Feature branches consistently receive isolated and thorough QA, resulting in seamless release cycles that often go unnoticed — a mark of true success.",{"type":21,"tag":22,"props":2018,"children":2019},{},[2020],{"type":26,"value":2021},"Beyond these direct testing benefits, integrating IaC code (via CDK) side-by-side with application code fosters a more collaborative and transparent development culture. When infrastructure definitions are visible and version-controlled within the same repository, developers gain a clearer understanding of the operational aspects of their features and feel more empowered to suggest improvements or identify potential issues early. DevOps and SecOps teams can more easily review infrastructure changes as part of the standard code review process, ensuring consistency, adherence to best practices, and preventing configuration drift. This shared ownership transforms infrastructure from a siloed concern into a team-driven, continuously improved asset, aligning perfectly with the DevOps philosophy.",{"type":21,"tag":190,"props":2023,"children":2025},{"id":2024},"conclusion-the-best-of-both-worlds-without-the-big-idp-bill",[2026],{"type":26,"value":2027},"Conclusion: The Best of Both Worlds Without the Big IDP Bill",{"type":21,"tag":22,"props":2029,"children":2030},{},[2031],{"type":26,"value":2032},"Adopting a strategy where Infrastructure as Code, particularly using a powerful framework like AWS CDK, lives alongside your application code isn't just a technical nicety; it's a fundamental shift towards more agile, reliable, and developer-centric software delivery. This approach allows teams to spin up and tear down fully independent, production-like ephemeral environments for every feature branch, pull request, or hotfix.",{"type":21,"tag":22,"props":2034,"children":2035},{},[2036],{"type":26,"value":2037},"The outcome is a development lifecycle supercharged with early feedback, isolated testing, and dramatically reduced integration risks. While the dream of a comprehensive Internal Developer Platform is appealing, the reality is that many of its most impactful benefits — developer self-service for environments, robust automation, enhanced collaboration, and deployment consistency — can be achieved with significantly less overhead. By empowering developers with tools like AWS CDK that leverage their existing programming skills, organizations can cultivate a \"platform thinking\" mindset and realize tangible improvements in speed and quality, without the substantial cost and complexity often associated with building a full-blown IDP.",{"type":21,"tag":190,"props":2039,"children":2041},{"id":2040},"optimizing-git-branching-strategy",[2042],{"type":26,"value":2043},"Optimizing Git Branching Strategy",{"type":21,"tag":22,"props":2045,"children":2046},{},[2047],{"type":26,"value":2048},"The moments I appreciate most about our deployment strategy aren't dramatic near-misses or tense rollbacks — it's exactly the opposite. It's when I realize I've gone a month without needing to micromanage merges or fret about someone accidentally pushing half-tested code to production. Thanks to reliable ephemeral environments defined in CDK and a clear branching policy, our team knows exactly what's expected. New features get smoothly tested, merged, and deployed without fuss. This frees me up to do the part of my job I love the most: crafting features that delight users and occasionally earn some kudos from colleagues.",{"type":21,"tag":190,"props":2050,"children":2052},{"id":2051},"next-step-with-ephemeral-deployments-and-cdk",[2053],{"type":26,"value":2054},"Next Step with Ephemeral Deployments and  CDK",{"type":21,"tag":22,"props":2056,"children":2057},{},[2058],{"type":26,"value":2059},"Here is a roadmap to implementing the practices discussed in this article:",{"type":21,"tag":2061,"props":2062,"children":2063},"table",{},[2064,2088],{"type":21,"tag":2065,"props":2066,"children":2067},"thead",{},[2068],{"type":21,"tag":2069,"props":2070,"children":2071},"tr",{},[2072,2078,2083],{"type":21,"tag":2073,"props":2074,"children":2075},"th",{},[2076],{"type":26,"value":2077},"#",{"type":21,"tag":2073,"props":2079,"children":2080},{},[2081],{"type":26,"value":2082},"Principle to Follow",{"type":21,"tag":2073,"props":2084,"children":2085},{},[2086],{"type":26,"value":2087},"Practical CDK Implementation Details",{"type":21,"tag":2089,"props":2090,"children":2091},"tbody",{},[2092,2119,2143,2176,2215,2261,2286,2311,2336],{"type":21,"tag":2069,"props":2093,"children":2094},{},[2095,2101,2109],{"type":21,"tag":2096,"props":2097,"children":2098},"td",{},[2099],{"type":26,"value":2100},"1",{"type":21,"tag":2096,"props":2102,"children":2103},{},[2104],{"type":21,"tag":195,"props":2105,"children":2106},{},[2107],{"type":26,"value":2108},"Embrace Infrastructure as Code (IaC) Fully",{"type":21,"tag":2096,"props":2110,"children":2111},{},[2112,2117],{"type":21,"tag":195,"props":2113,"children":2114},{},[2115],{"type":26,"value":2116},"CDK Action:",{"type":26,"value":2118}," Start by defining a small, manageable piece of your infrastructure (e.g., a single microservice and its database) using AWS CDK in your preferred language (TypeScript, Python, etc.). Commit this to a version control system like Git.",{"type":21,"tag":2069,"props":2120,"children":2121},{},[2122,2126,2134],{"type":21,"tag":2096,"props":2123,"children":2124},{},[2125],{"type":26,"value":730},{"type":21,"tag":2096,"props":2127,"children":2128},{},[2129],{"type":21,"tag":195,"props":2130,"children":2131},{},[2132],{"type":26,"value":2133},"Co-locate IaC with Application Code",{"type":21,"tag":2096,"props":2135,"children":2136},{},[2137,2141],{"type":21,"tag":195,"props":2138,"children":2139},{},[2140],{"type":26,"value":2116},{"type":26,"value":2142}," Ensure your CDK application code resides in the same Git repository as the application code it provisions infrastructure for. Structure your repository so that a feature branch contains both app and related infrastructure code.",{"type":21,"tag":2069,"props":2144,"children":2145},{},[2146,2151,2159],{"type":21,"tag":2096,"props":2147,"children":2148},{},[2149],{"type":26,"value":2150},"3",{"type":21,"tag":2096,"props":2152,"children":2153},{},[2154],{"type":21,"tag":195,"props":2155,"children":2156},{},[2157],{"type":26,"value":2158},"Establish an Ephemeral Environment Strategy",{"type":21,"tag":2096,"props":2160,"children":2161},{},[2162,2166,2168,2174],{"type":21,"tag":195,"props":2163,"children":2164},{},[2165],{"type":26,"value":2116},{"type":26,"value":2167}," Design your CDK stacks to be parameterizable (e.g., by branch name or feature ID). Use stack naming conventions (e.g., ",{"type":21,"tag":43,"props":2169,"children":2171},{"className":2170},[],[2172],{"type":26,"value":2173},"MyServiceStack-feature-xyz",{"type":26,"value":2175},") to ensure uniqueness for each ephemeral deployment.",{"type":21,"tag":2069,"props":2177,"children":2178},{},[2179,2184,2192],{"type":21,"tag":2096,"props":2180,"children":2181},{},[2182],{"type":26,"value":2183},"4",{"type":21,"tag":2096,"props":2185,"children":2186},{},[2187],{"type":21,"tag":195,"props":2188,"children":2189},{},[2190],{"type":26,"value":2191},"Automate Environment Provisioning & Destruction",{"type":21,"tag":2096,"props":2193,"children":2194},{},[2195,2199,2201,2206,2208,2213],{"type":21,"tag":195,"props":2196,"children":2197},{},[2198],{"type":26,"value":2116},{"type":26,"value":2200}," Integrate ",{"type":21,"tag":43,"props":2202,"children":2204},{"className":2203},[],[2205],{"type":26,"value":1866},{"type":26,"value":2207}," into your CI/CD pipeline (e.g., GitHub Actions, GitLab CI, AWS CodePipeline) to automatically create an environment when a feature branch is created/pushed. Implement ",{"type":21,"tag":43,"props":2209,"children":2211},{"className":2210},[],[2212],{"type":26,"value":1858},{"type":26,"value":2214}," in the pipeline to tear down the environment when the branch is merged/deleted.",{"type":21,"tag":2069,"props":2216,"children":2217},{},[2218,2223,2231],{"type":21,"tag":2096,"props":2219,"children":2220},{},[2221],{"type":26,"value":2222},"5",{"type":21,"tag":2096,"props":2224,"children":2225},{},[2226],{"type":21,"tag":195,"props":2227,"children":2228},{},[2229],{"type":26,"value":2230},"Leverage High-Level Constructs for Productivity",{"type":21,"tag":2096,"props":2232,"children":2233},{},[2234,2238,2240,2245,2246,2252,2253,2259],{"type":21,"tag":195,"props":2235,"children":2236},{},[2237],{"type":26,"value":2116},{"type":26,"value":2239}," Utilize CDK's L2/L3 constructs (e.g., ",{"type":21,"tag":43,"props":2241,"children":2243},{"className":2242},[],[2244],{"type":26,"value":487},{"type":26,"value":685},{"type":21,"tag":43,"props":2247,"children":2249},{"className":2248},[],[2250],{"type":26,"value":2251},"Queue",{"type":26,"value":685},{"type":21,"tag":43,"props":2254,"children":2256},{"className":2255},[],[2257],{"type":26,"value":2258},"Bucket",{"type":26,"value":2260},") to rapidly define common infrastructure patterns with minimal code, reducing boilerplate and accelerating development.",{"type":21,"tag":2069,"props":2262,"children":2263},{},[2264,2269,2277],{"type":21,"tag":2096,"props":2265,"children":2266},{},[2267],{"type":26,"value":2268},"6",{"type":21,"tag":2096,"props":2270,"children":2271},{},[2272],{"type":21,"tag":195,"props":2273,"children":2274},{},[2275],{"type":26,"value":2276},"Develop Reusable, Standardized Components",{"type":21,"tag":2096,"props":2278,"children":2279},{},[2280,2284],{"type":21,"tag":195,"props":2281,"children":2282},{},[2283],{"type":26,"value":2116},{"type":26,"value":2285}," Create your own custom L3 constructs that encapsulate your organization's best practices for common services (e.g., \"OurStandardWebService\" with pre-configured logging, security groups, IAM roles). Share these constructs across teams -- eg publish as a custom python package within your organization.",{"type":21,"tag":2069,"props":2287,"children":2288},{},[2289,2294,2302],{"type":21,"tag":2096,"props":2290,"children":2291},{},[2292],{"type":26,"value":2293},"7",{"type":21,"tag":2096,"props":2295,"children":2296},{},[2297],{"type":21,"tag":195,"props":2298,"children":2299},{},[2300],{"type":26,"value":2301},"Manage Configuration and Secrets as Code",{"type":21,"tag":2096,"props":2303,"children":2304},{},[2305,2309],{"type":21,"tag":195,"props":2306,"children":2307},{},[2308],{"type":26,"value":2116},{"type":26,"value":2310}," Integrate CDK with AWS Systems Manager Parameter Store or AWS Secrets Manager. Your CDK code should create/retrieve uniquely named parameters/secrets for each ephemeral environment and pass them to your application (e.g., via environment variables in container definitions).",{"type":21,"tag":2069,"props":2312,"children":2313},{},[2314,2319,2327],{"type":21,"tag":2096,"props":2315,"children":2316},{},[2317],{"type":26,"value":2318},"8",{"type":21,"tag":2096,"props":2320,"children":2321},{},[2322],{"type":21,"tag":195,"props":2323,"children":2324},{},[2325],{"type":26,"value":2326},"Implement Security as Code",{"type":21,"tag":2096,"props":2328,"children":2329},{},[2330,2334],{"type":21,"tag":195,"props":2331,"children":2332},{},[2333],{"type":26,"value":2116},{"type":26,"value":2335}," Define IAM roles, security groups, network ACLs, and encryption settings directly within your CDK application. Use least-privilege principles for roles associated with ephemeral resources. Leverage AWS Config or custom CDK aspects for compliance checks.",{"type":21,"tag":2069,"props":2337,"children":2338},{},[2339,2344,2352],{"type":21,"tag":2096,"props":2340,"children":2341},{},[2342],{"type":26,"value":2343},"9",{"type":21,"tag":2096,"props":2345,"children":2346},{},[2347],{"type":21,"tag":195,"props":2348,"children":2349},{},[2350],{"type":26,"value":2351},"Focus on Resource Management and Cost Control",{"type":21,"tag":2096,"props":2353,"children":2354},{},[2355,2359,2361,2367,2368,2374,2375,2381,2383,2388],{"type":21,"tag":195,"props":2356,"children":2357},{},[2358],{"type":26,"value":2116},{"type":26,"value":2360}," Systematically apply tags (e.g., ",{"type":21,"tag":43,"props":2362,"children":2364},{"className":2363},[],[2365],{"type":26,"value":2366},"environment:feature-xyz",{"type":26,"value":685},{"type":21,"tag":43,"props":2369,"children":2371},{"className":2370},[],[2372],{"type":26,"value":2373},"created-by:pipeline",{"type":26,"value":685},{"type":21,"tag":43,"props":2376,"children":2378},{"className":2377},[],[2379],{"type":26,"value":2380},"cost-center:my-team",{"type":26,"value":2382},") to all resources provisioned by CDK for tracking. Ensure automated ",{"type":21,"tag":43,"props":2384,"children":2386},{"className":2385},[],[2387],{"type":26,"value":1858},{"type":26,"value":2389}," processes are robust and have alerting for failures (e.g., using EventBridge, DLQ).",{"type":21,"tag":190,"props":2391,"children":2393},{"id":2392},"further-reading",[2394],{"type":26,"value":2395},"Further Reading",{"type":21,"tag":60,"props":2397,"children":2398},{},[2399,2411,2423,2435,2447,2459,2470,2482],{"type":21,"tag":64,"props":2400,"children":2401},{},[2402,2404,2409],{"type":26,"value":2403},"Forsgren, Nicole, et al. ",{"type":21,"tag":166,"props":2405,"children":2406},{},[2407],{"type":26,"value":2408},"Accelerate: The Science of Lean Software and DevOps",{"type":26,"value":2410},". IT Revolution, 2018.",{"type":21,"tag":64,"props":2412,"children":2413},{},[2414,2416,2421],{"type":26,"value":2415},"Beyer, Betsy, et al. ",{"type":21,"tag":166,"props":2417,"children":2418},{},[2419],{"type":26,"value":2420},"Site Reliability Engineering: How Google Runs Production Systems",{"type":26,"value":2422},". O’Reilly Media, 2016.",{"type":21,"tag":64,"props":2424,"children":2425},{},[2426,2428,2433],{"type":26,"value":2427},"Kim, Gene, et al. ",{"type":21,"tag":166,"props":2429,"children":2430},{},[2431],{"type":26,"value":2432},"The DevOps Handbook",{"type":26,"value":2434},". 2nd ed., IT Revolution, 2021.",{"type":21,"tag":64,"props":2436,"children":2437},{},[2438,2440,2445],{"type":26,"value":2439},"Morris, Kief. ",{"type":21,"tag":166,"props":2441,"children":2442},{},[2443],{"type":26,"value":2444},"Infrastructure as Code",{"type":26,"value":2446},". 2nd ed., O’Reilly Media, 2021.",{"type":21,"tag":64,"props":2448,"children":2449},{},[2450,2452,2457],{"type":26,"value":2451},"Cloud Native Computing Foundation, Technical Advisory Group App Delivery. Platform Engineering Maturity Model. Cloud Native Computing Foundation, Oct. 2023, ",{"type":21,"tag":206,"props":2453,"children":2455},{"href":391,"rel":2454},[210],[2456],{"type":26,"value":391},{"type":26,"value":2458},".",{"type":21,"tag":64,"props":2460,"children":2461},{},[2462,2464],{"type":26,"value":2463},"Thoughtworks. Technology Radar Vol. 32. Thoughtworks, Apr. 2025, ",{"type":21,"tag":206,"props":2465,"children":2468},{"href":2466,"rel":2467},"https://www.thoughtworks.com/en-us/radar",[210],[2469],{"type":26,"value":2466},{"type":21,"tag":64,"props":2471,"children":2472},{},[2473,2475,2481],{"type":26,"value":2474},"Kraja, Iris, et al. \"Multi-branch pipeline management and infrastructure deployment using AWS CDK Pipelines.\" AWS DevOps Blog, Amazon Web Services, 22 Dec. 2022, ",{"type":21,"tag":206,"props":2476,"children":2479},{"href":2477,"rel":2478},"https://aws.amazon.com/blogs/devops/multi-branch-pipeline-management-and-infrastructure-deployment-using-aws-cdk-pipelines/",[210],[2480],{"type":26,"value":2477},{"type":26,"value":2458},{"type":21,"tag":64,"props":2483,"children":2484},{},[2485,2487,2493],{"type":26,"value":2486},"AWS Community Builders. \"DevSecOps with AWS- Ephemeral Environments – Creating test Environments On-Demand - Part 1.\" DEV Community, 15 Oct. 2023, ",{"type":21,"tag":206,"props":2488,"children":2491},{"href":2489,"rel":2490},"https://dev.to/aws-builders/devsecops-with-aws-ephemeral-environments-creating-test-environments-on-demand-part-1-54il",[210],[2492],{"type":26,"value":2489},{"type":26,"value":2458},{"type":21,"tag":2495,"props":2496,"children":2497},"style",{},[2498],{"type":26,"value":2499},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":8,"searchDepth":232,"depth":232,"links":2501},[2502,2503,2504,2505,2512,2521,2522,2523,2524,2525],{"id":311,"depth":239,"text":314},{"id":340,"depth":239,"text":343},{"id":380,"depth":239,"text":383},{"id":417,"depth":239,"text":420,"children":2506},[2507,2508,2510,2511],{"id":455,"depth":232,"text":458},{"id":535,"depth":232,"text":2509},"Sidebar: Industry Insights from ThoughtWorks Technology Radar Vol. 32, 2025",{"id":576,"depth":232,"text":579},{"id":1668,"depth":232,"text":1671},{"id":1689,"depth":239,"text":1692,"children":2513},[2514,2515,2516,2517,2518,2519,2520],{"id":1709,"depth":232,"text":1712},{"id":1756,"depth":232,"text":1759},{"id":1792,"depth":232,"text":1795},{"id":1828,"depth":232,"text":1831},{"id":1880,"depth":232,"text":1883},{"id":1916,"depth":232,"text":1919},{"id":1952,"depth":232,"text":1955},{"id":1993,"depth":239,"text":1996},{"id":2024,"depth":239,"text":2027},{"id":2040,"depth":239,"text":2043},{"id":2051,"depth":239,"text":2054},{"id":2392,"depth":239,"text":2395},"content:ewahl:2025-05:escape_deployment_hell.md","ewahl/2025-05/escape_deployment_hell.md","ewahl/2025-05/escape_deployment_hell",{"user":247,"name":248},{"_path":2531,"_dir":2532,"_draft":7,"_partial":7,"_locale":8,"title":2533,"description":2534,"tags":2535,"image":2540,"excerpt":2534,"publishDate":2541,"body":2542,"_type":240,"_id":5320,"_source":242,"_file":5321,"_stem":5322,"_extension":245,"author":5323},"/jestep/2023-3/fastapi","2023-3","FastAPI: A High-Performance Python Framework for Rapid Web Development","FastAPI is a modern and high-performance Python web framework designed specifically for building APIs and web applications quickly and efficiently. Developed by Sebastián Ramírez and first released in 2018, FastAPI has rapidly gained traction in the developer community thanks to its focus on providing key features for API and web app development with excellent performance.",[16,2536,2537,2538,2539],"fastapi","django","flask","pyramid","/jestep/2023-3/img/header.png","2024-05-01",{"type":18,"children":2543,"toc":5292},[2544,2550,2571,2594,2616,2621,2627,2632,2638,2650,2655,2835,2878,2884,2906,2914,2922,2928,2933,2953,3055,3061,3066,3071,3081,3087,3098,3103,3109,3114,3120,3125,3130,3139,3144,3153,3158,3164,3169,3174,3637,3642,3816,3821,4064,4084,4090,4095,4693,4706,4712,4717,4981,4986,4992,4997,5003,5008,5014,5019,5025,5030,5036,5041,5047,5061,5067,5072,5078,5083,5089,5094,5147,5153,5158,5234,5239,5245,5250,5278,5283,5288],{"type":21,"tag":190,"props":2545,"children":2547},{"id":2546},"introduction",[2548],{"type":26,"value":2549},"Introduction",{"type":21,"tag":22,"props":2551,"children":2552},{},[2553,2560,2562,2569],{"type":21,"tag":206,"props":2554,"children":2557},{"href":2555,"rel":2556},"https://fastapi.tiangolo.com/",[210],[2558],{"type":26,"value":2559},"FastAPI",{"type":26,"value":2561}," is a modern and high-performance Python web framework designed specifically for building APIs and web applications quickly and efficiently. Developed by ",{"type":21,"tag":206,"props":2563,"children":2566},{"href":2564,"rel":2565},"https://tiangolo.com/",[210],[2567],{"type":26,"value":2568},"Sebastián Ramírez",{"type":26,"value":2570}," and first released in 2018, FastAPI has rapidly gained traction in the developer community thanks to its focus on providing key features for API and web app development with excellent performance.",{"type":21,"tag":22,"props":2572,"children":2573},{},[2574,2576,2583,2585,2592],{"type":26,"value":2575},"FastAPI is built on top of ",{"type":21,"tag":206,"props":2577,"children":2580},{"href":2578,"rel":2579},"https://www.starlette.io/",[210],[2581],{"type":26,"value":2582},"Starlette",{"type":26,"value":2584}," and ",{"type":21,"tag":206,"props":2586,"children":2589},{"href":2587,"rel":2588},"https://pydantic-docs.helpmanual.io/",[210],[2590],{"type":26,"value":2591},"Pydantic",{"type":26,"value":2593},", both of which contribute to its ease of use and performance. Starlette provides a lightweight and fast ASGI framework foundation, while Pydantic enables data validation using Python type hints.",{"type":21,"tag":22,"props":2595,"children":2596},{},[2597,2599,2606,2607,2614],{"type":26,"value":2598},"Compared to well-established frameworks like ",{"type":21,"tag":206,"props":2600,"children":2603},{"href":2601,"rel":2602},"https://www.djangoproject.com/",[210],[2604],{"type":26,"value":2605},"Django",{"type":26,"value":2584},{"type":21,"tag":206,"props":2608,"children":2611},{"href":2609,"rel":2610},"https://flask.palletsprojects.com/en/2.2.x/",[210],[2612],{"type":26,"value":2613},"Flask",{"type":26,"value":2615},", FastAPI differentiates itself by emphasizing developer velocity, productivity, and maintainable code.",{"type":21,"tag":22,"props":2617,"children":2618},{},[2619],{"type":26,"value":2620},"In this post, we’ll look at FastAPI’s key features, compare it to alternatives like Django and Flask, and provide use cases where FastAPI shines. We’ll also share hands-on impressions of using the framework and offer suggestions on when FastAPI may be the right choice for your next project.",{"type":21,"tag":190,"props":2622,"children":2624},{"id":2623},"fastapi-features-and-benefits",[2625],{"type":26,"value":2626},"FastAPI Features and Benefits",{"type":21,"tag":22,"props":2628,"children":2629},{},[2630],{"type":26,"value":2631},"FastAPI provides several features that make it well-suited for web development, especially APIs:",{"type":21,"tag":87,"props":2633,"children":2635},{"id":2634},"automatic-data-validation",[2636],{"type":26,"value":2637},"Automatic Data Validation",{"type":21,"tag":22,"props":2639,"children":2640},{},[2641,2643,2648],{"type":26,"value":2642},"FastAPI leverages ",{"type":21,"tag":206,"props":2644,"children":2646},{"href":2587,"rel":2645},[210],[2647],{"type":26,"value":2591},{"type":26,"value":2649}," and Python type hints to automatically validate data. This reduces boilerplate code and ensures that the API's inputs and outputs match the expected schemas.",{"type":21,"tag":22,"props":2651,"children":2652},{},[2653],{"type":26,"value":2654},"For example:",{"type":21,"tag":595,"props":2656,"children":2658},{"className":597,"code":2657,"language":16,"meta":8,"style":8},"from pydantic import BaseModel\n\nclass User(BaseModel):\n    id: int\n    name: str\n    joined: date\n    \n@app.post(\"/users\")\nasync def create_user(user: User):\n    # user will be validated against the User model\n    return user\n",[2659],{"type":21,"tag":43,"props":2660,"children":2661},{"__ignoreMap":8},[2662,2685,2694,2723,2740,2753,2761,2769,2790,2813,2822],{"type":21,"tag":399,"props":2663,"children":2664},{"class":605,"line":606},[2665,2670,2675,2680],{"type":21,"tag":399,"props":2666,"children":2667},{"style":616},[2668],{"type":26,"value":2669},"from",{"type":21,"tag":399,"props":2671,"children":2672},{"style":610},[2673],{"type":26,"value":2674}," pydantic ",{"type":21,"tag":399,"props":2676,"children":2677},{"style":616},[2678],{"type":26,"value":2679},"import",{"type":21,"tag":399,"props":2681,"children":2682},{"style":610},[2683],{"type":26,"value":2684}," BaseModel\n",{"type":21,"tag":399,"props":2686,"children":2687},{"class":605,"line":239},[2688],{"type":21,"tag":399,"props":2689,"children":2691},{"emptyLinePlaceholder":2690},true,[2692],{"type":26,"value":2693},"\n",{"type":21,"tag":399,"props":2695,"children":2696},{"class":605,"line":232},[2697,2702,2708,2713,2718],{"type":21,"tag":399,"props":2698,"children":2699},{"style":616},[2700],{"type":26,"value":2701},"class",{"type":21,"tag":399,"props":2703,"children":2705},{"style":2704},"--shiki-default:#6F42C1;--shiki-dark:#B392F0",[2706],{"type":26,"value":2707}," User",{"type":21,"tag":399,"props":2709,"children":2710},{"style":610},[2711],{"type":26,"value":2712},"(",{"type":21,"tag":399,"props":2714,"children":2715},{"style":2704},[2716],{"type":26,"value":2717},"BaseModel",{"type":21,"tag":399,"props":2719,"children":2720},{"style":610},[2721],{"type":26,"value":2722},"):\n",{"type":21,"tag":399,"props":2724,"children":2725},{"class":605,"line":654},[2726,2731,2735],{"type":21,"tag":399,"props":2727,"children":2728},{"style":630},[2729],{"type":26,"value":2730},"    id",{"type":21,"tag":399,"props":2732,"children":2733},{"style":610},[2734],{"type":26,"value":911},{"type":21,"tag":399,"props":2736,"children":2737},{"style":630},[2738],{"type":26,"value":2739},"int\n",{"type":21,"tag":399,"props":2741,"children":2742},{"class":605,"line":698},[2743,2748],{"type":21,"tag":399,"props":2744,"children":2745},{"style":610},[2746],{"type":26,"value":2747},"    name: ",{"type":21,"tag":399,"props":2749,"children":2750},{"style":630},[2751],{"type":26,"value":2752},"str\n",{"type":21,"tag":399,"props":2754,"children":2755},{"class":605,"line":737},[2756],{"type":21,"tag":399,"props":2757,"children":2758},{"style":610},[2759],{"type":26,"value":2760},"    joined: date\n",{"type":21,"tag":399,"props":2762,"children":2763},{"class":605,"line":755},[2764],{"type":21,"tag":399,"props":2765,"children":2766},{"style":610},[2767],{"type":26,"value":2768},"    \n",{"type":21,"tag":399,"props":2770,"children":2771},{"class":605,"line":773},[2772,2777,2781,2786],{"type":21,"tag":399,"props":2773,"children":2774},{"style":2704},[2775],{"type":26,"value":2776},"@app.post",{"type":21,"tag":399,"props":2778,"children":2779},{"style":610},[2780],{"type":26,"value":2712},{"type":21,"tag":399,"props":2782,"children":2783},{"style":644},[2784],{"type":26,"value":2785},"\"/users\"",{"type":21,"tag":399,"props":2787,"children":2788},{"style":610},[2789],{"type":26,"value":1652},{"type":21,"tag":399,"props":2791,"children":2792},{"class":605,"line":795},[2793,2798,2803,2808],{"type":21,"tag":399,"props":2794,"children":2795},{"style":616},[2796],{"type":26,"value":2797},"async",{"type":21,"tag":399,"props":2799,"children":2800},{"style":616},[2801],{"type":26,"value":2802}," def",{"type":21,"tag":399,"props":2804,"children":2805},{"style":2704},[2806],{"type":26,"value":2807}," create_user",{"type":21,"tag":399,"props":2809,"children":2810},{"style":610},[2811],{"type":26,"value":2812},"(user: User):\n",{"type":21,"tag":399,"props":2814,"children":2815},{"class":605,"line":804},[2816],{"type":21,"tag":399,"props":2817,"children":2819},{"style":2818},"--shiki-default:#6A737D;--shiki-dark:#6A737D",[2820],{"type":26,"value":2821},"    # user will be validated against the User model\n",{"type":21,"tag":399,"props":2823,"children":2824},{"class":605,"line":843},[2825,2830],{"type":21,"tag":399,"props":2826,"children":2827},{"style":616},[2828],{"type":26,"value":2829},"    return",{"type":21,"tag":399,"props":2831,"children":2832},{"style":610},[2833],{"type":26,"value":2834}," user\n",{"type":21,"tag":22,"props":2836,"children":2837},{},[2838,2840,2846,2848,2854,2856,2862,2863,2869,2871,2877],{"type":26,"value":2839},"Here, FastAPI automatically validates that the ",{"type":21,"tag":43,"props":2841,"children":2843},{"className":2842},[],[2844],{"type":26,"value":2845},"user",{"type":26,"value":2847}," object received matches the ",{"type":21,"tag":43,"props":2849,"children":2851},{"className":2850},[],[2852],{"type":26,"value":2853},"User",{"type":26,"value":2855}," model containing the expected types for ",{"type":21,"tag":43,"props":2857,"children":2859},{"className":2858},[],[2860],{"type":26,"value":2861},"id",{"type":26,"value":685},{"type":21,"tag":43,"props":2864,"children":2866},{"className":2865},[],[2867],{"type":26,"value":2868},"name",{"type":26,"value":2870},", and ",{"type":21,"tag":43,"props":2872,"children":2874},{"className":2873},[],[2875],{"type":26,"value":2876},"joined",{"type":26,"value":2458},{"type":21,"tag":87,"props":2879,"children":2881},{"id":2880},"automatic-interactive-api-documentation",[2882],{"type":26,"value":2883},"Automatic Interactive API Documentation",{"type":21,"tag":22,"props":2885,"children":2886},{},[2887,2889,2896,2897,2904],{"type":26,"value":2888},"FastAPI integrates with ",{"type":21,"tag":206,"props":2890,"children":2893},{"href":2891,"rel":2892},"https://swagger.io/tools/swagger-ui/",[210],[2894],{"type":26,"value":2895},"Swagger UI",{"type":26,"value":2584},{"type":21,"tag":206,"props":2898,"children":2901},{"href":2899,"rel":2900},"https://github.com/Redocly/redoc",[210],[2902],{"type":26,"value":2903},"ReDoc",{"type":26,"value":2905}," to automatically generate interactive documentation for the API. This lets you quickly test endpoints and review parameters without additional work.",{"type":21,"tag":22,"props":2907,"children":2908},{},[2909],{"type":21,"tag":1681,"props":2910,"children":2913},{"alt":2911,"src":2912},"Swagger UI docs example","/jestep/2023-3/img/index-03-swagger-02.png",[],{"type":21,"tag":22,"props":2915,"children":2916},{},[2917],{"type":21,"tag":166,"props":2918,"children":2919},{},[2920],{"type":26,"value":2921},"FastAPI Swagger UI interactive documentation",{"type":21,"tag":87,"props":2923,"children":2925},{"id":2924},"modern-python-features",[2926],{"type":26,"value":2927},"Modern Python Features",{"type":21,"tag":22,"props":2929,"children":2930},{},[2931],{"type":26,"value":2932},"FastAPI takes advantage of modern Python features like type hints and asynchronous programming. This results in cleaner and more idiomatic code while improving performance.",{"type":21,"tag":22,"props":2934,"children":2935},{},[2936,2938,2943,2945,2951],{"type":26,"value":2937},"For example, FastAPI supports Python's ",{"type":21,"tag":43,"props":2939,"children":2941},{"className":2940},[],[2942],{"type":26,"value":2797},{"type":26,"value":2944},"/",{"type":21,"tag":43,"props":2946,"children":2948},{"className":2947},[],[2949],{"type":26,"value":2950},"await",{"type":26,"value":2952}," syntax:",{"type":21,"tag":595,"props":2954,"children":2956},{"className":597,"code":2955,"language":16,"meta":8,"style":8},"@app.get(\"/items/{item_id}\")\nasync def read_item(item_id: int):\n    results = await async_database_call(item_id)\n    return results\n",[2957],{"type":21,"tag":43,"props":2958,"children":2959},{"__ignoreMap":8},[2960,2991,3021,3043],{"type":21,"tag":399,"props":2961,"children":2962},{"class":605,"line":606},[2963,2968,2972,2977,2982,2987],{"type":21,"tag":399,"props":2964,"children":2965},{"style":2704},[2966],{"type":26,"value":2967},"@app.get",{"type":21,"tag":399,"props":2969,"children":2970},{"style":610},[2971],{"type":26,"value":2712},{"type":21,"tag":399,"props":2973,"children":2974},{"style":644},[2975],{"type":26,"value":2976},"\"/items/",{"type":21,"tag":399,"props":2978,"children":2979},{"style":630},[2980],{"type":26,"value":2981},"{item_id}",{"type":21,"tag":399,"props":2983,"children":2984},{"style":644},[2985],{"type":26,"value":2986},"\"",{"type":21,"tag":399,"props":2988,"children":2989},{"style":610},[2990],{"type":26,"value":1652},{"type":21,"tag":399,"props":2992,"children":2993},{"class":605,"line":239},[2994,2998,3002,3007,3012,3017],{"type":21,"tag":399,"props":2995,"children":2996},{"style":616},[2997],{"type":26,"value":2797},{"type":21,"tag":399,"props":2999,"children":3000},{"style":616},[3001],{"type":26,"value":2802},{"type":21,"tag":399,"props":3003,"children":3004},{"style":2704},[3005],{"type":26,"value":3006}," read_item",{"type":21,"tag":399,"props":3008,"children":3009},{"style":610},[3010],{"type":26,"value":3011},"(item_id: ",{"type":21,"tag":399,"props":3013,"children":3014},{"style":630},[3015],{"type":26,"value":3016},"int",{"type":21,"tag":399,"props":3018,"children":3019},{"style":610},[3020],{"type":26,"value":2722},{"type":21,"tag":399,"props":3022,"children":3023},{"class":605,"line":232},[3024,3029,3033,3038],{"type":21,"tag":399,"props":3025,"children":3026},{"style":610},[3027],{"type":26,"value":3028},"    results ",{"type":21,"tag":399,"props":3030,"children":3031},{"style":616},[3032],{"type":26,"value":619},{"type":21,"tag":399,"props":3034,"children":3035},{"style":616},[3036],{"type":26,"value":3037}," await",{"type":21,"tag":399,"props":3039,"children":3040},{"style":610},[3041],{"type":26,"value":3042}," async_database_call(item_id)\n",{"type":21,"tag":399,"props":3044,"children":3045},{"class":605,"line":654},[3046,3050],{"type":21,"tag":399,"props":3047,"children":3048},{"style":616},[3049],{"type":26,"value":2829},{"type":21,"tag":399,"props":3051,"children":3052},{"style":610},[3053],{"type":26,"value":3054}," results\n",{"type":21,"tag":87,"props":3056,"children":3058},{"id":3057},"developer-friendly",[3059],{"type":26,"value":3060},"Developer Friendly",{"type":21,"tag":22,"props":3062,"children":3063},{},[3064],{"type":26,"value":3065},"FastAPI aims to provide a pleasant developer experience. It has an intuitive interface, helpful error messages, and encourages best practices that lead to maintainable code.",{"type":21,"tag":22,"props":3067,"children":3068},{},[3069],{"type":26,"value":3070},"For example, clear and explicit error messages help identify issues faster:",{"type":21,"tag":595,"props":3072,"children":3076},{"className":3073,"code":3075,"language":26},[3074],"language-text","422 Unprocessable Entity\n\nbody validation error\nfield required (type=value_error.missing)\n\n> body\n  > item_id\n    field required (type=value_error.missing)\n",[3077],{"type":21,"tag":43,"props":3078,"children":3079},{"__ignoreMap":8},[3080],{"type":26,"value":3075},{"type":21,"tag":87,"props":3082,"children":3084},{"id":3083},"asgi",[3085],{"type":26,"value":3086},"ASGI",{"type":21,"tag":22,"props":3088,"children":3089},{},[3090,3096],{"type":21,"tag":206,"props":3091,"children":3094},{"href":3092,"rel":3093},"https://en.wikipedia.org/wiki/Asynchronous_Server_Gateway_Interface",[210],[3095],{"type":26,"value":3086},{"type":26,"value":3097},", which stands for Asynchronous Server Gateway Interface, is a specification between web servers and Python web applications or frameworks to allow greater concurrency. It's a successor to WSGI, the Web Server Gateway Interface, which has been the standard for synchronous Python web applications.",{"type":21,"tag":22,"props":3099,"children":3100},{},[3101],{"type":26,"value":3102},"ASGI supports both synchronous and asynchronous communication, making it suitable for handling a large number of simultaneous incoming requests, particularly those that might spend a lot of time waiting, such as requests involving I/O operations.",{"type":21,"tag":190,"props":3104,"children":3106},{"id":3105},"fastapi-vs-other-frameworks",[3107],{"type":26,"value":3108},"FastAPI vs Other Frameworks",{"type":21,"tag":22,"props":3110,"children":3111},{},[3112],{"type":26,"value":3113},"How does FastAPI compare to popular frameworks like Django and Flask? Let's take a look at some of the key differences.",{"type":21,"tag":87,"props":3115,"children":3117},{"id":3116},"fastapi-vs-django",[3118],{"type":26,"value":3119},"FastAPI vs Django",{"type":21,"tag":22,"props":3121,"children":3122},{},[3123],{"type":26,"value":3124},"Django is a mature, full-featured framework with extensive documentation and an active community. However, it has a larger footprint and steeper learning curve than FastAPI.",{"type":21,"tag":22,"props":3126,"children":3127},{},[3128],{"type":26,"value":3129},"For example, Django's more complex project structure can make initial setup take longer, as well as requiring a deeper understanding of the framework's internals:",{"type":21,"tag":595,"props":3131,"children":3134},{"className":3132,"code":3133,"language":26},[3074],"myproject/\n  manage.py\n  myproject/\n    __init__.py\n    settings.py\n    urls.py\n    asgi.py\n    wsgi.py\n  app/\n    __init__.py\n    admin.py\n    apps.py\n    models.py\n    tests.py\n    views.py\n",[3135],{"type":21,"tag":43,"props":3136,"children":3137},{"__ignoreMap":8},[3138],{"type":26,"value":3133},{"type":21,"tag":22,"props":3140,"children":3141},{},[3142],{"type":26,"value":3143},"Whereas FastAPI can utilize a much simpler directory structure:",{"type":21,"tag":595,"props":3145,"children":3148},{"className":3146,"code":3147,"language":26},[3074],"myapi/\n  main.py\n  models.py\n  schemas.py\n  api/\n    __init__.py\n    endpoints.py\n",[3149],{"type":21,"tag":43,"props":3150,"children":3151},{"__ignoreMap":8},[3152],{"type":26,"value":3147},{"type":21,"tag":22,"props":3154,"children":3155},{},[3156],{"type":26,"value":3157},"Django's built-in ORM and admin interface are advantages for some projects. However, FastAPI's focus on API development makes it better suited for building lightweight, high-performance services. Django has a lot of features built-in that aren't needed in many applications. Django tries to give you everything a website COULD need, but that can be overkill for smaller projects or applications that require high performance, because you often don't NEED everything Django provides.",{"type":21,"tag":87,"props":3159,"children":3161},{"id":3160},"fastapi-vs-flask",[3162],{"type":26,"value":3163},"FastAPI vs Flask",{"type":21,"tag":22,"props":3165,"children":3166},{},[3167],{"type":26,"value":3168},"Flask and FastAPI are more directly comparable compared to Django and FastAPI, as they are both microframeworks that don't have a lot of included bloat. Compared to Flask, FastAPI's built-in features make it easier to get started and maintain code. While Flask and FastAPI are both well-suited for building APIs, and both are similar in speed, with Flask being slightly faster, FastAPI's automatic data validation and documentation generation make it a better choice for API development, in my opinion. With Flask, validation and documentation generation requires extra libraries and boilerplate code.",{"type":21,"tag":22,"props":3170,"children":3171},{},[3172],{"type":26,"value":3173},"For example, validating a POST request in Flask:",{"type":21,"tag":595,"props":3175,"children":3177},{"className":597,"code":3176,"language":16,"meta":8,"style":8},"from flask import request\nfrom werkzeug.exceptions import BadRequest\n\n@app.route(\"/users\", methods=[\"POST\"]) \ndef create_user():\n  data = request.get_json()\n\n  if not data:\n    return BadRequest(\"No JSON provided\")\n\n  if \"name\" not in data:\n    return BadRequest(\"Missing name\")\n\n  if not isinstance(data[\"name\"], str):\n    return BadRequest(\"Name must be a string\")\n\n  if \"age\" not in data:\n    return BadRequest(\"Missing age\")\n\n  if not isinstance(data[\"age\"], int):\n    return BadRequest(\"Age must be an integer\")\n\n  # ... etc for each field to be validated\n\n  # ... Create the user ...\n  \n  return user\n",[3178],{"type":21,"tag":43,"props":3179,"children":3180},{"__ignoreMap":8},[3181,3202,3223,3230,3274,3291,3308,3315,3333,3354,3361,3386,3406,3413,3453,3473,3480,3504,3524,3531,3567,3587,3594,3602,3609,3617,3625],{"type":21,"tag":399,"props":3182,"children":3183},{"class":605,"line":606},[3184,3188,3193,3197],{"type":21,"tag":399,"props":3185,"children":3186},{"style":616},[3187],{"type":26,"value":2669},{"type":21,"tag":399,"props":3189,"children":3190},{"style":610},[3191],{"type":26,"value":3192}," flask ",{"type":21,"tag":399,"props":3194,"children":3195},{"style":616},[3196],{"type":26,"value":2679},{"type":21,"tag":399,"props":3198,"children":3199},{"style":610},[3200],{"type":26,"value":3201}," request\n",{"type":21,"tag":399,"props":3203,"children":3204},{"class":605,"line":239},[3205,3209,3214,3218],{"type":21,"tag":399,"props":3206,"children":3207},{"style":616},[3208],{"type":26,"value":2669},{"type":21,"tag":399,"props":3210,"children":3211},{"style":610},[3212],{"type":26,"value":3213}," werkzeug.exceptions ",{"type":21,"tag":399,"props":3215,"children":3216},{"style":616},[3217],{"type":26,"value":2679},{"type":21,"tag":399,"props":3219,"children":3220},{"style":610},[3221],{"type":26,"value":3222}," BadRequest\n",{"type":21,"tag":399,"props":3224,"children":3225},{"class":605,"line":232},[3226],{"type":21,"tag":399,"props":3227,"children":3228},{"emptyLinePlaceholder":2690},[3229],{"type":26,"value":2693},{"type":21,"tag":399,"props":3231,"children":3232},{"class":605,"line":654},[3233,3238,3242,3246,3250,3255,3259,3264,3269],{"type":21,"tag":399,"props":3234,"children":3235},{"style":2704},[3236],{"type":26,"value":3237},"@app.route",{"type":21,"tag":399,"props":3239,"children":3240},{"style":610},[3241],{"type":26,"value":2712},{"type":21,"tag":399,"props":3243,"children":3244},{"style":644},[3245],{"type":26,"value":2785},{"type":21,"tag":399,"props":3247,"children":3248},{"style":610},[3249],{"type":26,"value":685},{"type":21,"tag":399,"props":3251,"children":3252},{"style":658},[3253],{"type":26,"value":3254},"methods",{"type":21,"tag":399,"props":3256,"children":3257},{"style":616},[3258],{"type":26,"value":619},{"type":21,"tag":399,"props":3260,"children":3261},{"style":610},[3262],{"type":26,"value":3263},"[",{"type":21,"tag":399,"props":3265,"children":3266},{"style":644},[3267],{"type":26,"value":3268},"\"POST\"",{"type":21,"tag":399,"props":3270,"children":3271},{"style":610},[3272],{"type":26,"value":3273},"]) \n",{"type":21,"tag":399,"props":3275,"children":3276},{"class":605,"line":698},[3277,3282,3286],{"type":21,"tag":399,"props":3278,"children":3279},{"style":616},[3280],{"type":26,"value":3281},"def",{"type":21,"tag":399,"props":3283,"children":3284},{"style":2704},[3285],{"type":26,"value":2807},{"type":21,"tag":399,"props":3287,"children":3288},{"style":610},[3289],{"type":26,"value":3290},"():\n",{"type":21,"tag":399,"props":3292,"children":3293},{"class":605,"line":737},[3294,3299,3303],{"type":21,"tag":399,"props":3295,"children":3296},{"style":610},[3297],{"type":26,"value":3298},"  data ",{"type":21,"tag":399,"props":3300,"children":3301},{"style":616},[3302],{"type":26,"value":619},{"type":21,"tag":399,"props":3304,"children":3305},{"style":610},[3306],{"type":26,"value":3307}," request.get_json()\n",{"type":21,"tag":399,"props":3309,"children":3310},{"class":605,"line":755},[3311],{"type":21,"tag":399,"props":3312,"children":3313},{"emptyLinePlaceholder":2690},[3314],{"type":26,"value":2693},{"type":21,"tag":399,"props":3316,"children":3317},{"class":605,"line":773},[3318,3323,3328],{"type":21,"tag":399,"props":3319,"children":3320},{"style":616},[3321],{"type":26,"value":3322},"  if",{"type":21,"tag":399,"props":3324,"children":3325},{"style":616},[3326],{"type":26,"value":3327}," not",{"type":21,"tag":399,"props":3329,"children":3330},{"style":610},[3331],{"type":26,"value":3332}," data:\n",{"type":21,"tag":399,"props":3334,"children":3335},{"class":605,"line":795},[3336,3340,3345,3350],{"type":21,"tag":399,"props":3337,"children":3338},{"style":616},[3339],{"type":26,"value":2829},{"type":21,"tag":399,"props":3341,"children":3342},{"style":610},[3343],{"type":26,"value":3344}," BadRequest(",{"type":21,"tag":399,"props":3346,"children":3347},{"style":644},[3348],{"type":26,"value":3349},"\"No JSON provided\"",{"type":21,"tag":399,"props":3351,"children":3352},{"style":610},[3353],{"type":26,"value":1652},{"type":21,"tag":399,"props":3355,"children":3356},{"class":605,"line":804},[3357],{"type":21,"tag":399,"props":3358,"children":3359},{"emptyLinePlaceholder":2690},[3360],{"type":26,"value":2693},{"type":21,"tag":399,"props":3362,"children":3363},{"class":605,"line":843},[3364,3368,3373,3377,3382],{"type":21,"tag":399,"props":3365,"children":3366},{"style":616},[3367],{"type":26,"value":3322},{"type":21,"tag":399,"props":3369,"children":3370},{"style":644},[3371],{"type":26,"value":3372}," \"name\"",{"type":21,"tag":399,"props":3374,"children":3375},{"style":616},[3376],{"type":26,"value":3327},{"type":21,"tag":399,"props":3378,"children":3379},{"style":616},[3380],{"type":26,"value":3381}," in",{"type":21,"tag":399,"props":3383,"children":3384},{"style":610},[3385],{"type":26,"value":3332},{"type":21,"tag":399,"props":3387,"children":3388},{"class":605,"line":882},[3389,3393,3397,3402],{"type":21,"tag":399,"props":3390,"children":3391},{"style":616},[3392],{"type":26,"value":2829},{"type":21,"tag":399,"props":3394,"children":3395},{"style":610},[3396],{"type":26,"value":3344},{"type":21,"tag":399,"props":3398,"children":3399},{"style":644},[3400],{"type":26,"value":3401},"\"Missing name\"",{"type":21,"tag":399,"props":3403,"children":3404},{"style":610},[3405],{"type":26,"value":1652},{"type":21,"tag":399,"props":3407,"children":3408},{"class":605,"line":900},[3409],{"type":21,"tag":399,"props":3410,"children":3411},{"emptyLinePlaceholder":2690},[3412],{"type":26,"value":2693},{"type":21,"tag":399,"props":3414,"children":3415},{"class":605,"line":923},[3416,3420,3424,3429,3434,3439,3444,3449],{"type":21,"tag":399,"props":3417,"children":3418},{"style":616},[3419],{"type":26,"value":3322},{"type":21,"tag":399,"props":3421,"children":3422},{"style":616},[3423],{"type":26,"value":3327},{"type":21,"tag":399,"props":3425,"children":3426},{"style":630},[3427],{"type":26,"value":3428}," isinstance",{"type":21,"tag":399,"props":3430,"children":3431},{"style":610},[3432],{"type":26,"value":3433},"(data[",{"type":21,"tag":399,"props":3435,"children":3436},{"style":644},[3437],{"type":26,"value":3438},"\"name\"",{"type":21,"tag":399,"props":3440,"children":3441},{"style":610},[3442],{"type":26,"value":3443},"], ",{"type":21,"tag":399,"props":3445,"children":3446},{"style":630},[3447],{"type":26,"value":3448},"str",{"type":21,"tag":399,"props":3450,"children":3451},{"style":610},[3452],{"type":26,"value":2722},{"type":21,"tag":399,"props":3454,"children":3455},{"class":605,"line":969},[3456,3460,3464,3469],{"type":21,"tag":399,"props":3457,"children":3458},{"style":616},[3459],{"type":26,"value":2829},{"type":21,"tag":399,"props":3461,"children":3462},{"style":610},[3463],{"type":26,"value":3344},{"type":21,"tag":399,"props":3465,"children":3466},{"style":644},[3467],{"type":26,"value":3468},"\"Name must be a string\"",{"type":21,"tag":399,"props":3470,"children":3471},{"style":610},[3472],{"type":26,"value":1652},{"type":21,"tag":399,"props":3474,"children":3475},{"class":605,"line":991},[3476],{"type":21,"tag":399,"props":3477,"children":3478},{"emptyLinePlaceholder":2690},[3479],{"type":26,"value":2693},{"type":21,"tag":399,"props":3481,"children":3482},{"class":605,"line":1013},[3483,3487,3492,3496,3500],{"type":21,"tag":399,"props":3484,"children":3485},{"style":616},[3486],{"type":26,"value":3322},{"type":21,"tag":399,"props":3488,"children":3489},{"style":644},[3490],{"type":26,"value":3491}," \"age\"",{"type":21,"tag":399,"props":3493,"children":3494},{"style":616},[3495],{"type":26,"value":3327},{"type":21,"tag":399,"props":3497,"children":3498},{"style":616},[3499],{"type":26,"value":3381},{"type":21,"tag":399,"props":3501,"children":3502},{"style":610},[3503],{"type":26,"value":3332},{"type":21,"tag":399,"props":3505,"children":3506},{"class":605,"line":1035},[3507,3511,3515,3520],{"type":21,"tag":399,"props":3508,"children":3509},{"style":616},[3510],{"type":26,"value":2829},{"type":21,"tag":399,"props":3512,"children":3513},{"style":610},[3514],{"type":26,"value":3344},{"type":21,"tag":399,"props":3516,"children":3517},{"style":644},[3518],{"type":26,"value":3519},"\"Missing age\"",{"type":21,"tag":399,"props":3521,"children":3522},{"style":610},[3523],{"type":26,"value":1652},{"type":21,"tag":399,"props":3525,"children":3526},{"class":605,"line":1092},[3527],{"type":21,"tag":399,"props":3528,"children":3529},{"emptyLinePlaceholder":2690},[3530],{"type":26,"value":2693},{"type":21,"tag":399,"props":3532,"children":3533},{"class":605,"line":1114},[3534,3538,3542,3546,3550,3555,3559,3563],{"type":21,"tag":399,"props":3535,"children":3536},{"style":616},[3537],{"type":26,"value":3322},{"type":21,"tag":399,"props":3539,"children":3540},{"style":616},[3541],{"type":26,"value":3327},{"type":21,"tag":399,"props":3543,"children":3544},{"style":630},[3545],{"type":26,"value":3428},{"type":21,"tag":399,"props":3547,"children":3548},{"style":610},[3549],{"type":26,"value":3433},{"type":21,"tag":399,"props":3551,"children":3552},{"style":644},[3553],{"type":26,"value":3554},"\"age\"",{"type":21,"tag":399,"props":3556,"children":3557},{"style":610},[3558],{"type":26,"value":3443},{"type":21,"tag":399,"props":3560,"children":3561},{"style":630},[3562],{"type":26,"value":3016},{"type":21,"tag":399,"props":3564,"children":3565},{"style":610},[3566],{"type":26,"value":2722},{"type":21,"tag":399,"props":3568,"children":3569},{"class":605,"line":1136},[3570,3574,3578,3583],{"type":21,"tag":399,"props":3571,"children":3572},{"style":616},[3573],{"type":26,"value":2829},{"type":21,"tag":399,"props":3575,"children":3576},{"style":610},[3577],{"type":26,"value":3344},{"type":21,"tag":399,"props":3579,"children":3580},{"style":644},[3581],{"type":26,"value":3582},"\"Age must be an integer\"",{"type":21,"tag":399,"props":3584,"children":3585},{"style":610},[3586],{"type":26,"value":1652},{"type":21,"tag":399,"props":3588,"children":3589},{"class":605,"line":1158},[3590],{"type":21,"tag":399,"props":3591,"children":3592},{"emptyLinePlaceholder":2690},[3593],{"type":26,"value":2693},{"type":21,"tag":399,"props":3595,"children":3596},{"class":605,"line":1180},[3597],{"type":21,"tag":399,"props":3598,"children":3599},{"style":2818},[3600],{"type":26,"value":3601},"  # ... etc for each field to be validated\n",{"type":21,"tag":399,"props":3603,"children":3604},{"class":605,"line":1202},[3605],{"type":21,"tag":399,"props":3606,"children":3607},{"emptyLinePlaceholder":2690},[3608],{"type":26,"value":2693},{"type":21,"tag":399,"props":3610,"children":3611},{"class":605,"line":1211},[3612],{"type":21,"tag":399,"props":3613,"children":3614},{"style":2818},[3615],{"type":26,"value":3616},"  # ... Create the user ...\n",{"type":21,"tag":399,"props":3618,"children":3619},{"class":605,"line":1228},[3620],{"type":21,"tag":399,"props":3621,"children":3622},{"style":610},[3623],{"type":26,"value":3624},"  \n",{"type":21,"tag":399,"props":3626,"children":3627},{"class":605,"line":1269},[3628,3633],{"type":21,"tag":399,"props":3629,"children":3630},{"style":616},[3631],{"type":26,"value":3632},"  return",{"type":21,"tag":399,"props":3634,"children":3635},{"style":610},[3636],{"type":26,"value":2834},{"type":21,"tag":22,"props":3638,"children":3639},{},[3640],{"type":26,"value":3641},"The same validation in FastAPI:",{"type":21,"tag":595,"props":3643,"children":3645},{"className":597,"code":3644,"language":16,"meta":8,"style":8},"from pydantic import BaseModel\n\nclass User(BaseModel):\n  name: str\n  age: int\n  # ...etc\n\n@app.post(\"/users\")\nasync def create_user(user: User):\n   # All input data is validated against the User model automatically, returning a 422 error if validation fails\n\n   # ... Create the user ...\n\n  return user\n",[3646],{"type":21,"tag":43,"props":3647,"children":3648},{"__ignoreMap":8},[3649,3668,3675,3698,3710,3722,3730,3737,3756,3775,3783,3790,3798,3805],{"type":21,"tag":399,"props":3650,"children":3651},{"class":605,"line":606},[3652,3656,3660,3664],{"type":21,"tag":399,"props":3653,"children":3654},{"style":616},[3655],{"type":26,"value":2669},{"type":21,"tag":399,"props":3657,"children":3658},{"style":610},[3659],{"type":26,"value":2674},{"type":21,"tag":399,"props":3661,"children":3662},{"style":616},[3663],{"type":26,"value":2679},{"type":21,"tag":399,"props":3665,"children":3666},{"style":610},[3667],{"type":26,"value":2684},{"type":21,"tag":399,"props":3669,"children":3670},{"class":605,"line":239},[3671],{"type":21,"tag":399,"props":3672,"children":3673},{"emptyLinePlaceholder":2690},[3674],{"type":26,"value":2693},{"type":21,"tag":399,"props":3676,"children":3677},{"class":605,"line":232},[3678,3682,3686,3690,3694],{"type":21,"tag":399,"props":3679,"children":3680},{"style":616},[3681],{"type":26,"value":2701},{"type":21,"tag":399,"props":3683,"children":3684},{"style":2704},[3685],{"type":26,"value":2707},{"type":21,"tag":399,"props":3687,"children":3688},{"style":610},[3689],{"type":26,"value":2712},{"type":21,"tag":399,"props":3691,"children":3692},{"style":2704},[3693],{"type":26,"value":2717},{"type":21,"tag":399,"props":3695,"children":3696},{"style":610},[3697],{"type":26,"value":2722},{"type":21,"tag":399,"props":3699,"children":3700},{"class":605,"line":654},[3701,3706],{"type":21,"tag":399,"props":3702,"children":3703},{"style":610},[3704],{"type":26,"value":3705},"  name: ",{"type":21,"tag":399,"props":3707,"children":3708},{"style":630},[3709],{"type":26,"value":2752},{"type":21,"tag":399,"props":3711,"children":3712},{"class":605,"line":698},[3713,3718],{"type":21,"tag":399,"props":3714,"children":3715},{"style":610},[3716],{"type":26,"value":3717},"  age: ",{"type":21,"tag":399,"props":3719,"children":3720},{"style":630},[3721],{"type":26,"value":2739},{"type":21,"tag":399,"props":3723,"children":3724},{"class":605,"line":737},[3725],{"type":21,"tag":399,"props":3726,"children":3727},{"style":2818},[3728],{"type":26,"value":3729},"  # ...etc\n",{"type":21,"tag":399,"props":3731,"children":3732},{"class":605,"line":755},[3733],{"type":21,"tag":399,"props":3734,"children":3735},{"emptyLinePlaceholder":2690},[3736],{"type":26,"value":2693},{"type":21,"tag":399,"props":3738,"children":3739},{"class":605,"line":773},[3740,3744,3748,3752],{"type":21,"tag":399,"props":3741,"children":3742},{"style":2704},[3743],{"type":26,"value":2776},{"type":21,"tag":399,"props":3745,"children":3746},{"style":610},[3747],{"type":26,"value":2712},{"type":21,"tag":399,"props":3749,"children":3750},{"style":644},[3751],{"type":26,"value":2785},{"type":21,"tag":399,"props":3753,"children":3754},{"style":610},[3755],{"type":26,"value":1652},{"type":21,"tag":399,"props":3757,"children":3758},{"class":605,"line":795},[3759,3763,3767,3771],{"type":21,"tag":399,"props":3760,"children":3761},{"style":616},[3762],{"type":26,"value":2797},{"type":21,"tag":399,"props":3764,"children":3765},{"style":616},[3766],{"type":26,"value":2802},{"type":21,"tag":399,"props":3768,"children":3769},{"style":2704},[3770],{"type":26,"value":2807},{"type":21,"tag":399,"props":3772,"children":3773},{"style":610},[3774],{"type":26,"value":2812},{"type":21,"tag":399,"props":3776,"children":3777},{"class":605,"line":804},[3778],{"type":21,"tag":399,"props":3779,"children":3780},{"style":2818},[3781],{"type":26,"value":3782},"   # All input data is validated against the User model automatically, returning a 422 error if validation fails\n",{"type":21,"tag":399,"props":3784,"children":3785},{"class":605,"line":843},[3786],{"type":21,"tag":399,"props":3787,"children":3788},{"emptyLinePlaceholder":2690},[3789],{"type":26,"value":2693},{"type":21,"tag":399,"props":3791,"children":3792},{"class":605,"line":882},[3793],{"type":21,"tag":399,"props":3794,"children":3795},{"style":2818},[3796],{"type":26,"value":3797},"   # ... Create the user ...\n",{"type":21,"tag":399,"props":3799,"children":3800},{"class":605,"line":900},[3801],{"type":21,"tag":399,"props":3802,"children":3803},{"emptyLinePlaceholder":2690},[3804],{"type":26,"value":2693},{"type":21,"tag":399,"props":3806,"children":3807},{"class":605,"line":923},[3808,3812],{"type":21,"tag":399,"props":3809,"children":3810},{"style":616},[3811],{"type":26,"value":3632},{"type":21,"tag":399,"props":3813,"children":3814},{"style":610},[3815],{"type":26,"value":2834},{"type":21,"tag":22,"props":3817,"children":3818},{},[3819],{"type":26,"value":3820},"FastAPI also allows for validating the output data, like so:",{"type":21,"tag":595,"props":3822,"children":3824},{"className":597,"code":3823,"language":16,"meta":8,"style":8},"from pydantic import BaseModel\n\nclass User(BaseModel):\n  name: str\n  age: int\n  # ...etc\n\nclass UserOut(BaseModel):\n  name: str\n  age: int\n  # ...etc\n\n@app.post(\"/users\", response_model=UserOut)\nasync def create_user(user: User):\n   # All input data is validated against the User model automatically, returning a 422 error if validation fails\n\n   # ... Create the user ...\n\n  return user\n",[3825],{"type":21,"tag":43,"props":3826,"children":3827},{"__ignoreMap":8},[3828,3847,3854,3877,3888,3899,3906,3913,3937,3948,3959,3966,3973,4006,4025,4032,4039,4046,4053],{"type":21,"tag":399,"props":3829,"children":3830},{"class":605,"line":606},[3831,3835,3839,3843],{"type":21,"tag":399,"props":3832,"children":3833},{"style":616},[3834],{"type":26,"value":2669},{"type":21,"tag":399,"props":3836,"children":3837},{"style":610},[3838],{"type":26,"value":2674},{"type":21,"tag":399,"props":3840,"children":3841},{"style":616},[3842],{"type":26,"value":2679},{"type":21,"tag":399,"props":3844,"children":3845},{"style":610},[3846],{"type":26,"value":2684},{"type":21,"tag":399,"props":3848,"children":3849},{"class":605,"line":239},[3850],{"type":21,"tag":399,"props":3851,"children":3852},{"emptyLinePlaceholder":2690},[3853],{"type":26,"value":2693},{"type":21,"tag":399,"props":3855,"children":3856},{"class":605,"line":232},[3857,3861,3865,3869,3873],{"type":21,"tag":399,"props":3858,"children":3859},{"style":616},[3860],{"type":26,"value":2701},{"type":21,"tag":399,"props":3862,"children":3863},{"style":2704},[3864],{"type":26,"value":2707},{"type":21,"tag":399,"props":3866,"children":3867},{"style":610},[3868],{"type":26,"value":2712},{"type":21,"tag":399,"props":3870,"children":3871},{"style":2704},[3872],{"type":26,"value":2717},{"type":21,"tag":399,"props":3874,"children":3875},{"style":610},[3876],{"type":26,"value":2722},{"type":21,"tag":399,"props":3878,"children":3879},{"class":605,"line":654},[3880,3884],{"type":21,"tag":399,"props":3881,"children":3882},{"style":610},[3883],{"type":26,"value":3705},{"type":21,"tag":399,"props":3885,"children":3886},{"style":630},[3887],{"type":26,"value":2752},{"type":21,"tag":399,"props":3889,"children":3890},{"class":605,"line":698},[3891,3895],{"type":21,"tag":399,"props":3892,"children":3893},{"style":610},[3894],{"type":26,"value":3717},{"type":21,"tag":399,"props":3896,"children":3897},{"style":630},[3898],{"type":26,"value":2739},{"type":21,"tag":399,"props":3900,"children":3901},{"class":605,"line":737},[3902],{"type":21,"tag":399,"props":3903,"children":3904},{"style":2818},[3905],{"type":26,"value":3729},{"type":21,"tag":399,"props":3907,"children":3908},{"class":605,"line":755},[3909],{"type":21,"tag":399,"props":3910,"children":3911},{"emptyLinePlaceholder":2690},[3912],{"type":26,"value":2693},{"type":21,"tag":399,"props":3914,"children":3915},{"class":605,"line":773},[3916,3920,3925,3929,3933],{"type":21,"tag":399,"props":3917,"children":3918},{"style":616},[3919],{"type":26,"value":2701},{"type":21,"tag":399,"props":3921,"children":3922},{"style":2704},[3923],{"type":26,"value":3924}," UserOut",{"type":21,"tag":399,"props":3926,"children":3927},{"style":610},[3928],{"type":26,"value":2712},{"type":21,"tag":399,"props":3930,"children":3931},{"style":2704},[3932],{"type":26,"value":2717},{"type":21,"tag":399,"props":3934,"children":3935},{"style":610},[3936],{"type":26,"value":2722},{"type":21,"tag":399,"props":3938,"children":3939},{"class":605,"line":795},[3940,3944],{"type":21,"tag":399,"props":3941,"children":3942},{"style":610},[3943],{"type":26,"value":3705},{"type":21,"tag":399,"props":3945,"children":3946},{"style":630},[3947],{"type":26,"value":2752},{"type":21,"tag":399,"props":3949,"children":3950},{"class":605,"line":804},[3951,3955],{"type":21,"tag":399,"props":3952,"children":3953},{"style":610},[3954],{"type":26,"value":3717},{"type":21,"tag":399,"props":3956,"children":3957},{"style":630},[3958],{"type":26,"value":2739},{"type":21,"tag":399,"props":3960,"children":3961},{"class":605,"line":843},[3962],{"type":21,"tag":399,"props":3963,"children":3964},{"style":2818},[3965],{"type":26,"value":3729},{"type":21,"tag":399,"props":3967,"children":3968},{"class":605,"line":882},[3969],{"type":21,"tag":399,"props":3970,"children":3971},{"emptyLinePlaceholder":2690},[3972],{"type":26,"value":2693},{"type":21,"tag":399,"props":3974,"children":3975},{"class":605,"line":900},[3976,3980,3984,3988,3992,3997,4001],{"type":21,"tag":399,"props":3977,"children":3978},{"style":2704},[3979],{"type":26,"value":2776},{"type":21,"tag":399,"props":3981,"children":3982},{"style":610},[3983],{"type":26,"value":2712},{"type":21,"tag":399,"props":3985,"children":3986},{"style":644},[3987],{"type":26,"value":2785},{"type":21,"tag":399,"props":3989,"children":3990},{"style":610},[3991],{"type":26,"value":685},{"type":21,"tag":399,"props":3993,"children":3994},{"style":658},[3995],{"type":26,"value":3996},"response_model",{"type":21,"tag":399,"props":3998,"children":3999},{"style":616},[4000],{"type":26,"value":619},{"type":21,"tag":399,"props":4002,"children":4003},{"style":610},[4004],{"type":26,"value":4005},"UserOut)\n",{"type":21,"tag":399,"props":4007,"children":4008},{"class":605,"line":923},[4009,4013,4017,4021],{"type":21,"tag":399,"props":4010,"children":4011},{"style":616},[4012],{"type":26,"value":2797},{"type":21,"tag":399,"props":4014,"children":4015},{"style":616},[4016],{"type":26,"value":2802},{"type":21,"tag":399,"props":4018,"children":4019},{"style":2704},[4020],{"type":26,"value":2807},{"type":21,"tag":399,"props":4022,"children":4023},{"style":610},[4024],{"type":26,"value":2812},{"type":21,"tag":399,"props":4026,"children":4027},{"class":605,"line":969},[4028],{"type":21,"tag":399,"props":4029,"children":4030},{"style":2818},[4031],{"type":26,"value":3782},{"type":21,"tag":399,"props":4033,"children":4034},{"class":605,"line":991},[4035],{"type":21,"tag":399,"props":4036,"children":4037},{"emptyLinePlaceholder":2690},[4038],{"type":26,"value":2693},{"type":21,"tag":399,"props":4040,"children":4041},{"class":605,"line":1013},[4042],{"type":21,"tag":399,"props":4043,"children":4044},{"style":2818},[4045],{"type":26,"value":3797},{"type":21,"tag":399,"props":4047,"children":4048},{"class":605,"line":1035},[4049],{"type":21,"tag":399,"props":4050,"children":4051},{"emptyLinePlaceholder":2690},[4052],{"type":26,"value":2693},{"type":21,"tag":399,"props":4054,"children":4055},{"class":605,"line":1092},[4056,4060],{"type":21,"tag":399,"props":4057,"children":4058},{"style":616},[4059],{"type":26,"value":3632},{"type":21,"tag":399,"props":4061,"children":4062},{"style":610},[4063],{"type":26,"value":2834},{"type":21,"tag":22,"props":4065,"children":4066},{},[4067,4069,4074,4076,4082],{"type":26,"value":4068},"In this example, the ",{"type":21,"tag":43,"props":4070,"children":4072},{"className":4071},[],[4073],{"type":26,"value":3996},{"type":26,"value":4075}," parameter validates the output User object against the ",{"type":21,"tag":43,"props":4077,"children":4079},{"className":4078},[],[4080],{"type":26,"value":4081},"UserOut",{"type":26,"value":4083}," model, returning a 422 error if validation fails. This ensures that the output data matches the expected format, and also assists in generating more detailed documentation.",{"type":21,"tag":87,"props":4085,"children":4087},{"id":4086},"api-documentation-in-flask",[4088],{"type":26,"value":4089},"API Documentation in Flask",{"type":21,"tag":22,"props":4091,"children":4092},{},[4093],{"type":26,"value":4094},"Generating comprehensive API documentation in Flask requires installing extensions, configuring swagger, and decorating routes:",{"type":21,"tag":595,"props":4096,"children":4098},{"className":597,"code":4097,"language":16,"meta":8,"style":8},"from flask import Flask\nfrom flask_swagger_ui import get_swaggerui_blueprint\nfrom flasgger import Swagger, swag_from\n\napp = Flask(__name__)\n\n### Required Swagger setup ###\nSWAGGER_URL = '/api/docs'\nAPI_URL = '/static/swagger.json'\nSWAGGERUI_BLUEPRINT = get_swaggerui_blueprint(\n    SWAGGER_URL,\n    API_URL,\n    config={\n        'app_name': \"My App\"\n    }\n)\napp.register_blueprint(SWAGGERUI_BLUEPRINT, url_prefix=SWAGGER_URL)\n### End Swagger setup ###\n\n\n@app.route('/users', methods=['GET'])\n@swag_from('swagger/users.yml')\ndef get_users():\n  \"\"\"Gets list of users.\n  \n  ---\n  get:\n    description: Returns list of users\n    responses:\n      200:\n        description: Successful response\n        content:\n          application/json:\n            schema:\n              type: array\n              items:\n                type: object\n                properties:\n                  id: \n                    type: integer\n                  name:\n                    type: string\n  \"\"\"\n  pass \n\nif __name__ == \"__main__\":\n    app.run()\n",[4099],{"type":21,"tag":43,"props":4100,"children":4101},{"__ignoreMap":8},[4102,4122,4143,4164,4171,4197,4204,4212,4230,4247,4264,4276,4288,4304,4321,4329,4336,4369,4377,4384,4391,4433,4454,4470,4478,4485,4493,4501,4509,4517,4525,4533,4541,4549,4557,4565,4573,4581,4589,4597,4606,4615,4624,4633,4647,4655,4684],{"type":21,"tag":399,"props":4103,"children":4104},{"class":605,"line":606},[4105,4109,4113,4117],{"type":21,"tag":399,"props":4106,"children":4107},{"style":616},[4108],{"type":26,"value":2669},{"type":21,"tag":399,"props":4110,"children":4111},{"style":610},[4112],{"type":26,"value":3192},{"type":21,"tag":399,"props":4114,"children":4115},{"style":616},[4116],{"type":26,"value":2679},{"type":21,"tag":399,"props":4118,"children":4119},{"style":610},[4120],{"type":26,"value":4121}," Flask\n",{"type":21,"tag":399,"props":4123,"children":4124},{"class":605,"line":239},[4125,4129,4134,4138],{"type":21,"tag":399,"props":4126,"children":4127},{"style":616},[4128],{"type":26,"value":2669},{"type":21,"tag":399,"props":4130,"children":4131},{"style":610},[4132],{"type":26,"value":4133}," flask_swagger_ui ",{"type":21,"tag":399,"props":4135,"children":4136},{"style":616},[4137],{"type":26,"value":2679},{"type":21,"tag":399,"props":4139,"children":4140},{"style":610},[4141],{"type":26,"value":4142}," get_swaggerui_blueprint\n",{"type":21,"tag":399,"props":4144,"children":4145},{"class":605,"line":232},[4146,4150,4155,4159],{"type":21,"tag":399,"props":4147,"children":4148},{"style":616},[4149],{"type":26,"value":2669},{"type":21,"tag":399,"props":4151,"children":4152},{"style":610},[4153],{"type":26,"value":4154}," flasgger ",{"type":21,"tag":399,"props":4156,"children":4157},{"style":616},[4158],{"type":26,"value":2679},{"type":21,"tag":399,"props":4160,"children":4161},{"style":610},[4162],{"type":26,"value":4163}," Swagger, swag_from\n",{"type":21,"tag":399,"props":4165,"children":4166},{"class":605,"line":654},[4167],{"type":21,"tag":399,"props":4168,"children":4169},{"emptyLinePlaceholder":2690},[4170],{"type":26,"value":2693},{"type":21,"tag":399,"props":4172,"children":4173},{"class":605,"line":698},[4174,4179,4183,4188,4193],{"type":21,"tag":399,"props":4175,"children":4176},{"style":610},[4177],{"type":26,"value":4178},"app ",{"type":21,"tag":399,"props":4180,"children":4181},{"style":616},[4182],{"type":26,"value":619},{"type":21,"tag":399,"props":4184,"children":4185},{"style":610},[4186],{"type":26,"value":4187}," Flask(",{"type":21,"tag":399,"props":4189,"children":4190},{"style":630},[4191],{"type":26,"value":4192},"__name__",{"type":21,"tag":399,"props":4194,"children":4195},{"style":610},[4196],{"type":26,"value":1652},{"type":21,"tag":399,"props":4198,"children":4199},{"class":605,"line":737},[4200],{"type":21,"tag":399,"props":4201,"children":4202},{"emptyLinePlaceholder":2690},[4203],{"type":26,"value":2693},{"type":21,"tag":399,"props":4205,"children":4206},{"class":605,"line":755},[4207],{"type":21,"tag":399,"props":4208,"children":4209},{"style":2818},[4210],{"type":26,"value":4211},"### Required Swagger setup ###\n",{"type":21,"tag":399,"props":4213,"children":4214},{"class":605,"line":773},[4215,4220,4225],{"type":21,"tag":399,"props":4216,"children":4217},{"style":630},[4218],{"type":26,"value":4219},"SWAGGER_URL",{"type":21,"tag":399,"props":4221,"children":4222},{"style":616},[4223],{"type":26,"value":4224}," =",{"type":21,"tag":399,"props":4226,"children":4227},{"style":644},[4228],{"type":26,"value":4229}," '/api/docs'\n",{"type":21,"tag":399,"props":4231,"children":4232},{"class":605,"line":795},[4233,4238,4242],{"type":21,"tag":399,"props":4234,"children":4235},{"style":630},[4236],{"type":26,"value":4237},"API_URL",{"type":21,"tag":399,"props":4239,"children":4240},{"style":616},[4241],{"type":26,"value":4224},{"type":21,"tag":399,"props":4243,"children":4244},{"style":644},[4245],{"type":26,"value":4246}," '/static/swagger.json'\n",{"type":21,"tag":399,"props":4248,"children":4249},{"class":605,"line":804},[4250,4255,4259],{"type":21,"tag":399,"props":4251,"children":4252},{"style":630},[4253],{"type":26,"value":4254},"SWAGGERUI_BLUEPRINT",{"type":21,"tag":399,"props":4256,"children":4257},{"style":616},[4258],{"type":26,"value":4224},{"type":21,"tag":399,"props":4260,"children":4261},{"style":610},[4262],{"type":26,"value":4263}," get_swaggerui_blueprint(\n",{"type":21,"tag":399,"props":4265,"children":4266},{"class":605,"line":843},[4267,4272],{"type":21,"tag":399,"props":4268,"children":4269},{"style":630},[4270],{"type":26,"value":4271},"    SWAGGER_URL",{"type":21,"tag":399,"props":4273,"children":4274},{"style":610},[4275],{"type":26,"value":638},{"type":21,"tag":399,"props":4277,"children":4278},{"class":605,"line":882},[4279,4284],{"type":21,"tag":399,"props":4280,"children":4281},{"style":630},[4282],{"type":26,"value":4283},"    API_URL",{"type":21,"tag":399,"props":4285,"children":4286},{"style":610},[4287],{"type":26,"value":638},{"type":21,"tag":399,"props":4289,"children":4290},{"class":605,"line":900},[4291,4296,4300],{"type":21,"tag":399,"props":4292,"children":4293},{"style":658},[4294],{"type":26,"value":4295},"    config",{"type":21,"tag":399,"props":4297,"children":4298},{"style":616},[4299],{"type":26,"value":619},{"type":21,"tag":399,"props":4301,"children":4302},{"style":610},[4303],{"type":26,"value":897},{"type":21,"tag":399,"props":4305,"children":4306},{"class":605,"line":923},[4307,4312,4316],{"type":21,"tag":399,"props":4308,"children":4309},{"style":644},[4310],{"type":26,"value":4311},"        'app_name'",{"type":21,"tag":399,"props":4313,"children":4314},{"style":610},[4315],{"type":26,"value":911},{"type":21,"tag":399,"props":4317,"children":4318},{"style":644},[4319],{"type":26,"value":4320},"\"My App\"\n",{"type":21,"tag":399,"props":4322,"children":4323},{"class":605,"line":969},[4324],{"type":21,"tag":399,"props":4325,"children":4326},{"style":610},[4327],{"type":26,"value":4328},"    }\n",{"type":21,"tag":399,"props":4330,"children":4331},{"class":605,"line":991},[4332],{"type":21,"tag":399,"props":4333,"children":4334},{"style":610},[4335],{"type":26,"value":1652},{"type":21,"tag":399,"props":4337,"children":4338},{"class":605,"line":1013},[4339,4344,4348,4352,4357,4361,4365],{"type":21,"tag":399,"props":4340,"children":4341},{"style":610},[4342],{"type":26,"value":4343},"app.register_blueprint(",{"type":21,"tag":399,"props":4345,"children":4346},{"style":630},[4347],{"type":26,"value":4254},{"type":21,"tag":399,"props":4349,"children":4350},{"style":610},[4351],{"type":26,"value":685},{"type":21,"tag":399,"props":4353,"children":4354},{"style":658},[4355],{"type":26,"value":4356},"url_prefix",{"type":21,"tag":399,"props":4358,"children":4359},{"style":616},[4360],{"type":26,"value":619},{"type":21,"tag":399,"props":4362,"children":4363},{"style":630},[4364],{"type":26,"value":4219},{"type":21,"tag":399,"props":4366,"children":4367},{"style":610},[4368],{"type":26,"value":1652},{"type":21,"tag":399,"props":4370,"children":4371},{"class":605,"line":1035},[4372],{"type":21,"tag":399,"props":4373,"children":4374},{"style":2818},[4375],{"type":26,"value":4376},"### End Swagger setup ###\n",{"type":21,"tag":399,"props":4378,"children":4379},{"class":605,"line":1092},[4380],{"type":21,"tag":399,"props":4381,"children":4382},{"emptyLinePlaceholder":2690},[4383],{"type":26,"value":2693},{"type":21,"tag":399,"props":4385,"children":4386},{"class":605,"line":1114},[4387],{"type":21,"tag":399,"props":4388,"children":4389},{"emptyLinePlaceholder":2690},[4390],{"type":26,"value":2693},{"type":21,"tag":399,"props":4392,"children":4393},{"class":605,"line":1136},[4394,4398,4402,4407,4411,4415,4419,4423,4428],{"type":21,"tag":399,"props":4395,"children":4396},{"style":2704},[4397],{"type":26,"value":3237},{"type":21,"tag":399,"props":4399,"children":4400},{"style":610},[4401],{"type":26,"value":2712},{"type":21,"tag":399,"props":4403,"children":4404},{"style":644},[4405],{"type":26,"value":4406},"'/users'",{"type":21,"tag":399,"props":4408,"children":4409},{"style":610},[4410],{"type":26,"value":685},{"type":21,"tag":399,"props":4412,"children":4413},{"style":658},[4414],{"type":26,"value":3254},{"type":21,"tag":399,"props":4416,"children":4417},{"style":616},[4418],{"type":26,"value":619},{"type":21,"tag":399,"props":4420,"children":4421},{"style":610},[4422],{"type":26,"value":3263},{"type":21,"tag":399,"props":4424,"children":4425},{"style":644},[4426],{"type":26,"value":4427},"'GET'",{"type":21,"tag":399,"props":4429,"children":4430},{"style":610},[4431],{"type":26,"value":4432},"])\n",{"type":21,"tag":399,"props":4434,"children":4435},{"class":605,"line":1158},[4436,4441,4445,4450],{"type":21,"tag":399,"props":4437,"children":4438},{"style":2704},[4439],{"type":26,"value":4440},"@swag_from",{"type":21,"tag":399,"props":4442,"children":4443},{"style":610},[4444],{"type":26,"value":2712},{"type":21,"tag":399,"props":4446,"children":4447},{"style":644},[4448],{"type":26,"value":4449},"'swagger/users.yml'",{"type":21,"tag":399,"props":4451,"children":4452},{"style":610},[4453],{"type":26,"value":1652},{"type":21,"tag":399,"props":4455,"children":4456},{"class":605,"line":1180},[4457,4461,4466],{"type":21,"tag":399,"props":4458,"children":4459},{"style":616},[4460],{"type":26,"value":3281},{"type":21,"tag":399,"props":4462,"children":4463},{"style":2704},[4464],{"type":26,"value":4465}," get_users",{"type":21,"tag":399,"props":4467,"children":4468},{"style":610},[4469],{"type":26,"value":3290},{"type":21,"tag":399,"props":4471,"children":4472},{"class":605,"line":1202},[4473],{"type":21,"tag":399,"props":4474,"children":4475},{"style":644},[4476],{"type":26,"value":4477},"  \"\"\"Gets list of users.\n",{"type":21,"tag":399,"props":4479,"children":4480},{"class":605,"line":1211},[4481],{"type":21,"tag":399,"props":4482,"children":4483},{"style":644},[4484],{"type":26,"value":3624},{"type":21,"tag":399,"props":4486,"children":4487},{"class":605,"line":1228},[4488],{"type":21,"tag":399,"props":4489,"children":4490},{"style":644},[4491],{"type":26,"value":4492},"  ---\n",{"type":21,"tag":399,"props":4494,"children":4495},{"class":605,"line":1269},[4496],{"type":21,"tag":399,"props":4497,"children":4498},{"style":644},[4499],{"type":26,"value":4500},"  get:\n",{"type":21,"tag":399,"props":4502,"children":4503},{"class":605,"line":1307},[4504],{"type":21,"tag":399,"props":4505,"children":4506},{"style":644},[4507],{"type":26,"value":4508},"    description: Returns list of users\n",{"type":21,"tag":399,"props":4510,"children":4511},{"class":605,"line":1346},[4512],{"type":21,"tag":399,"props":4513,"children":4514},{"style":644},[4515],{"type":26,"value":4516},"    responses:\n",{"type":21,"tag":399,"props":4518,"children":4519},{"class":605,"line":1355},[4520],{"type":21,"tag":399,"props":4521,"children":4522},{"style":644},[4523],{"type":26,"value":4524},"      200:\n",{"type":21,"tag":399,"props":4526,"children":4527},{"class":605,"line":1364},[4528],{"type":21,"tag":399,"props":4529,"children":4530},{"style":644},[4531],{"type":26,"value":4532},"        description: Successful response\n",{"type":21,"tag":399,"props":4534,"children":4535},{"class":605,"line":1403},[4536],{"type":21,"tag":399,"props":4537,"children":4538},{"style":644},[4539],{"type":26,"value":4540},"        content:\n",{"type":21,"tag":399,"props":4542,"children":4543},{"class":605,"line":1442},[4544],{"type":21,"tag":399,"props":4545,"children":4546},{"style":644},[4547],{"type":26,"value":4548},"          application/json:\n",{"type":21,"tag":399,"props":4550,"children":4551},{"class":605,"line":1463},[4552],{"type":21,"tag":399,"props":4553,"children":4554},{"style":644},[4555],{"type":26,"value":4556},"            schema:\n",{"type":21,"tag":399,"props":4558,"children":4559},{"class":605,"line":1501},[4560],{"type":21,"tag":399,"props":4561,"children":4562},{"style":644},[4563],{"type":26,"value":4564},"              type: array\n",{"type":21,"tag":399,"props":4566,"children":4567},{"class":605,"line":1546},[4568],{"type":21,"tag":399,"props":4569,"children":4570},{"style":644},[4571],{"type":26,"value":4572},"              items:\n",{"type":21,"tag":399,"props":4574,"children":4575},{"class":605,"line":1585},[4576],{"type":21,"tag":399,"props":4577,"children":4578},{"style":644},[4579],{"type":26,"value":4580},"                type: object\n",{"type":21,"tag":399,"props":4582,"children":4583},{"class":605,"line":1624},[4584],{"type":21,"tag":399,"props":4585,"children":4586},{"style":644},[4587],{"type":26,"value":4588},"                properties:\n",{"type":21,"tag":399,"props":4590,"children":4591},{"class":605,"line":1646},[4592],{"type":21,"tag":399,"props":4593,"children":4594},{"style":644},[4595],{"type":26,"value":4596},"                  id: \n",{"type":21,"tag":399,"props":4598,"children":4600},{"class":605,"line":4599},40,[4601],{"type":21,"tag":399,"props":4602,"children":4603},{"style":644},[4604],{"type":26,"value":4605},"                    type: integer\n",{"type":21,"tag":399,"props":4607,"children":4609},{"class":605,"line":4608},41,[4610],{"type":21,"tag":399,"props":4611,"children":4612},{"style":644},[4613],{"type":26,"value":4614},"                  name:\n",{"type":21,"tag":399,"props":4616,"children":4618},{"class":605,"line":4617},42,[4619],{"type":21,"tag":399,"props":4620,"children":4621},{"style":644},[4622],{"type":26,"value":4623},"                    type: string\n",{"type":21,"tag":399,"props":4625,"children":4627},{"class":605,"line":4626},43,[4628],{"type":21,"tag":399,"props":4629,"children":4630},{"style":644},[4631],{"type":26,"value":4632},"  \"\"\"\n",{"type":21,"tag":399,"props":4634,"children":4636},{"class":605,"line":4635},44,[4637,4642],{"type":21,"tag":399,"props":4638,"children":4639},{"style":616},[4640],{"type":26,"value":4641},"  pass",{"type":21,"tag":399,"props":4643,"children":4644},{"style":610},[4645],{"type":26,"value":4646}," \n",{"type":21,"tag":399,"props":4648,"children":4650},{"class":605,"line":4649},45,[4651],{"type":21,"tag":399,"props":4652,"children":4653},{"emptyLinePlaceholder":2690},[4654],{"type":26,"value":2693},{"type":21,"tag":399,"props":4656,"children":4658},{"class":605,"line":4657},46,[4659,4664,4669,4674,4679],{"type":21,"tag":399,"props":4660,"children":4661},{"style":616},[4662],{"type":26,"value":4663},"if",{"type":21,"tag":399,"props":4665,"children":4666},{"style":630},[4667],{"type":26,"value":4668}," __name__",{"type":21,"tag":399,"props":4670,"children":4671},{"style":616},[4672],{"type":26,"value":4673}," ==",{"type":21,"tag":399,"props":4675,"children":4676},{"style":644},[4677],{"type":26,"value":4678}," \"__main__\"",{"type":21,"tag":399,"props":4680,"children":4681},{"style":610},[4682],{"type":26,"value":4683},":\n",{"type":21,"tag":399,"props":4685,"children":4687},{"class":605,"line":4686},47,[4688],{"type":21,"tag":399,"props":4689,"children":4690},{"style":610},[4691],{"type":26,"value":4692},"    app.run()\n",{"type":21,"tag":22,"props":4694,"children":4695},{},[4696,4698,4704],{"type":26,"value":4697},"The Swagger spec must be explicitly configured and ",{"type":21,"tag":43,"props":4699,"children":4701},{"className":4700},[],[4702],{"type":26,"value":4703},"flask-swagger-ui",{"type":26,"value":4705}," registered to serve docs. API routes require decorators to define schema and parameters. This is a lot of overhead to keep documentation in sync.",{"type":21,"tag":87,"props":4707,"children":4709},{"id":4708},"api-documentation-in-fastapi",[4710],{"type":26,"value":4711},"API Documentation in FastAPI",{"type":21,"tag":22,"props":4713,"children":4714},{},[4715],{"type":26,"value":4716},"FastAPI automatically generates OpenAPI schemas and Swagger UI interactive documentation:",{"type":21,"tag":595,"props":4718,"children":4720},{"className":597,"code":4719,"language":16,"meta":8,"style":8},"from fastapi import FastAPI\n\napp = FastAPI()\n\nclass UserOut(BaseModel):\n  id: int\n  name: str\n\n@app.get(\"/users\", response_model=list[UserOut])\nasync def get_users():\n    return [{\"id\": 1, \"name\": \"John\"}]\n\nif __name__ == \"__main__\":\n    import uvicorn\n    uvicorn.run(app)\n",[4721],{"type":21,"tag":43,"props":4722,"children":4723},{"__ignoreMap":8},[4724,4745,4752,4768,4775,4798,4814,4825,4832,4864,4883,4930,4937,4960,4973],{"type":21,"tag":399,"props":4725,"children":4726},{"class":605,"line":606},[4727,4731,4736,4740],{"type":21,"tag":399,"props":4728,"children":4729},{"style":616},[4730],{"type":26,"value":2669},{"type":21,"tag":399,"props":4732,"children":4733},{"style":610},[4734],{"type":26,"value":4735}," fastapi ",{"type":21,"tag":399,"props":4737,"children":4738},{"style":616},[4739],{"type":26,"value":2679},{"type":21,"tag":399,"props":4741,"children":4742},{"style":610},[4743],{"type":26,"value":4744}," FastAPI\n",{"type":21,"tag":399,"props":4746,"children":4747},{"class":605,"line":239},[4748],{"type":21,"tag":399,"props":4749,"children":4750},{"emptyLinePlaceholder":2690},[4751],{"type":26,"value":2693},{"type":21,"tag":399,"props":4753,"children":4754},{"class":605,"line":232},[4755,4759,4763],{"type":21,"tag":399,"props":4756,"children":4757},{"style":610},[4758],{"type":26,"value":4178},{"type":21,"tag":399,"props":4760,"children":4761},{"style":616},[4762],{"type":26,"value":619},{"type":21,"tag":399,"props":4764,"children":4765},{"style":610},[4766],{"type":26,"value":4767}," FastAPI()\n",{"type":21,"tag":399,"props":4769,"children":4770},{"class":605,"line":654},[4771],{"type":21,"tag":399,"props":4772,"children":4773},{"emptyLinePlaceholder":2690},[4774],{"type":26,"value":2693},{"type":21,"tag":399,"props":4776,"children":4777},{"class":605,"line":698},[4778,4782,4786,4790,4794],{"type":21,"tag":399,"props":4779,"children":4780},{"style":616},[4781],{"type":26,"value":2701},{"type":21,"tag":399,"props":4783,"children":4784},{"style":2704},[4785],{"type":26,"value":3924},{"type":21,"tag":399,"props":4787,"children":4788},{"style":610},[4789],{"type":26,"value":2712},{"type":21,"tag":399,"props":4791,"children":4792},{"style":2704},[4793],{"type":26,"value":2717},{"type":21,"tag":399,"props":4795,"children":4796},{"style":610},[4797],{"type":26,"value":2722},{"type":21,"tag":399,"props":4799,"children":4800},{"class":605,"line":737},[4801,4806,4810],{"type":21,"tag":399,"props":4802,"children":4803},{"style":630},[4804],{"type":26,"value":4805},"  id",{"type":21,"tag":399,"props":4807,"children":4808},{"style":610},[4809],{"type":26,"value":911},{"type":21,"tag":399,"props":4811,"children":4812},{"style":630},[4813],{"type":26,"value":2739},{"type":21,"tag":399,"props":4815,"children":4816},{"class":605,"line":755},[4817,4821],{"type":21,"tag":399,"props":4818,"children":4819},{"style":610},[4820],{"type":26,"value":3705},{"type":21,"tag":399,"props":4822,"children":4823},{"style":630},[4824],{"type":26,"value":2752},{"type":21,"tag":399,"props":4826,"children":4827},{"class":605,"line":773},[4828],{"type":21,"tag":399,"props":4829,"children":4830},{"emptyLinePlaceholder":2690},[4831],{"type":26,"value":2693},{"type":21,"tag":399,"props":4833,"children":4834},{"class":605,"line":795},[4835,4839,4843,4847,4851,4855,4859],{"type":21,"tag":399,"props":4836,"children":4837},{"style":2704},[4838],{"type":26,"value":2967},{"type":21,"tag":399,"props":4840,"children":4841},{"style":610},[4842],{"type":26,"value":2712},{"type":21,"tag":399,"props":4844,"children":4845},{"style":644},[4846],{"type":26,"value":2785},{"type":21,"tag":399,"props":4848,"children":4849},{"style":610},[4850],{"type":26,"value":685},{"type":21,"tag":399,"props":4852,"children":4853},{"style":658},[4854],{"type":26,"value":3996},{"type":21,"tag":399,"props":4856,"children":4857},{"style":616},[4858],{"type":26,"value":619},{"type":21,"tag":399,"props":4860,"children":4861},{"style":610},[4862],{"type":26,"value":4863},"list[UserOut])\n",{"type":21,"tag":399,"props":4865,"children":4866},{"class":605,"line":804},[4867,4871,4875,4879],{"type":21,"tag":399,"props":4868,"children":4869},{"style":616},[4870],{"type":26,"value":2797},{"type":21,"tag":399,"props":4872,"children":4873},{"style":616},[4874],{"type":26,"value":2802},{"type":21,"tag":399,"props":4876,"children":4877},{"style":2704},[4878],{"type":26,"value":4465},{"type":21,"tag":399,"props":4880,"children":4881},{"style":610},[4882],{"type":26,"value":3290},{"type":21,"tag":399,"props":4884,"children":4885},{"class":605,"line":843},[4886,4890,4895,4900,4904,4908,4912,4916,4920,4925],{"type":21,"tag":399,"props":4887,"children":4888},{"style":616},[4889],{"type":26,"value":2829},{"type":21,"tag":399,"props":4891,"children":4892},{"style":610},[4893],{"type":26,"value":4894}," [{",{"type":21,"tag":399,"props":4896,"children":4897},{"style":644},[4898],{"type":26,"value":4899},"\"id\"",{"type":21,"tag":399,"props":4901,"children":4902},{"style":610},[4903],{"type":26,"value":911},{"type":21,"tag":399,"props":4905,"children":4906},{"style":630},[4907],{"type":26,"value":2100},{"type":21,"tag":399,"props":4909,"children":4910},{"style":610},[4911],{"type":26,"value":685},{"type":21,"tag":399,"props":4913,"children":4914},{"style":644},[4915],{"type":26,"value":3438},{"type":21,"tag":399,"props":4917,"children":4918},{"style":610},[4919],{"type":26,"value":911},{"type":21,"tag":399,"props":4921,"children":4922},{"style":644},[4923],{"type":26,"value":4924},"\"John\"",{"type":21,"tag":399,"props":4926,"children":4927},{"style":610},[4928],{"type":26,"value":4929},"}]\n",{"type":21,"tag":399,"props":4931,"children":4932},{"class":605,"line":882},[4933],{"type":21,"tag":399,"props":4934,"children":4935},{"emptyLinePlaceholder":2690},[4936],{"type":26,"value":2693},{"type":21,"tag":399,"props":4938,"children":4939},{"class":605,"line":900},[4940,4944,4948,4952,4956],{"type":21,"tag":399,"props":4941,"children":4942},{"style":616},[4943],{"type":26,"value":4663},{"type":21,"tag":399,"props":4945,"children":4946},{"style":630},[4947],{"type":26,"value":4668},{"type":21,"tag":399,"props":4949,"children":4950},{"style":616},[4951],{"type":26,"value":4673},{"type":21,"tag":399,"props":4953,"children":4954},{"style":644},[4955],{"type":26,"value":4678},{"type":21,"tag":399,"props":4957,"children":4958},{"style":610},[4959],{"type":26,"value":4683},{"type":21,"tag":399,"props":4961,"children":4962},{"class":605,"line":923},[4963,4968],{"type":21,"tag":399,"props":4964,"children":4965},{"style":616},[4966],{"type":26,"value":4967},"    import",{"type":21,"tag":399,"props":4969,"children":4970},{"style":610},[4971],{"type":26,"value":4972}," uvicorn\n",{"type":21,"tag":399,"props":4974,"children":4975},{"class":605,"line":969},[4976],{"type":21,"tag":399,"props":4977,"children":4978},{"style":610},[4979],{"type":26,"value":4980},"    uvicorn.run(app)\n",{"type":21,"tag":22,"props":4982,"children":4983},{},[4984],{"type":26,"value":4985},"That's it! FastAPI parses the function signatures and return types to build a live Swagger UI. Documentation always stays up-to-date with the code. The developer experience is greatly improved compared to manually defining schemas in Flask.",{"type":21,"tag":190,"props":4987,"children":4989},{"id":4988},"first-impressions-of-fastapi",[4990],{"type":26,"value":4991},"First Impressions of FastAPI",{"type":21,"tag":22,"props":4993,"children":4994},{},[4995],{"type":26,"value":4996},"After building an initial API with FastAPI, here are some thoughts:",{"type":21,"tag":87,"props":4998,"children":5000},{"id":4999},"quick-setup",[5001],{"type":26,"value":5002},"Quick Setup",{"type":21,"tag":22,"props":5004,"children":5005},{},[5006],{"type":26,"value":5007},"FastAPI was extremely quick to install and start using. The ability to use it directly without an application factory or settings file accelerated development.",{"type":21,"tag":87,"props":5009,"children":5011},{"id":5010},"intuitive-syntax",[5012],{"type":26,"value":5013},"Intuitive Syntax",{"type":21,"tag":22,"props":5015,"children":5016},{},[5017],{"type":26,"value":5018},"The syntax for creating endpoints, validating parameters, and configuring docs made sense right away. Python type hints integrate directly for validation.",{"type":21,"tag":87,"props":5020,"children":5022},{"id":5021},"automatic-documentation",[5023],{"type":26,"value":5024},"Automatic Documentation",{"type":21,"tag":22,"props":5026,"children":5027},{},[5028],{"type":26,"value":5029},"Generating interactive API docs using Swagger UI required no extra configuration. It was easy to immediately test endpoints and review parameters.",{"type":21,"tag":87,"props":5031,"children":5033},{"id":5032},"pydantic-data-validation",[5034],{"type":26,"value":5035},"Pydantic Data Validation",{"type":21,"tag":22,"props":5037,"children":5038},{},[5039],{"type":26,"value":5040},"Leveraging Pydantic for data validation using Python type hints reduced boilerplate code tremendously. Input and output was validated automatically.",{"type":21,"tag":87,"props":5042,"children":5044},{"id":5043},"performance",[5045],{"type":26,"value":5046},"Performance",{"type":21,"tag":22,"props":5048,"children":5049},{},[5050,5052,5059],{"type":26,"value":5051},"In ",{"type":21,"tag":206,"props":5053,"children":5056},{"href":5054,"rel":5055},"https://www.techempower.com/benchmarks/#section=data-r21&test=composite",[210],[5057],{"type":26,"value":5058},"benchmarks",{"type":26,"value":5060},", FastAPI achieved a score of 1184 compared to Django's 274, and Flask's 1229. This makes it a strong choice for high-performance web applications that don't require the speed of a lower-level language like Go or Rust, like typical APIs and microservices.",{"type":21,"tag":87,"props":5062,"children":5064},{"id":5063},"balance-of-flexibility-and-structure",[5065],{"type":26,"value":5066},"Balance of Flexibility and Structure",{"type":21,"tag":22,"props":5068,"children":5069},{},[5070],{"type":26,"value":5071},"While slightly opinionated in some areas like project structure, FastAPI still provided freedom to organize code beyond the basics as needed.",{"type":21,"tag":87,"props":5073,"children":5075},{"id":5074},"positive-first-impressions",[5076],{"type":26,"value":5077},"Positive First Impressions",{"type":21,"tag":22,"props":5079,"children":5080},{},[5081],{"type":26,"value":5082},"After creating several APIs with FastAPI, I'm impressed with FastAPI's performance, automatic validation, docs generation, and overall developer experience. The community and ecosystem are growing rapidly as well.",{"type":21,"tag":190,"props":5084,"children":5086},{"id":5085},"when-to-use-fastapi",[5087],{"type":26,"value":5088},"When to Use FastAPI",{"type":21,"tag":22,"props":5090,"children":5091},{},[5092],{"type":26,"value":5093},"Based on its strengths and comparisons to alternatives, here are some good use cases for FastAPI:",{"type":21,"tag":60,"props":5095,"children":5096},{},[5097,5107,5117,5127,5137],{"type":21,"tag":64,"props":5098,"children":5099},{},[5100,5105],{"type":21,"tag":195,"props":5101,"children":5102},{},[5103],{"type":26,"value":5104},"APIs and Microservices",{"type":26,"value":5106},": FastAPI is purpose-built for API development. Its performance and dev experience make it great for microservices too.",{"type":21,"tag":64,"props":5108,"children":5109},{},[5110,5115],{"type":21,"tag":195,"props":5111,"children":5112},{},[5113],{"type":26,"value":5114},"Real-time Applications",{"type":26,"value":5116},": FastAPI's ASGI support makes it a strong choice for apps requiring low latency like live streaming, gaming, and messaging.",{"type":21,"tag":64,"props":5118,"children":5119},{},[5120,5125],{"type":21,"tag":195,"props":5121,"children":5122},{},[5123],{"type":26,"value":5124},"Machine Learning",{"type":26,"value":5126},": For ML applications processing lots of data, FastAPI provides speed and reliability.",{"type":21,"tag":64,"props":5128,"children":5129},{},[5130,5135],{"type":21,"tag":195,"props":5131,"children":5132},{},[5133],{"type":26,"value":5134},"Legacy Modernization",{"type":26,"value":5136},": You can use FastAPI to create a modern API frontend for legacy systems and improve performance.",{"type":21,"tag":64,"props":5138,"children":5139},{},[5140,5145],{"type":21,"tag":195,"props":5141,"children":5142},{},[5143],{"type":26,"value":5144},"Prototyping and MVPs",{"type":26,"value":5146},": FastAPI's ability to create APIs and docs quickly accelerates prototyping and building minimal viable products.",{"type":21,"tag":190,"props":5148,"children":5150},{"id":5149},"real-world-usage",[5151],{"type":26,"value":5152},"Real-World Usage",{"type":21,"tag":22,"props":5154,"children":5155},{},[5156],{"type":26,"value":5157},"Several companies and organizations have adopted FastAPI for their projects, demonstrating its effectiveness and versatility in real-world use cases. Some examples include:",{"type":21,"tag":60,"props":5159,"children":5160},{},[5161,5180,5198,5208],{"type":21,"tag":64,"props":5162,"children":5163},{},[5164,5169,5171,5178],{"type":21,"tag":195,"props":5165,"children":5166},{},[5167],{"type":26,"value":5168},"Uber",{"type":26,"value":5170},": Uber uses FastAPI in their internal projects to build and maintain high-performance APIs. FastAPI's performance and ease of use make it a suitable choice for Uber's large-scale applications that need to handle a high volume of requests efficiently. Uber uses FastAPI in their ",{"type":21,"tag":206,"props":5172,"children":5175},{"href":5173,"rel":5174},"https://github.com/uber/ludwig",[210],[5176],{"type":26,"value":5177},"Ludwig machine learning framework",{"type":26,"value":5179},", a deep learning toolbox.",{"type":21,"tag":64,"props":5181,"children":5182},{},[5183,5188,5190,5197],{"type":21,"tag":195,"props":5184,"children":5185},{},[5186],{"type":26,"value":5187},"Netflix",{"type":26,"value":5189},": Netflix uses FastAPI for some of their backend services. FastAPI's performance and support for asynchronous programming help Netflix build services that are capable of handling large amounts of data and concurrent connections. Netflix uses FastAPI for their open-source crisis management orchestration framework, ",{"type":21,"tag":206,"props":5191,"children":5194},{"href":5192,"rel":5193},"https://netflix.github.io/dispatch/",[210],[5195],{"type":26,"value":5196},"Dispatch",{"type":26,"value":2458},{"type":21,"tag":64,"props":5199,"children":5200},{},[5201,5206],{"type":21,"tag":195,"props":5202,"children":5203},{},[5204],{"type":26,"value":5205},"Microsoft",{"type":26,"value":5207},": Microsoft utilizes FastAPI in some of their internal projects, taking advantage of its performance, data validation, and automatic documentation generation capabilities. FastAPI's features help Microsoft's development teams build APIs and services that are efficient, robust, and easily maintainable. Some machine learning related services that are made with FastAPI are being integrated into core Windows and Office products.",{"type":21,"tag":64,"props":5209,"children":5210},{},[5211,5216,5218,5222,5224,5227,5229,5232],{"type":21,"tag":195,"props":5212,"children":5213},{},[5214],{"type":26,"value":5215},"Art & Logic",{"type":26,"value":5217},": At Art+Logic, I have started utilizing FastAPI for various projects, including new applications and supporting older applications.",{"type":21,"tag":5219,"props":5220,"children":5221},"br",{},[],{"type":26,"value":5223},"In one particular project, we were working with a legacy application built on WebSauna, a Pyramid-based framework. The existing system relied on server-side rendering for its pages, which posed a challenge when developing a native iOS application, as we needed to create API endpoints for it. We were faced with two choices: either expand the existing WebSauna application by adding RESTful API endpoints or implement an API sidecar using another framework.",{"type":21,"tag":5219,"props":5225,"children":5226},{},[],{"type":26,"value":5228},"Given that WebSauna is a legacy framework with little to no documentation and no active support, extending it would have been a difficult and time-consuming process. Moreover, it is much heavier and harder to extend compared to FastAPI. Considering these factors, we decided to go with FastAPI for its lightweight nature and ease of use.",{"type":21,"tag":5219,"props":5230,"children":5231},{},[],{"type":26,"value":5233},"With FastAPI, we were able to quickly develop the necessary API for the iOS developer to utilize, streamlining the development process and ensuring a more efficient and maintainable solution, using a modern framework. This will also allow for the client to opt into creating new clients for web and Android, as having an API gives them that option.",{"type":21,"tag":22,"props":5235,"children":5236},{},[5237],{"type":26,"value":5238},"These real-world use cases demonstrate FastAPI's capabilities in handling a variety of projects across different industries. FastAPI's performance, ease of use, and data validation features make it an attractive choice for companies and organizations looking to build efficient, reliable, and high-performance web applications and APIs.",{"type":21,"tag":190,"props":5240,"children":5242},{"id":5241},"key-takeaways",[5243],{"type":26,"value":5244},"Key Takeaways",{"type":21,"tag":22,"props":5246,"children":5247},{},[5248],{"type":26,"value":5249},"Here are some of the key highlights of FastAPI as a Python web framework:",{"type":21,"tag":60,"props":5251,"children":5252},{},[5253,5258,5263,5268,5273],{"type":21,"tag":64,"props":5254,"children":5255},{},[5256],{"type":26,"value":5257},"Excellent performance thanks to optimizations like ASGI support",{"type":21,"tag":64,"props":5259,"children":5260},{},[5261],{"type":26,"value":5262},"Automatic data validation using Pydantic and type hints",{"type":21,"tag":64,"props":5264,"children":5265},{},[5266],{"type":26,"value":5267},"Streamlined API development experience",{"type":21,"tag":64,"props":5269,"children":5270},{},[5271],{"type":26,"value":5272},"Automatic interactive documentation using OpenAPI schemas",{"type":21,"tag":64,"props":5274,"children":5275},{},[5276],{"type":26,"value":5277},"Rapidly growing ecosystem of plugins, extensions, and community support",{"type":21,"tag":22,"props":5279,"children":5280},{},[5281],{"type":26,"value":5282},"FastAPI is ideal for developers who prioritize building APIs and web services with high productivity and little boilerplate code. Its balance of ease-of-use, flexibility, performance, and reliability make it a worthy contender among Python's web frameworks.",{"type":21,"tag":22,"props":5284,"children":5285},{},[5286],{"type":26,"value":5287},"As FastAPI continues maturing, it will be exciting to see the additional capabilities it develops alongside the growing community support. Based on my experience so far, I believe FastAPI can be the right choice for many API and web app projects that need speed, validation, and great documentation.",{"type":21,"tag":2495,"props":5289,"children":5290},{},[5291],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":5293},[5294,5295,5302,5308,5317,5318,5319],{"id":2546,"depth":239,"text":2549},{"id":2623,"depth":239,"text":2626,"children":5296},[5297,5298,5299,5300,5301],{"id":2634,"depth":232,"text":2637},{"id":2880,"depth":232,"text":2883},{"id":2924,"depth":232,"text":2927},{"id":3057,"depth":232,"text":3060},{"id":3083,"depth":232,"text":3086},{"id":3105,"depth":239,"text":3108,"children":5303},[5304,5305,5306,5307],{"id":3116,"depth":232,"text":3119},{"id":3160,"depth":232,"text":3163},{"id":4086,"depth":232,"text":4089},{"id":4708,"depth":232,"text":4711},{"id":4988,"depth":239,"text":4991,"children":5309},[5310,5311,5312,5313,5314,5315,5316],{"id":4999,"depth":232,"text":5002},{"id":5010,"depth":232,"text":5013},{"id":5021,"depth":232,"text":5024},{"id":5032,"depth":232,"text":5035},{"id":5043,"depth":232,"text":5046},{"id":5063,"depth":232,"text":5066},{"id":5074,"depth":232,"text":5077},{"id":5085,"depth":239,"text":5088},{"id":5149,"depth":239,"text":5152},{"id":5241,"depth":239,"text":5244},"content:jestep:2023-3:fastapi.md","jestep/2023-3/fastapi.md","jestep/2023-3/fastapi",{"user":5324,"name":5325},"jestep","Jagger Estep",{"_path":5327,"_dir":5328,"_draft":7,"_partial":7,"_locale":8,"title":5329,"description":5330,"tags":5331,"excerpt":5330,"image":5333,"publishDate":5334,"body":5335,"_type":240,"_id":7255,"_source":242,"_file":7256,"_stem":7257,"_extension":245,"author":7258},"/dpopowich/2023-8/postgres-pubsub","2023-8","Using PostgreSQL for Pub/Sub","A+L has been working on a Single Page Application (SPA) wherein our client's users take on the role of Staff Users (think: project managers) as they aid their Customer Users in using the application to complete a complex project.",[5332,16,2797],"postgresql","/dpopowich/2023-8/img/psql_pub_sub.png","2024-04-15",{"type":18,"children":5336,"toc":7241},[5337,5365,5370,5378,5474,5485,5490,5498,5517,5538,5543,5567,5572,5578,5600,5605,5610,5616,5621,5630,5671,5707,5770,5786,5959,5974,5980,5985,6036,6047,6094,6114,6145,6163,6186,6198,6235,6246,6271,6392,6397,6419,6454,6464,6480,6485,6727,6732,6838,6843,6849,6870,6881,6912,6918,6945,6976,7002,7079,7084,7154,7160,7184,7196,7208,7237],{"type":21,"tag":22,"props":5338,"children":5339},{},[5340,5342,5348,5350,5355,5357,5363],{"type":26,"value":5341},"A+L has been working on a Single Page Application (SPA) wherein our client's users take on the role of ",{"type":21,"tag":43,"props":5343,"children":5345},{"className":5344},[],[5346],{"type":26,"value":5347},"Staff Users",{"type":26,"value":5349}," (think: project managers) as they aid ",{"type":21,"tag":166,"props":5351,"children":5352},{},[5353],{"type":26,"value":5354},"their",{"type":26,"value":5356}," ",{"type":21,"tag":43,"props":5358,"children":5360},{"className":5359},[],[5361],{"type":26,"value":5362},"Customer Users",{"type":26,"value":5364}," in using the application to complete a complex project.",{"type":21,"tag":22,"props":5366,"children":5367},{},[5368],{"type":26,"value":5369},"The architecture is common to many SPAs:",{"type":21,"tag":22,"props":5371,"children":5372},{},[5373],{"type":21,"tag":1681,"props":5374,"children":5377},{"alt":5375,"src":5376},"architecture","/dpopowich/2023-8/spa.png",[],{"type":21,"tag":60,"props":5379,"children":5380},{},[5381,5393,5425,5437,5469],{"type":21,"tag":64,"props":5382,"children":5383},{},[5384,5386],{"type":26,"value":5385},"RDBMS - ",{"type":21,"tag":206,"props":5387,"children":5390},{"href":5388,"rel":5389},"https://www.postgresql.org/",[210],[5391],{"type":26,"value":5392},"PostgreSQL",{"type":21,"tag":64,"props":5394,"children":5395},{},[5396,5398,5405,5407,5414,5416,5423],{"type":26,"value":5397},"REST API Server - ",{"type":21,"tag":206,"props":5399,"children":5402},{"href":5400,"rel":5401},"https://docs.python.org/3.11/library/asyncio.html#module-asyncio",[210],[5403],{"type":26,"value":5404},"asynchronous python-3.11",{"type":26,"value":5406}," using ",{"type":21,"tag":206,"props":5408,"children":5411},{"href":5409,"rel":5410},"https://docs.sqlalchemy.org/en/20/orm/extensions/asyncio.html",[210],[5412],{"type":26,"value":5413},"SQLAlchemy 2.x",{"type":26,"value":5415}," with ",{"type":21,"tag":206,"props":5417,"children":5420},{"href":5418,"rel":5419},"https://magicstack.github.io/asyncpg/current/",[210],[5421],{"type":26,"value":5422},"asyncpg",{"type":26,"value":5424}," as the database engine.",{"type":21,"tag":64,"props":5426,"children":5427},{},[5428,5430],{"type":26,"value":5429},"Reverse Proxy Server - ",{"type":21,"tag":206,"props":5431,"children":5434},{"href":5432,"rel":5433},"https://www.nginx.com/",[210],[5435],{"type":26,"value":5436},"nginx",{"type":21,"tag":64,"props":5438,"children":5439},{},[5440,5442,5449,5451,5455,5457,5461,5463,5467],{"type":26,"value":5441},"SPA Clients - ",{"type":21,"tag":206,"props":5443,"children":5446},{"href":5444,"rel":5445},"https://vuejs.org/",[210],[5447],{"type":26,"value":5448},"Vue",{"type":26,"value":5450},".  As noted: the SPA supports different classes of users.  Our client's users are the ",{"type":21,"tag":166,"props":5452,"children":5453},{},[5454],{"type":26,"value":5347},{"type":26,"value":5456},", taking on the role of Project Manager, aiding ",{"type":21,"tag":166,"props":5458,"children":5459},{},[5460],{"type":26,"value":5354},{"type":26,"value":5462}," customers (",{"type":21,"tag":166,"props":5464,"children":5465},{},[5466],{"type":26,"value":5362},{"type":26,"value":5468}," in the diagram) in completing a project.",{"type":21,"tag":64,"props":5470,"children":5471},{},[5472],{"type":26,"value":5473},"Notifications - early versions of the application configured the API servers to send notifications via an external SMTP server.",{"type":21,"tag":190,"props":5475,"children":5477},{"id":5476},"live-updates",[5478,5483],{"type":21,"tag":166,"props":5479,"children":5480},{},[5481],{"type":26,"value":5482},"Live",{"type":26,"value":5484}," Updates",{"type":21,"tag":22,"props":5486,"children":5487},{},[5488],{"type":26,"value":5489},"The SPA has a dashboard which shows a Customer User their progress in completing all their tasks; a simple graphic of a bar, 0% to 100%, with the bar filled to the percentage of tasks completed by the customer.",{"type":21,"tag":22,"props":5491,"children":5492},{},[5493],{"type":21,"tag":1681,"props":5494,"children":5497},{"alt":5495,"src":5496},"progress","/dpopowich/2023-8/img/progress.png",[],{"type":21,"tag":22,"props":5499,"children":5500},{},[5501,5503,5508,5510,5515],{"type":26,"value":5502},"Below the graphic, a list of outstanding tasks is displayed.  An interesting aspect of this application is that many customers, working on the same project, can affect ",{"type":21,"tag":166,"props":5504,"children":5505},{},[5506],{"type":26,"value":5507},"other",{"type":26,"value":5509}," customer's progress.  E.g., a customer who is idle in the application may still see the progress increase as ",{"type":21,"tag":166,"props":5511,"children":5512},{},[5513],{"type":26,"value":5514},"another",{"type":26,"value":5516}," customer completes a task.",{"type":21,"tag":22,"props":5518,"children":5519},{},[5520,5522,5529,5531,5536],{"type":26,"value":5521},"Early demands on the project (mostly time-demands on getting a ",{"type":21,"tag":206,"props":5523,"children":5526},{"href":5524,"rel":5525},"https://en.wikipedia.org/wiki/Minimum_viable_product",[210],[5527],{"type":26,"value":5528},"MVP",{"type":26,"value":5530}," delivered for client review) prevented us from integrating websockets, so the SPA polls every ",{"type":21,"tag":166,"props":5532,"children":5533},{},[5534],{"type":26,"value":5535},"n",{"type":26,"value":5537},"-minutes for progress via the REST API.",{"type":21,"tag":22,"props":5539,"children":5540},{},[5541],{"type":26,"value":5542},"As we approach publication of the first version of the product we're now exploring ideas we had to table during early MVP development.  These future enhancements mostly center around data updates and notifications:",{"type":21,"tag":60,"props":5544,"children":5545},{},[5546,5551,5556],{"type":21,"tag":64,"props":5547,"children":5548},{},[5549],{"type":26,"value":5550},"Calculating progress is not cheap. Having every user poll every n-minutes is very expensive, especially when you consider it is difficult for the SPA to know how frequent to poll.  Since the server knows when data comes in from collaborating users that may affect progress, it's a clear win to have the server send notifications.",{"type":21,"tag":64,"props":5552,"children":5553},{},[5554],{"type":26,"value":5555},"There's a fair amount of complexity in completing tasks and it is expected customers will have several questions for staff, so there's a desire to add a live chat feature, allowing customers to ask questions.  These questions and answers are not ephemeral, but must be recorded for the life of the project.",{"type":21,"tag":64,"props":5557,"children":5558},{},[5559,5561,5565],{"type":26,"value":5560},"We're planning on removing immediate notification of events by email, rather, sending notifications via the same mechanism as live chat.  To handle missed notifications, a background task can poll the database for unseen notifications and send email only after ",{"type":21,"tag":166,"props":5562,"children":5563},{},[5564],{"type":26,"value":5535},{"type":26,"value":5566},"-hours of going unread.  This will also limit \"spamming\" staff with each completed task (which they might already know about if they're active in the SPA), spooling up several notifications in one email.",{"type":21,"tag":22,"props":5568,"children":5569},{},[5570],{"type":26,"value":5571},"Enter websockets.  Integrating websockets gives the server a direct connection to the SPA and can send notifications for all these desired notifications.",{"type":21,"tag":190,"props":5573,"children":5575},{"id":5574},"pubsub",[5576],{"type":26,"value":5577},"Pub/Sub",{"type":21,"tag":22,"props":5579,"children":5580},{},[5581,5583,5589,5591,5598],{"type":26,"value":5582},"When I think of implementing ",{"type":21,"tag":206,"props":5584,"children":5587},{"href":5585,"rel":5586},"https://en.wikipedia.org/wiki/Publish%E2%80%93subscribe_pattern",[210],[5588],{"type":26,"value":5577},{"type":26,"value":5590}," for an application my go-to has been ",{"type":21,"tag":206,"props":5592,"children":5595},{"href":5593,"rel":5594},"https://redis.io/docs/interact/pubsub/",[210],[5596],{"type":26,"value":5597},"redis",{"type":26,"value":5599},".  It is both performant and scalable.  It is an excellent choice for many use-cases and particularly useful for ephemeral communication that is not tied to any data.",{"type":21,"tag":22,"props":5601,"children":5602},{},[5603],{"type":26,"value":5604},"But if you need to know your notifications have been seen or need archives of all communication, you will need persistence of your messages.  This makes redis less desireable as you will need to maintain your data in two places: 1) a permanent storage, like a RDBMS, 2) your redis server.",{"type":21,"tag":22,"props":5606,"children":5607},{},[5608],{"type":26,"value":5609},"If you're using PostgreSQL for your data storage you already have (a little known) pub/sub engine.",{"type":21,"tag":190,"props":5611,"children":5613},{"id":5612},"demo-preparation",[5614],{"type":26,"value":5615},"Demo Preparation",{"type":21,"tag":22,"props":5617,"children":5618},{},[5619],{"type":26,"value":5620},"You can run all the code in this tutorial if you have python-3 (tested on 3.8–3.11) and access to docker for installation of PostgreSQL.  If you have earlier versions of python and/or your own installation of PostgreSQL, your mileage may vary as you follow along.",{"type":21,"tag":5622,"props":5623,"children":5624},"ol",{},[5625],{"type":21,"tag":64,"props":5626,"children":5627},{},[5628],{"type":26,"value":5629},"Clone the repo holding demo SQL and python script:",{"type":21,"tag":595,"props":5631,"children":5635},{"className":5632,"code":5633,"language":5634,"meta":8,"style":8},"language-console shiki shiki-themes github-light github-dark","$ git clone https://github.com/artandlogic/postgresql-pubsub-demo.git\n$ cd postgresql-pubsub-demo\n$ ls\ndemo.py  demo.sql  README.md\n","console",[5636],{"type":21,"tag":43,"props":5637,"children":5638},{"__ignoreMap":8},[5639,5647,5655,5663],{"type":21,"tag":399,"props":5640,"children":5641},{"class":605,"line":606},[5642],{"type":21,"tag":399,"props":5643,"children":5644},{},[5645],{"type":26,"value":5646},"$ git clone https://github.com/artandlogic/postgresql-pubsub-demo.git\n",{"type":21,"tag":399,"props":5648,"children":5649},{"class":605,"line":239},[5650],{"type":21,"tag":399,"props":5651,"children":5652},{},[5653],{"type":26,"value":5654},"$ cd postgresql-pubsub-demo\n",{"type":21,"tag":399,"props":5656,"children":5657},{"class":605,"line":232},[5658],{"type":21,"tag":399,"props":5659,"children":5660},{},[5661],{"type":26,"value":5662},"$ ls\n",{"type":21,"tag":399,"props":5664,"children":5665},{"class":605,"line":654},[5666],{"type":21,"tag":399,"props":5667,"children":5668},{},[5669],{"type":26,"value":5670},"demo.py  demo.sql  README.md\n",{"type":21,"tag":5622,"props":5672,"children":5673},{"start":239},[5674,5695],{"type":21,"tag":64,"props":5675,"children":5676},{},[5677,5679,5685,5687,5693],{"type":26,"value":5678},"Review two files in the repo: ",{"type":21,"tag":43,"props":5680,"children":5682},{"className":5681},[],[5683],{"type":26,"value":5684},"demo.sql",{"type":26,"value":5686},", SQL schema for the demo, and ",{"type":21,"tag":43,"props":5688,"children":5690},{"className":5689},[],[5691],{"type":26,"value":5692},"demo.py",{"type":26,"value":5694},", a simple asynchronous python application.",{"type":21,"tag":64,"props":5696,"children":5697},{},[5698,5700,5705],{"type":26,"value":5699},"Start PostgreSQL via docker.  The following commands will launch a container, loading the SQL downloaded in step 1.  It is important to run the command from the cloned directory containing ",{"type":21,"tag":43,"props":5701,"children":5703},{"className":5702},[],[5704],{"type":26,"value":5684},{"type":26,"value":5706},", so the database initializes properly.  This binds host port 8432 to the container's port 5432 (postgresql).  If that port is not available, edit as necessary",{"type":21,"tag":595,"props":5708,"children":5710},{"className":5632,"code":5709,"language":5634,"meta":8,"style":8},"$ export PGHOST=localhost PGPORT=8432 PGUSER=demo PGPASSWORD=\"passw0rd!\"\n$ docker run --rm -d --name pg-pubsub-demo  \\\n    -v $(pwd):/docker-entrypoint-initdb.d \\\n    -e POSTGRES_USER=$PGUSER -e POSTGRES_PASSWORD=$PGPASSWORD \\\n    -p $PGPORT:5432 \\\n    postgres:14-alpine\n$ alias psql='docker exec -it pg-pubsub-demo psql -U demo'\n",[5711],{"type":21,"tag":43,"props":5712,"children":5713},{"__ignoreMap":8},[5714,5722,5730,5738,5746,5754,5762],{"type":21,"tag":399,"props":5715,"children":5716},{"class":605,"line":606},[5717],{"type":21,"tag":399,"props":5718,"children":5719},{},[5720],{"type":26,"value":5721},"$ export PGHOST=localhost PGPORT=8432 PGUSER=demo PGPASSWORD=\"passw0rd!\"\n",{"type":21,"tag":399,"props":5723,"children":5724},{"class":605,"line":239},[5725],{"type":21,"tag":399,"props":5726,"children":5727},{},[5728],{"type":26,"value":5729},"$ docker run --rm -d --name pg-pubsub-demo  \\\n",{"type":21,"tag":399,"props":5731,"children":5732},{"class":605,"line":232},[5733],{"type":21,"tag":399,"props":5734,"children":5735},{},[5736],{"type":26,"value":5737},"    -v $(pwd):/docker-entrypoint-initdb.d \\\n",{"type":21,"tag":399,"props":5739,"children":5740},{"class":605,"line":654},[5741],{"type":21,"tag":399,"props":5742,"children":5743},{},[5744],{"type":26,"value":5745},"    -e POSTGRES_USER=$PGUSER -e POSTGRES_PASSWORD=$PGPASSWORD \\\n",{"type":21,"tag":399,"props":5747,"children":5748},{"class":605,"line":698},[5749],{"type":21,"tag":399,"props":5750,"children":5751},{},[5752],{"type":26,"value":5753},"    -p $PGPORT:5432 \\\n",{"type":21,"tag":399,"props":5755,"children":5756},{"class":605,"line":737},[5757],{"type":21,"tag":399,"props":5758,"children":5759},{},[5760],{"type":26,"value":5761},"    postgres:14-alpine\n",{"type":21,"tag":399,"props":5763,"children":5764},{"class":605,"line":755},[5765],{"type":21,"tag":399,"props":5766,"children":5767},{},[5768],{"type":26,"value":5769},"$ alias psql='docker exec -it pg-pubsub-demo psql -U demo'\n",{"type":21,"tag":5622,"props":5771,"children":5772},{"start":654},[5773],{"type":21,"tag":64,"props":5774,"children":5775},{},[5776,5778,5784],{"type":26,"value":5777},"Run ",{"type":21,"tag":43,"props":5779,"children":5781},{"className":5780},[],[5782],{"type":26,"value":5783},"psql",{"type":26,"value":5785}," to verify the setup.  You should see the following:",{"type":21,"tag":595,"props":5787,"children":5789},{"className":5632,"code":5788,"language":5634,"meta":8,"style":8},"$ psql\npsql (14.5)\nType \"help\" for help.\n\ndemo=# \\d\n             List of relations\n Schema |      Name      |   Type   | Owner\n--------+----------------+----------+-------\n public | appuser        | table    | demo\n public | appuser_id_seq | sequence | demo\n public | message        | table    | demo\n public | message_id_seq | sequence | demo\n(4 rows)\n\ndemo=# select * from appuser;\n id | fullname\n----+----------\n  1 | Abe\n  2 | Bob\n  3 | Charlie\n(3 rows)\n",[5790],{"type":21,"tag":43,"props":5791,"children":5792},{"__ignoreMap":8},[5793,5801,5809,5817,5824,5832,5840,5848,5856,5864,5872,5880,5888,5896,5903,5911,5919,5927,5935,5943,5951],{"type":21,"tag":399,"props":5794,"children":5795},{"class":605,"line":606},[5796],{"type":21,"tag":399,"props":5797,"children":5798},{},[5799],{"type":26,"value":5800},"$ psql\n",{"type":21,"tag":399,"props":5802,"children":5803},{"class":605,"line":239},[5804],{"type":21,"tag":399,"props":5805,"children":5806},{},[5807],{"type":26,"value":5808},"psql (14.5)\n",{"type":21,"tag":399,"props":5810,"children":5811},{"class":605,"line":232},[5812],{"type":21,"tag":399,"props":5813,"children":5814},{},[5815],{"type":26,"value":5816},"Type \"help\" for help.\n",{"type":21,"tag":399,"props":5818,"children":5819},{"class":605,"line":654},[5820],{"type":21,"tag":399,"props":5821,"children":5822},{"emptyLinePlaceholder":2690},[5823],{"type":26,"value":2693},{"type":21,"tag":399,"props":5825,"children":5826},{"class":605,"line":698},[5827],{"type":21,"tag":399,"props":5828,"children":5829},{},[5830],{"type":26,"value":5831},"demo=# \\d\n",{"type":21,"tag":399,"props":5833,"children":5834},{"class":605,"line":737},[5835],{"type":21,"tag":399,"props":5836,"children":5837},{},[5838],{"type":26,"value":5839},"             List of relations\n",{"type":21,"tag":399,"props":5841,"children":5842},{"class":605,"line":755},[5843],{"type":21,"tag":399,"props":5844,"children":5845},{},[5846],{"type":26,"value":5847}," Schema |      Name      |   Type   | Owner\n",{"type":21,"tag":399,"props":5849,"children":5850},{"class":605,"line":773},[5851],{"type":21,"tag":399,"props":5852,"children":5853},{},[5854],{"type":26,"value":5855},"--------+----------------+----------+-------\n",{"type":21,"tag":399,"props":5857,"children":5858},{"class":605,"line":795},[5859],{"type":21,"tag":399,"props":5860,"children":5861},{},[5862],{"type":26,"value":5863}," public | appuser        | table    | demo\n",{"type":21,"tag":399,"props":5865,"children":5866},{"class":605,"line":804},[5867],{"type":21,"tag":399,"props":5868,"children":5869},{},[5870],{"type":26,"value":5871}," public | appuser_id_seq | sequence | demo\n",{"type":21,"tag":399,"props":5873,"children":5874},{"class":605,"line":843},[5875],{"type":21,"tag":399,"props":5876,"children":5877},{},[5878],{"type":26,"value":5879}," public | message        | table    | demo\n",{"type":21,"tag":399,"props":5881,"children":5882},{"class":605,"line":882},[5883],{"type":21,"tag":399,"props":5884,"children":5885},{},[5886],{"type":26,"value":5887}," public | message_id_seq | sequence | demo\n",{"type":21,"tag":399,"props":5889,"children":5890},{"class":605,"line":900},[5891],{"type":21,"tag":399,"props":5892,"children":5893},{},[5894],{"type":26,"value":5895},"(4 rows)\n",{"type":21,"tag":399,"props":5897,"children":5898},{"class":605,"line":923},[5899],{"type":21,"tag":399,"props":5900,"children":5901},{"emptyLinePlaceholder":2690},[5902],{"type":26,"value":2693},{"type":21,"tag":399,"props":5904,"children":5905},{"class":605,"line":969},[5906],{"type":21,"tag":399,"props":5907,"children":5908},{},[5909],{"type":26,"value":5910},"demo=# select * from appuser;\n",{"type":21,"tag":399,"props":5912,"children":5913},{"class":605,"line":991},[5914],{"type":21,"tag":399,"props":5915,"children":5916},{},[5917],{"type":26,"value":5918}," id | fullname\n",{"type":21,"tag":399,"props":5920,"children":5921},{"class":605,"line":1013},[5922],{"type":21,"tag":399,"props":5923,"children":5924},{},[5925],{"type":26,"value":5926},"----+----------\n",{"type":21,"tag":399,"props":5928,"children":5929},{"class":605,"line":1035},[5930],{"type":21,"tag":399,"props":5931,"children":5932},{},[5933],{"type":26,"value":5934},"  1 | Abe\n",{"type":21,"tag":399,"props":5936,"children":5937},{"class":605,"line":1092},[5938],{"type":21,"tag":399,"props":5939,"children":5940},{},[5941],{"type":26,"value":5942},"  2 | Bob\n",{"type":21,"tag":399,"props":5944,"children":5945},{"class":605,"line":1114},[5946],{"type":21,"tag":399,"props":5947,"children":5948},{},[5949],{"type":26,"value":5950},"  3 | Charlie\n",{"type":21,"tag":399,"props":5952,"children":5953},{"class":605,"line":1136},[5954],{"type":21,"tag":399,"props":5955,"children":5956},{},[5957],{"type":26,"value":5958},"(3 rows)\n",{"type":21,"tag":306,"props":5960,"children":5961},{},[5962],{"type":21,"tag":22,"props":5963,"children":5964},{},[5965,5967,5973],{"type":26,"value":5966},"NOTE: When you are finished with the demo, you can stop/remove the container with ",{"type":21,"tag":43,"props":5968,"children":5970},{"className":5969},[],[5971],{"type":26,"value":5972},"docker stop pg-pubsub-demo",{"type":26,"value":2458},{"type":21,"tag":190,"props":5975,"children":5977},{"id":5976},"notifylistenunlisten",[5978],{"type":26,"value":5979},"NOTIFY/LISTEN/UNLISTEN",{"type":21,"tag":22,"props":5981,"children":5982},{},[5983],{"type":26,"value":5984},"PostgreSQL has three non-standard SQL commands:",{"type":21,"tag":60,"props":5986,"children":5987},{},[5988,6004,6020],{"type":21,"tag":64,"props":5989,"children":5990},{},[5991,6002],{"type":21,"tag":206,"props":5992,"children":5995},{"href":5993,"rel":5994},"https://www.postgresql.org/docs/14/sql-notify.html",[210],[5996],{"type":21,"tag":43,"props":5997,"children":5999},{"className":5998},[],[6000],{"type":26,"value":6001},"NOTIFY",{"type":26,"value":6003}," - publish to a channel.",{"type":21,"tag":64,"props":6005,"children":6006},{},[6007,6018],{"type":21,"tag":206,"props":6008,"children":6011},{"href":6009,"rel":6010},"https://www.postgresql.org/docs/14/sql-listen.html",[210],[6012],{"type":21,"tag":43,"props":6013,"children":6015},{"className":6014},[],[6016],{"type":26,"value":6017},"LISTEN",{"type":26,"value":6019}," - subscribe to a channel.",{"type":21,"tag":64,"props":6021,"children":6022},{},[6023,6034],{"type":21,"tag":206,"props":6024,"children":6027},{"href":6025,"rel":6026},"https://www.postgresql.org/docs/14/sql-unlisten.html",[210],[6028],{"type":21,"tag":43,"props":6029,"children":6031},{"className":6030},[],[6032],{"type":26,"value":6033},"UNLISTEN",{"type":26,"value":6035}," - unsubscribe from a channel",{"type":21,"tag":22,"props":6037,"children":6038},{},[6039,6041,6046],{"type":26,"value":6040},"Let's look at examples with ",{"type":21,"tag":43,"props":6042,"children":6044},{"className":6043},[],[6045],{"type":26,"value":5783},{"type":26,"value":434},{"type":21,"tag":595,"props":6048,"children":6050},{"className":5632,"code":6049,"language":5634,"meta":8,"style":8},"demo=# listen foo;\nLISTEN\ndemo=# notify foo, 'Hello, world';\nNOTIFY\nAsynchronous notification \"foo\" with payload \"Hello, world\" received from server process with PID 60807.\n",[6051],{"type":21,"tag":43,"props":6052,"children":6053},{"__ignoreMap":8},[6054,6062,6070,6078,6086],{"type":21,"tag":399,"props":6055,"children":6056},{"class":605,"line":606},[6057],{"type":21,"tag":399,"props":6058,"children":6059},{},[6060],{"type":26,"value":6061},"demo=# listen foo;\n",{"type":21,"tag":399,"props":6063,"children":6064},{"class":605,"line":239},[6065],{"type":21,"tag":399,"props":6066,"children":6067},{},[6068],{"type":26,"value":6069},"LISTEN\n",{"type":21,"tag":399,"props":6071,"children":6072},{"class":605,"line":232},[6073],{"type":21,"tag":399,"props":6074,"children":6075},{},[6076],{"type":26,"value":6077},"demo=# notify foo, 'Hello, world';\n",{"type":21,"tag":399,"props":6079,"children":6080},{"class":605,"line":654},[6081],{"type":21,"tag":399,"props":6082,"children":6083},{},[6084],{"type":26,"value":6085},"NOTIFY\n",{"type":21,"tag":399,"props":6087,"children":6088},{"class":605,"line":698},[6089],{"type":21,"tag":399,"props":6090,"children":6091},{},[6092],{"type":26,"value":6093},"Asynchronous notification \"foo\" with payload \"Hello, world\" received from server process with PID 60807.\n",{"type":21,"tag":22,"props":6095,"children":6096},{},[6097,6099,6104,6106,6112],{"type":26,"value":6098},"The ",{"type":21,"tag":43,"props":6100,"children":6102},{"className":6101},[],[6103],{"type":26,"value":6001},{"type":26,"value":6105}," command expects constants for both the channel name (a legal SQL identifier) and the text notification (type ",{"type":21,"tag":43,"props":6107,"children":6109},{"className":6108},[],[6110],{"type":26,"value":6111},"TEXT",{"type":26,"value":6113},").  You cannot use an expression:",{"type":21,"tag":595,"props":6115,"children":6117},{"className":5632,"code":6116,"language":5634,"meta":8,"style":8},"demo=# notify foo, 'This will ' || 'not work.';\nERROR:  syntax error at or near \"||\"\nLINE 1: notify foo, 'This will ' || 'not work.';\n",[6118],{"type":21,"tag":43,"props":6119,"children":6120},{"__ignoreMap":8},[6121,6129,6137],{"type":21,"tag":399,"props":6122,"children":6123},{"class":605,"line":606},[6124],{"type":21,"tag":399,"props":6125,"children":6126},{},[6127],{"type":26,"value":6128},"demo=# notify foo, 'This will ' || 'not work.';\n",{"type":21,"tag":399,"props":6130,"children":6131},{"class":605,"line":239},[6132],{"type":21,"tag":399,"props":6133,"children":6134},{},[6135],{"type":26,"value":6136},"ERROR:  syntax error at or near \"||\"\n",{"type":21,"tag":399,"props":6138,"children":6139},{"class":605,"line":232},[6140],{"type":21,"tag":399,"props":6141,"children":6142},{},[6143],{"type":26,"value":6144},"LINE 1: notify foo, 'This will ' || 'not work.';\n",{"type":21,"tag":22,"props":6146,"children":6147},{},[6148,6150,6161],{"type":26,"value":6149},"Fortunately, PostgreSQL provides the function ",{"type":21,"tag":206,"props":6151,"children":6154},{"href":6152,"rel":6153},"https://www.postgresql.org/docs/14/sql-notify.html#id-1.9.3.158.7.5",[210],[6155],{"type":21,"tag":43,"props":6156,"children":6158},{"className":6157},[],[6159],{"type":26,"value":6160},"pg_notify",{"type":26,"value":6162}," which allows dynamic generation of the notifications:",{"type":21,"tag":595,"props":6164,"children":6166},{"className":5632,"code":6165,"language":5634,"meta":8,"style":8},"demo=# select pg_notify('foo', 'This ' || 'is a ' || 'computed value!');\nAsynchronous notification \"foo\" with payload \"This is a computed value!\" received from server process with PID 60807.\n",[6167],{"type":21,"tag":43,"props":6168,"children":6169},{"__ignoreMap":8},[6170,6178],{"type":21,"tag":399,"props":6171,"children":6172},{"class":605,"line":606},[6173],{"type":21,"tag":399,"props":6174,"children":6175},{},[6176],{"type":26,"value":6177},"demo=# select pg_notify('foo', 'This ' || 'is a ' || 'computed value!');\n",{"type":21,"tag":399,"props":6179,"children":6180},{"class":605,"line":239},[6181],{"type":21,"tag":399,"props":6182,"children":6183},{},[6184],{"type":26,"value":6185},"Asynchronous notification \"foo\" with payload \"This is a computed value!\" received from server process with PID 60807.\n",{"type":21,"tag":22,"props":6187,"children":6188},{},[6189,6191,6196],{"type":26,"value":6190},"A session can \"unsubscibe\" with the ",{"type":21,"tag":43,"props":6192,"children":6194},{"className":6193},[],[6195],{"type":26,"value":6033},{"type":26,"value":6197}," command:",{"type":21,"tag":595,"props":6199,"children":6201},{"className":5632,"code":6200,"language":5634,"meta":8,"style":8},"demo=# unlisten foo;\nUNLISTEN\ndemo=# notify foo, 'Hello, world';\nNOTIFY\n",[6202],{"type":21,"tag":43,"props":6203,"children":6204},{"__ignoreMap":8},[6205,6213,6221,6228],{"type":21,"tag":399,"props":6206,"children":6207},{"class":605,"line":606},[6208],{"type":21,"tag":399,"props":6209,"children":6210},{},[6211],{"type":26,"value":6212},"demo=# unlisten foo;\n",{"type":21,"tag":399,"props":6214,"children":6215},{"class":605,"line":239},[6216],{"type":21,"tag":399,"props":6217,"children":6218},{},[6219],{"type":26,"value":6220},"UNLISTEN\n",{"type":21,"tag":399,"props":6222,"children":6223},{"class":605,"line":232},[6224],{"type":21,"tag":399,"props":6225,"children":6226},{},[6227],{"type":26,"value":6077},{"type":21,"tag":399,"props":6229,"children":6230},{"class":605,"line":654},[6231],{"type":21,"tag":399,"props":6232,"children":6233},{},[6234],{"type":26,"value":6085},{"type":21,"tag":87,"props":6236,"children":6238},{"id":6237},"notify-in-transactions",[6239,6244],{"type":21,"tag":43,"props":6240,"children":6242},{"className":6241},[],[6243],{"type":26,"value":6001},{"type":26,"value":6245}," in Transactions",{"type":21,"tag":22,"props":6247,"children":6248},{},[6249,6251,6256,6258,6263,6265,6270],{"type":26,"value":6250},"What is particularly attractive about ",{"type":21,"tag":43,"props":6252,"children":6254},{"className":6253},[],[6255],{"type":26,"value":6001},{"type":26,"value":6257}," is you can place it in a transaction and if the transaction fails, ",{"type":21,"tag":166,"props":6259,"children":6260},{},[6261],{"type":26,"value":6262},"notifications are not delivered",{"type":26,"value":6264},".  Back in ",{"type":21,"tag":43,"props":6266,"children":6268},{"className":6267},[],[6269],{"type":26,"value":5783},{"type":26,"value":434},{"type":21,"tag":595,"props":6272,"children":6274},{"className":5632,"code":6273,"language":5634,"meta":8,"style":8},"demo=# listen foo;\nLISTEN\ndemo=# begin;\nBEGIN\ndemo=*# notify foo, 'This will not be delivered if we rollback!';\nNOTIFY\ndemo=*# rollback;\nROLLBACK\ndemo=# begin;\nBEGIN\ndemo=*# notify foo, 'This will be delivered when we commit!';\nNOTIFY\ndemo=*# commit;\nCOMMIT\nAsynchronous notification \"foo\" with payload \"This will be delivered when we commit!\" received from server process with PID 60807.\n",[6275],{"type":21,"tag":43,"props":6276,"children":6277},{"__ignoreMap":8},[6278,6285,6292,6300,6308,6316,6323,6331,6339,6346,6353,6361,6368,6376,6384],{"type":21,"tag":399,"props":6279,"children":6280},{"class":605,"line":606},[6281],{"type":21,"tag":399,"props":6282,"children":6283},{},[6284],{"type":26,"value":6061},{"type":21,"tag":399,"props":6286,"children":6287},{"class":605,"line":239},[6288],{"type":21,"tag":399,"props":6289,"children":6290},{},[6291],{"type":26,"value":6069},{"type":21,"tag":399,"props":6293,"children":6294},{"class":605,"line":232},[6295],{"type":21,"tag":399,"props":6296,"children":6297},{},[6298],{"type":26,"value":6299},"demo=# begin;\n",{"type":21,"tag":399,"props":6301,"children":6302},{"class":605,"line":654},[6303],{"type":21,"tag":399,"props":6304,"children":6305},{},[6306],{"type":26,"value":6307},"BEGIN\n",{"type":21,"tag":399,"props":6309,"children":6310},{"class":605,"line":698},[6311],{"type":21,"tag":399,"props":6312,"children":6313},{},[6314],{"type":26,"value":6315},"demo=*# notify foo, 'This will not be delivered if we rollback!';\n",{"type":21,"tag":399,"props":6317,"children":6318},{"class":605,"line":737},[6319],{"type":21,"tag":399,"props":6320,"children":6321},{},[6322],{"type":26,"value":6085},{"type":21,"tag":399,"props":6324,"children":6325},{"class":605,"line":755},[6326],{"type":21,"tag":399,"props":6327,"children":6328},{},[6329],{"type":26,"value":6330},"demo=*# rollback;\n",{"type":21,"tag":399,"props":6332,"children":6333},{"class":605,"line":773},[6334],{"type":21,"tag":399,"props":6335,"children":6336},{},[6337],{"type":26,"value":6338},"ROLLBACK\n",{"type":21,"tag":399,"props":6340,"children":6341},{"class":605,"line":795},[6342],{"type":21,"tag":399,"props":6343,"children":6344},{},[6345],{"type":26,"value":6299},{"type":21,"tag":399,"props":6347,"children":6348},{"class":605,"line":804},[6349],{"type":21,"tag":399,"props":6350,"children":6351},{},[6352],{"type":26,"value":6307},{"type":21,"tag":399,"props":6354,"children":6355},{"class":605,"line":843},[6356],{"type":21,"tag":399,"props":6357,"children":6358},{},[6359],{"type":26,"value":6360},"demo=*# notify foo, 'This will be delivered when we commit!';\n",{"type":21,"tag":399,"props":6362,"children":6363},{"class":605,"line":882},[6364],{"type":21,"tag":399,"props":6365,"children":6366},{},[6367],{"type":26,"value":6085},{"type":21,"tag":399,"props":6369,"children":6370},{"class":605,"line":900},[6371],{"type":21,"tag":399,"props":6372,"children":6373},{},[6374],{"type":26,"value":6375},"demo=*# commit;\n",{"type":21,"tag":399,"props":6377,"children":6378},{"class":605,"line":923},[6379],{"type":21,"tag":399,"props":6380,"children":6381},{},[6382],{"type":26,"value":6383},"COMMIT\n",{"type":21,"tag":399,"props":6385,"children":6386},{"class":605,"line":969},[6387],{"type":21,"tag":399,"props":6388,"children":6389},{},[6390],{"type":26,"value":6391},"Asynchronous notification \"foo\" with payload \"This will be delivered when we commit!\" received from server process with PID 60807.\n",{"type":21,"tag":22,"props":6393,"children":6394},{},[6395],{"type":26,"value":6396},"This means we can write trigger functions that send notifications on events, making our notifications data-driven.",{"type":21,"tag":306,"props":6398,"children":6399},{},[6400],{"type":21,"tag":22,"props":6401,"children":6402},{},[6403,6405,6410,6412,6417],{"type":26,"value":6404},"NOTE: Compare and contrast with what we would need to do if we used redis for pub/sub.  After committing out postgres transaction we would have to make a separate call to redis.  This separation of data ",{"type":21,"tag":166,"props":6406,"children":6407},{},[6408],{"type":26,"value":6409},"event",{"type":26,"value":6411}," from event ",{"type":21,"tag":166,"props":6413,"children":6414},{},[6415],{"type":26,"value":6416},"notification",{"type":26,"value":6418}," would be a weak link in the system.",{"type":21,"tag":22,"props":6420,"children":6421},{},[6422,6424,6429,6431,6437,6439,6445,6447,6453],{"type":26,"value":6423},"If you look at ",{"type":21,"tag":43,"props":6425,"children":6427},{"className":6426},[],[6428],{"type":26,"value":5684},{"type":26,"value":6430}," you will see a table, ",{"type":21,"tag":43,"props":6432,"children":6434},{"className":6433},[],[6435],{"type":26,"value":6436},"message",{"type":26,"value":6438},", for the permanent storage of all messages, and a trigger function, ",{"type":21,"tag":43,"props":6440,"children":6442},{"className":6441},[],[6443],{"type":26,"value":6444},"notify_message()",{"type":26,"value":6446},", that will send a notification (the JSON representation of the inserted record) on successful ",{"type":21,"tag":43,"props":6448,"children":6450},{"className":6449},[],[6451],{"type":26,"value":6452},"INSERT",{"type":26,"value":2458},{"type":21,"tag":22,"props":6455,"children":6456},{},[6457,6458,6463],{"type":26,"value":5051},{"type":21,"tag":43,"props":6459,"children":6461},{"className":6460},[],[6462],{"type":26,"value":5783},{"type":26,"value":434},{"type":21,"tag":595,"props":6465,"children":6469},{"className":6466,"code":6467,"language":6468,"meta":8,"style":8},"language-sql shiki shiki-themes github-light github-dark","LISTEN broadcast;\n","sql",[6470],{"type":21,"tag":43,"props":6471,"children":6472},{"__ignoreMap":8},[6473],{"type":21,"tag":399,"props":6474,"children":6475},{"class":605,"line":606},[6476],{"type":21,"tag":399,"props":6477,"children":6478},{"style":610},[6479],{"type":26,"value":6467},{"type":21,"tag":22,"props":6481,"children":6482},{},[6483],{"type":26,"value":6484},"User \"Abe\" delivers a message to the group on channel \"broadcast\":",{"type":21,"tag":595,"props":6486,"children":6488},{"className":6466,"code":6487,"language":6468,"meta":8,"style":8},"-- Abe saves a message:\ninsert into message (sender, channel, content)\n     select id, 'broadcast', 'Hello, everone!'\n       from appuser where fullname = 'Abe';\nINSERT 0 1\nAsynchronous notification \"broadcast\" with payload \"{\"id\":2,\"sender\":1,\"channel\":\"broadcast\",\"content\":\"Hello, everone!\"}\" received from server process with PID 61006.\n",[6489],{"type":21,"tag":43,"props":6490,"children":6491},{"__ignoreMap":8},[6492,6500,6518,6545,6582,6599],{"type":21,"tag":399,"props":6493,"children":6494},{"class":605,"line":606},[6495],{"type":21,"tag":399,"props":6496,"children":6497},{"style":2818},[6498],{"type":26,"value":6499},"-- Abe saves a message:\n",{"type":21,"tag":399,"props":6501,"children":6502},{"class":605,"line":239},[6503,6508,6513],{"type":21,"tag":399,"props":6504,"children":6505},{"style":616},[6506],{"type":26,"value":6507},"insert into",{"type":21,"tag":399,"props":6509,"children":6510},{"style":616},[6511],{"type":26,"value":6512}," message",{"type":21,"tag":399,"props":6514,"children":6515},{"style":610},[6516],{"type":26,"value":6517}," (sender, channel, content)\n",{"type":21,"tag":399,"props":6519,"children":6520},{"class":605,"line":232},[6521,6526,6531,6536,6540],{"type":21,"tag":399,"props":6522,"children":6523},{"style":616},[6524],{"type":26,"value":6525},"     select",{"type":21,"tag":399,"props":6527,"children":6528},{"style":610},[6529],{"type":26,"value":6530}," id, ",{"type":21,"tag":399,"props":6532,"children":6533},{"style":644},[6534],{"type":26,"value":6535},"'broadcast'",{"type":21,"tag":399,"props":6537,"children":6538},{"style":610},[6539],{"type":26,"value":685},{"type":21,"tag":399,"props":6541,"children":6542},{"style":644},[6543],{"type":26,"value":6544},"'Hello, everone!'\n",{"type":21,"tag":399,"props":6546,"children":6547},{"class":605,"line":654},[6548,6553,6558,6563,6568,6572,6577],{"type":21,"tag":399,"props":6549,"children":6550},{"style":616},[6551],{"type":26,"value":6552},"       from",{"type":21,"tag":399,"props":6554,"children":6555},{"style":610},[6556],{"type":26,"value":6557}," appuser ",{"type":21,"tag":399,"props":6559,"children":6560},{"style":616},[6561],{"type":26,"value":6562},"where",{"type":21,"tag":399,"props":6564,"children":6565},{"style":610},[6566],{"type":26,"value":6567}," fullname ",{"type":21,"tag":399,"props":6569,"children":6570},{"style":616},[6571],{"type":26,"value":619},{"type":21,"tag":399,"props":6573,"children":6574},{"style":644},[6575],{"type":26,"value":6576}," 'Abe'",{"type":21,"tag":399,"props":6578,"children":6579},{"style":610},[6580],{"type":26,"value":6581},";\n",{"type":21,"tag":399,"props":6583,"children":6584},{"class":605,"line":698},[6585,6589,6594],{"type":21,"tag":399,"props":6586,"children":6587},{"style":616},[6588],{"type":26,"value":6452},{"type":21,"tag":399,"props":6590,"children":6591},{"style":630},[6592],{"type":26,"value":6593}," 0",{"type":21,"tag":399,"props":6595,"children":6596},{"style":630},[6597],{"type":26,"value":6598}," 1\n",{"type":21,"tag":399,"props":6600,"children":6601},{"class":605,"line":737},[6602,6607,6611,6616,6621,6626,6631,6635,6640,6645,6650,6655,6660,6665,6670,6674,6678,6683,6688,6693,6697,6702,6707,6712,6717,6722],{"type":21,"tag":399,"props":6603,"children":6604},{"style":610},[6605],{"type":26,"value":6606},"Asynchronous ",{"type":21,"tag":399,"props":6608,"children":6609},{"style":616},[6610],{"type":26,"value":6416},{"type":21,"tag":399,"props":6612,"children":6613},{"style":644},[6614],{"type":26,"value":6615}," \"broadcast\"",{"type":21,"tag":399,"props":6617,"children":6618},{"style":616},[6619],{"type":26,"value":6620}," with",{"type":21,"tag":399,"props":6622,"children":6623},{"style":610},[6624],{"type":26,"value":6625}," payload ",{"type":21,"tag":399,"props":6627,"children":6628},{"style":644},[6629],{"type":26,"value":6630},"\"{\"",{"type":21,"tag":399,"props":6632,"children":6633},{"style":610},[6634],{"type":26,"value":2861},{"type":21,"tag":399,"props":6636,"children":6637},{"style":644},[6638],{"type":26,"value":6639},"\":2,\"",{"type":21,"tag":399,"props":6641,"children":6642},{"style":610},[6643],{"type":26,"value":6644},"sender",{"type":21,"tag":399,"props":6646,"children":6647},{"style":644},[6648],{"type":26,"value":6649},"\":1,\"",{"type":21,"tag":399,"props":6651,"children":6652},{"style":610},[6653],{"type":26,"value":6654},"channel",{"type":21,"tag":399,"props":6656,"children":6657},{"style":644},[6658],{"type":26,"value":6659},"\":\"",{"type":21,"tag":399,"props":6661,"children":6662},{"style":610},[6663],{"type":26,"value":6664},"broadcast",{"type":21,"tag":399,"props":6666,"children":6667},{"style":644},[6668],{"type":26,"value":6669},"\",\"",{"type":21,"tag":399,"props":6671,"children":6672},{"style":610},[6673],{"type":26,"value":242},{"type":21,"tag":399,"props":6675,"children":6676},{"style":644},[6677],{"type":26,"value":6659},{"type":21,"tag":399,"props":6679,"children":6680},{"style":610},[6681],{"type":26,"value":6682},"Hello, everone!",{"type":21,"tag":399,"props":6684,"children":6685},{"style":644},[6686],{"type":26,"value":6687},"\"}\"",{"type":21,"tag":399,"props":6689,"children":6690},{"style":610},[6691],{"type":26,"value":6692}," received ",{"type":21,"tag":399,"props":6694,"children":6695},{"style":616},[6696],{"type":26,"value":2669},{"type":21,"tag":399,"props":6698,"children":6699},{"style":616},[6700],{"type":26,"value":6701}," server",{"type":21,"tag":399,"props":6703,"children":6704},{"style":610},[6705],{"type":26,"value":6706}," process ",{"type":21,"tag":399,"props":6708,"children":6709},{"style":616},[6710],{"type":26,"value":6711},"with",{"type":21,"tag":399,"props":6713,"children":6714},{"style":610},[6715],{"type":26,"value":6716}," PID ",{"type":21,"tag":399,"props":6718,"children":6719},{"style":630},[6720],{"type":26,"value":6721},"61006",{"type":21,"tag":399,"props":6723,"children":6724},{"style":610},[6725],{"type":26,"value":6726},".\n",{"type":21,"tag":22,"props":6728,"children":6729},{},[6730],{"type":26,"value":6731},"We're also protected from false-notifications (which we would be susceptible to if we managed notifcations in application logic, i.e., first complete the transaction, followed by manually sending notifications).  Let's say \"Bob\" sends a message as part of a large transaction that fails:",{"type":21,"tag":595,"props":6733,"children":6735},{"className":5632,"code":6734,"language":5634,"meta":8,"style":8},"demo=# begin;\nBEGIN\ndemo=*# -- insert lots of work in the transaction here\ndemo=*#\ndemo=*# -- Bob saves a message:\ndemo=*# insert into message (sender, channel, content)\ndemo-*>      select id, 'broadcast', 'I just completed the...'\ndemo-*>        from appuser where fullname = 'Bob';\nINSERT 0 1\ndemo=*#\ndemo=*# -- insert lots more work in the transaction here that fails\ndemo=*# rollback;\nROLLBACK\n",[6736],{"type":21,"tag":43,"props":6737,"children":6738},{"__ignoreMap":8},[6739,6746,6753,6761,6769,6777,6785,6793,6801,6809,6816,6824,6831],{"type":21,"tag":399,"props":6740,"children":6741},{"class":605,"line":606},[6742],{"type":21,"tag":399,"props":6743,"children":6744},{},[6745],{"type":26,"value":6299},{"type":21,"tag":399,"props":6747,"children":6748},{"class":605,"line":239},[6749],{"type":21,"tag":399,"props":6750,"children":6751},{},[6752],{"type":26,"value":6307},{"type":21,"tag":399,"props":6754,"children":6755},{"class":605,"line":232},[6756],{"type":21,"tag":399,"props":6757,"children":6758},{},[6759],{"type":26,"value":6760},"demo=*# -- insert lots of work in the transaction here\n",{"type":21,"tag":399,"props":6762,"children":6763},{"class":605,"line":654},[6764],{"type":21,"tag":399,"props":6765,"children":6766},{},[6767],{"type":26,"value":6768},"demo=*#\n",{"type":21,"tag":399,"props":6770,"children":6771},{"class":605,"line":698},[6772],{"type":21,"tag":399,"props":6773,"children":6774},{},[6775],{"type":26,"value":6776},"demo=*# -- Bob saves a message:\n",{"type":21,"tag":399,"props":6778,"children":6779},{"class":605,"line":737},[6780],{"type":21,"tag":399,"props":6781,"children":6782},{},[6783],{"type":26,"value":6784},"demo=*# insert into message (sender, channel, content)\n",{"type":21,"tag":399,"props":6786,"children":6787},{"class":605,"line":755},[6788],{"type":21,"tag":399,"props":6789,"children":6790},{},[6791],{"type":26,"value":6792},"demo-*>      select id, 'broadcast', 'I just completed the...'\n",{"type":21,"tag":399,"props":6794,"children":6795},{"class":605,"line":773},[6796],{"type":21,"tag":399,"props":6797,"children":6798},{},[6799],{"type":26,"value":6800},"demo-*>        from appuser where fullname = 'Bob';\n",{"type":21,"tag":399,"props":6802,"children":6803},{"class":605,"line":795},[6804],{"type":21,"tag":399,"props":6805,"children":6806},{},[6807],{"type":26,"value":6808},"INSERT 0 1\n",{"type":21,"tag":399,"props":6810,"children":6811},{"class":605,"line":804},[6812],{"type":21,"tag":399,"props":6813,"children":6814},{},[6815],{"type":26,"value":6768},{"type":21,"tag":399,"props":6817,"children":6818},{"class":605,"line":843},[6819],{"type":21,"tag":399,"props":6820,"children":6821},{},[6822],{"type":26,"value":6823},"demo=*# -- insert lots more work in the transaction here that fails\n",{"type":21,"tag":399,"props":6825,"children":6826},{"class":605,"line":882},[6827],{"type":21,"tag":399,"props":6828,"children":6829},{},[6830],{"type":26,"value":6330},{"type":21,"tag":399,"props":6832,"children":6833},{"class":605,"line":900},[6834],{"type":21,"tag":399,"props":6835,"children":6836},{},[6837],{"type":26,"value":6338},{"type":21,"tag":22,"props":6839,"children":6840},{},[6841],{"type":26,"value":6842},"No notification delivered!",{"type":21,"tag":190,"props":6844,"children":6846},{"id":6845},"in-python",[6847],{"type":26,"value":6848},"In Python",{"type":21,"tag":22,"props":6850,"children":6851},{},[6852,6854,6859,6861,6868],{"type":26,"value":6853},"As noted above, our architecture uses ",{"type":21,"tag":43,"props":6855,"children":6857},{"className":6856},[],[6858],{"type":26,"value":5422},{"type":26,"value":6860}," which has support for ",{"type":21,"tag":206,"props":6862,"children":6865},{"href":6863,"rel":6864},"https://magicstack.github.io/asyncpg/current/api/index.html#asyncpg.connection.Connection.add_listener",[210],[6866],{"type":26,"value":6867},"registering callbacks",{"type":26,"value":6869}," on notifications.",{"type":21,"tag":22,"props":6871,"children":6872},{},[6873,6875,6880],{"type":26,"value":6874},"First we must create a virtualenv to install ",{"type":21,"tag":43,"props":6876,"children":6878},{"className":6877},[],[6879],{"type":26,"value":5422},{"type":26,"value":2458},{"type":21,"tag":595,"props":6882,"children":6884},{"className":5632,"code":6883,"language":5634,"meta":8,"style":8},"$ python3.11 -m venv venv\n$ source venv/bin/activate\n(venv) $ pip install asyncpg\n",[6885],{"type":21,"tag":43,"props":6886,"children":6887},{"__ignoreMap":8},[6888,6896,6904],{"type":21,"tag":399,"props":6889,"children":6890},{"class":605,"line":606},[6891],{"type":21,"tag":399,"props":6892,"children":6893},{},[6894],{"type":26,"value":6895},"$ python3.11 -m venv venv\n",{"type":21,"tag":399,"props":6897,"children":6898},{"class":605,"line":239},[6899],{"type":21,"tag":399,"props":6900,"children":6901},{},[6902],{"type":26,"value":6903},"$ source venv/bin/activate\n",{"type":21,"tag":399,"props":6905,"children":6906},{"class":605,"line":232},[6907],{"type":21,"tag":399,"props":6908,"children":6909},{},[6910],{"type":26,"value":6911},"(venv) $ pip install asyncpg\n",{"type":21,"tag":87,"props":6913,"children":6915},{"id":6914},"simple-demo",[6916],{"type":26,"value":6917},"Simple Demo",{"type":21,"tag":22,"props":6919,"children":6920},{},[6921,6923,6928,6930,6935,6937,6943],{"type":26,"value":6922},"The python script, ",{"type":21,"tag":43,"props":6924,"children":6926},{"className":6925},[],[6927],{"type":26,"value":5692},{"type":26,"value":6929},", establishes a connection to PostgreSQL using the ",{"type":21,"tag":43,"props":6931,"children":6933},{"className":6932},[],[6934],{"type":26,"value":5422},{"type":26,"value":6936}," package and adds a listener for notifications on channel ",{"type":21,"tag":43,"props":6938,"children":6940},{"className":6939},[],[6941],{"type":26,"value":6942},"\"broadcast\"",{"type":26,"value":6944},".  It then simulates a server waiting on requests by sleeping.",{"type":21,"tag":595,"props":6946,"children":6948},{"className":5632,"code":6947,"language":5634,"meta":8,"style":8},"(venv) $ python demo.py\nType Ctrl-c to exit\nMain task Waiting for notifications...\n",[6949],{"type":21,"tag":43,"props":6950,"children":6951},{"__ignoreMap":8},[6952,6960,6968],{"type":21,"tag":399,"props":6953,"children":6954},{"class":605,"line":606},[6955],{"type":21,"tag":399,"props":6956,"children":6957},{},[6958],{"type":26,"value":6959},"(venv) $ python demo.py\n",{"type":21,"tag":399,"props":6961,"children":6962},{"class":605,"line":239},[6963],{"type":21,"tag":399,"props":6964,"children":6965},{},[6966],{"type":26,"value":6967},"Type Ctrl-c to exit\n",{"type":21,"tag":399,"props":6969,"children":6970},{"class":605,"line":232},[6971],{"type":21,"tag":399,"props":6972,"children":6973},{},[6974],{"type":26,"value":6975},"Main task Waiting for notifications...\n",{"type":21,"tag":22,"props":6977,"children":6978},{},[6979,6981,6986,6988,6993,6995,7000],{"type":26,"value":6980},"With the application running in one terminal, run ",{"type":21,"tag":43,"props":6982,"children":6984},{"className":6983},[],[6985],{"type":26,"value":5783},{"type":26,"value":6987}," in another.  In that session, when an ",{"type":21,"tag":43,"props":6989,"children":6991},{"className":6990},[],[6992],{"type":26,"value":6452},{"type":26,"value":6994}," on table ",{"type":21,"tag":43,"props":6996,"children":6998},{"className":6997},[],[6999],{"type":26,"value":6436},{"type":26,"value":7001}," is committed, we will see our python application report it:",{"type":21,"tag":595,"props":7003,"children":7005},{"className":6466,"code":7004,"language":6468,"meta":8,"style":8},"insert into message (sender, channel, content)\n     select id, 'broadcast', 'Hello, demo application!'\n       from appuser where fullname = 'Abe';\n",[7006],{"type":21,"tag":43,"props":7007,"children":7008},{"__ignoreMap":8},[7009,7024,7048],{"type":21,"tag":399,"props":7010,"children":7011},{"class":605,"line":606},[7012,7016,7020],{"type":21,"tag":399,"props":7013,"children":7014},{"style":616},[7015],{"type":26,"value":6507},{"type":21,"tag":399,"props":7017,"children":7018},{"style":616},[7019],{"type":26,"value":6512},{"type":21,"tag":399,"props":7021,"children":7022},{"style":610},[7023],{"type":26,"value":6517},{"type":21,"tag":399,"props":7025,"children":7026},{"class":605,"line":239},[7027,7031,7035,7039,7043],{"type":21,"tag":399,"props":7028,"children":7029},{"style":616},[7030],{"type":26,"value":6525},{"type":21,"tag":399,"props":7032,"children":7033},{"style":610},[7034],{"type":26,"value":6530},{"type":21,"tag":399,"props":7036,"children":7037},{"style":644},[7038],{"type":26,"value":6535},{"type":21,"tag":399,"props":7040,"children":7041},{"style":610},[7042],{"type":26,"value":685},{"type":21,"tag":399,"props":7044,"children":7045},{"style":644},[7046],{"type":26,"value":7047},"'Hello, demo application!'\n",{"type":21,"tag":399,"props":7049,"children":7050},{"class":605,"line":232},[7051,7055,7059,7063,7067,7071,7075],{"type":21,"tag":399,"props":7052,"children":7053},{"style":616},[7054],{"type":26,"value":6552},{"type":21,"tag":399,"props":7056,"children":7057},{"style":610},[7058],{"type":26,"value":6557},{"type":21,"tag":399,"props":7060,"children":7061},{"style":616},[7062],{"type":26,"value":6562},{"type":21,"tag":399,"props":7064,"children":7065},{"style":610},[7066],{"type":26,"value":6567},{"type":21,"tag":399,"props":7068,"children":7069},{"style":616},[7070],{"type":26,"value":619},{"type":21,"tag":399,"props":7072,"children":7073},{"style":644},[7074],{"type":26,"value":6576},{"type":21,"tag":399,"props":7076,"children":7077},{"style":610},[7078],{"type":26,"value":6581},{"type":21,"tag":22,"props":7080,"children":7081},{},[7082],{"type":26,"value":7083},"In the other terminal we will see:",{"type":21,"tag":595,"props":7085,"children":7087},{"className":5632,"code":7086,"language":5634,"meta":8,"style":8},"RECEIVED notification from \u003Casyncpg.connection.Connection object at 0x7f23f93b7920>[pid: 202] on channel broadcast:\n{\n    \"id\": 9,\n    \"sender\": 1,\n    \"channel\": \"broadcast\",\n    \"content\": \"Hello, demo application!\"\n}\n------------------------------------------------------------------------------\n",[7088],{"type":21,"tag":43,"props":7089,"children":7090},{"__ignoreMap":8},[7091,7099,7106,7114,7122,7130,7138,7146],{"type":21,"tag":399,"props":7092,"children":7093},{"class":605,"line":606},[7094],{"type":21,"tag":399,"props":7095,"children":7096},{},[7097],{"type":26,"value":7098},"RECEIVED notification from \u003Casyncpg.connection.Connection object at 0x7f23f93b7920>[pid: 202] on channel broadcast:\n",{"type":21,"tag":399,"props":7100,"children":7101},{"class":605,"line":239},[7102],{"type":21,"tag":399,"props":7103,"children":7104},{},[7105],{"type":26,"value":897},{"type":21,"tag":399,"props":7107,"children":7108},{"class":605,"line":232},[7109],{"type":21,"tag":399,"props":7110,"children":7111},{},[7112],{"type":26,"value":7113},"    \"id\": 9,\n",{"type":21,"tag":399,"props":7115,"children":7116},{"class":605,"line":654},[7117],{"type":21,"tag":399,"props":7118,"children":7119},{},[7120],{"type":26,"value":7121},"    \"sender\": 1,\n",{"type":21,"tag":399,"props":7123,"children":7124},{"class":605,"line":698},[7125],{"type":21,"tag":399,"props":7126,"children":7127},{},[7128],{"type":26,"value":7129},"    \"channel\": \"broadcast\",\n",{"type":21,"tag":399,"props":7131,"children":7132},{"class":605,"line":737},[7133],{"type":21,"tag":399,"props":7134,"children":7135},{},[7136],{"type":26,"value":7137},"    \"content\": \"Hello, demo application!\"\n",{"type":21,"tag":399,"props":7139,"children":7140},{"class":605,"line":755},[7141],{"type":21,"tag":399,"props":7142,"children":7143},{},[7144],{"type":26,"value":7145},"}\n",{"type":21,"tag":399,"props":7147,"children":7148},{"class":605,"line":773},[7149],{"type":21,"tag":399,"props":7150,"children":7151},{},[7152],{"type":26,"value":7153},"------------------------------------------------------------------------------\n",{"type":21,"tag":190,"props":7155,"children":7157},{"id":7156},"summary",[7158],{"type":26,"value":7159},"Summary",{"type":21,"tag":22,"props":7161,"children":7162},{},[7163,7165,7170,7171,7176,7177,7182],{"type":26,"value":7164},"For applications already using PostgreSQL you can use ",{"type":21,"tag":43,"props":7166,"children":7168},{"className":7167},[],[7169],{"type":26,"value":6001},{"type":26,"value":685},{"type":21,"tag":43,"props":7172,"children":7174},{"className":7173},[],[7175],{"type":26,"value":6017},{"type":26,"value":2870},{"type":21,"tag":43,"props":7178,"children":7180},{"className":7179},[],[7181],{"type":26,"value":6033},{"type":26,"value":7183}," to manage pub/sub for your application without having to install redis solely to add pub/sub infrastructure, allowing you to reduce the footprint of your application suite.",{"type":21,"tag":22,"props":7185,"children":7186},{},[7187,7189,7194],{"type":26,"value":7188},"By placing ",{"type":21,"tag":43,"props":7190,"children":7192},{"className":7191},[],[7193],{"type":26,"value":6001},{"type":26,"value":7195}," in transactions, and more specifically, triggers, you can make your notifications data-driven.",{"type":21,"tag":22,"props":7197,"children":7198},{},[7199,7201,7206],{"type":26,"value":7200},"For asynchronous applications using ",{"type":21,"tag":43,"props":7202,"children":7204},{"className":7203},[],[7205],{"type":26,"value":5422},{"type":26,"value":7207},", an API exists for registering callbacks which will be run asynchronously as notifications are delivered.",{"type":21,"tag":306,"props":7209,"children":7210},{},[7211],{"type":21,"tag":22,"props":7212,"children":7213},{},[7214,7216,7227,7229,7236],{"type":26,"value":7215},"NOTE, not shown here: if you're using synchronous python with ",{"type":21,"tag":206,"props":7217,"children":7220},{"href":7218,"rel":7219},"https://www.psycopg.org/docs/",[210],[7221],{"type":21,"tag":43,"props":7222,"children":7224},{"className":7223},[],[7225],{"type":26,"value":7226},"psycopg",{"type":26,"value":7228},", the library has support for ",{"type":21,"tag":206,"props":7230,"children":7233},{"href":7231,"rel":7232},"https://www.psycopg.org/docs/advanced.html#asynchronous-notifications",[210],[7234],{"type":26,"value":7235},"receiving notifications",{"type":26,"value":2458},{"type":21,"tag":2495,"props":7238,"children":7239},{},[7240],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":7242},[7243,7245,7246,7247,7251,7254],{"id":5476,"depth":239,"text":7244},"Live Updates",{"id":5574,"depth":239,"text":5577},{"id":5612,"depth":239,"text":5615},{"id":5976,"depth":239,"text":5979,"children":7248},[7249],{"id":6237,"depth":232,"text":7250},"NOTIFY in Transactions",{"id":6845,"depth":239,"text":6848,"children":7252},[7253],{"id":6914,"depth":232,"text":6917},{"id":7156,"depth":239,"text":7159},"content:dpopowich:2023-8:postgres-pubsub.md","dpopowich/2023-8/postgres-pubsub.md","dpopowich/2023-8/postgres-pubsub",{"user":7259,"name":7260},"dpopowich","Daniel Popowich",{"_path":7262,"_dir":7263,"_draft":7,"_partial":7,"_locale":8,"title":7264,"description":7265,"publishDate":7263,"tags":7266,"excerpt":7268,"body":7269,"_type":240,"_id":8389,"_source":242,"_file":8390,"_stem":8391,"_extension":245,"author":8392},"/dpopowich/2021-07-30/data-collector","2021-07-30","Asynchronous Python - A Real World Example","A dive into a real example of async Python usage.",[16,7267,2797],"how-to","We have a customer that developed a hardware device to make physical measurements.  Some years ago we wrote a suite of software tools for the customer: a tablet application for configuring the hardware device, a django web server to receive uploaded XML documents generated by the device, and a user-facing web application (using the same django server), providing reporting and data analytics.",{"type":18,"children":7270,"toc":8384},[7271,7275,7279,7283,7288,7296,7339,7345,7358,7363,7418,7423,7431,7436,7472,7478,7500,7505,7610,7623,7632,7637,7977,7982,8363,8367,8372,8380],{"type":21,"tag":7272,"props":7273,"children":7274},"toc",{},[],{"type":21,"tag":190,"props":7276,"children":7277},{"id":2546},[7278],{"type":26,"value":2549},{"type":21,"tag":22,"props":7280,"children":7281},{},[7282],{"type":26,"value":7268},{"type":21,"tag":22,"props":7284,"children":7285},{},[7286],{"type":26,"value":7287},"The architecture roughly looks like this (simplified for clarity):",{"type":21,"tag":22,"props":7289,"children":7290},{},[7291],{"type":21,"tag":1681,"props":7292,"children":7295},{"alt":7293,"src":7294},"Legacy App Architecture","/dpopowich/2021-07-30/img/legacy.png",[],{"type":21,"tag":5622,"props":7297,"children":7298},{},[7299,7309,7319,7329],{"type":21,"tag":64,"props":7300,"children":7301},{},[7302,7307],{"type":21,"tag":195,"props":7303,"children":7304},{},[7305],{"type":26,"value":7306},"H/W device:",{"type":26,"value":7308}," takes data measurements, generates an XML document, and uploads the XML document to the (3) Web App via a cellular WAN.",{"type":21,"tag":64,"props":7310,"children":7311},{},[7312,7317],{"type":21,"tag":195,"props":7313,"children":7314},{},[7315],{"type":26,"value":7316},"Tablet Application:",{"type":26,"value":7318}," used in the field to configure the device, setting parameters for daily operation and authentication tokens for secure uploads.",{"type":21,"tag":64,"props":7320,"children":7321},{},[7322,7327],{"type":21,"tag":195,"props":7323,"children":7324},{},[7325],{"type":26,"value":7326},"DJANGO Web App:",{"type":26,"value":7328}," the web app receives the XML documents, parses them, and stores the data in a relational database.  It also serves requests from a client-facing web application offering reports and analytics.",{"type":21,"tag":64,"props":7330,"children":7331},{},[7332,7337],{"type":21,"tag":195,"props":7333,"children":7334},{},[7335],{"type":26,"value":7336},"Web Application:",{"type":26,"value":7338}," a modern browser web application for viewing data and analytics.",{"type":21,"tag":190,"props":7340,"children":7342},{"id":7341},"the-challenge",[7343],{"type":26,"value":7344},"The Challenge",{"type":21,"tag":22,"props":7346,"children":7347},{},[7348,7350,7357],{"type":26,"value":7349},"After several years of successful operation our customer came back to us with a new requirement: they wanted to use devices with a new mode of communication to ease the burden and costs of using a cellular network, devices based on ",{"type":21,"tag":206,"props":7351,"children":7354},{"href":7352,"rel":7353},"https://en.wikipedia.org/wiki/LoRa",[210],[7355],{"type":26,"value":7356},"LoRaWAN",{"type":26,"value":2458},{"type":21,"tag":22,"props":7359,"children":7360},{},[7361],{"type":26,"value":7362},"LoRaWAN, a low-power, wide-area network, presents several challenges:",{"type":21,"tag":5622,"props":7364,"children":7365},{},[7366,7379,7392,7405],{"type":21,"tag":64,"props":7367,"children":7368},{},[7369,7371],{"type":26,"value":7370},"It can only transmit 10s to 100s of bytes at a time.  This is too small to deliver our 2-3K XML documents, so immediately we know our messages will need to be broken up into several payloads.\n",{"type":21,"tag":60,"props":7372,"children":7373},{},[7374],{"type":21,"tag":64,"props":7375,"children":7376},{},[7377],{"type":26,"value":7378},"Our solution: if we don't send XML, but rather the data within the XML as a packed struct, we can shrink the message from several K to approximately 600 bytes, reducing greatly the number of payloads we need to deliver to complete a message.",{"type":21,"tag":64,"props":7380,"children":7381},{},[7382,7384],{"type":26,"value":7383},"When connecting, the device and LoRa gateway will calculate the bandwidth, so each message may be delivered at different data rates and therefore different payload sizes.  E.g., message m1 may be delivered in 11-byte payloads, while m2, connecting at a different time, may deliver 242-byte payloads.\n",{"type":21,"tag":60,"props":7385,"children":7386},{},[7387],{"type":21,"tag":64,"props":7388,"children":7389},{},[7390],{"type":26,"value":7391},"Our solution: if each payload is given a header we can identify which message each payload belongs to.  The first payload can include an expected payload count for the complete message. (Metadata delivered by the LoRa gateway identifies the device, so which device is sending the message we get for \"free\".)",{"type":21,"tag":64,"props":7393,"children":7394},{},[7395,7397],{"type":26,"value":7396},"Payloads may arrive out-of-order.\n",{"type":21,"tag":60,"props":7398,"children":7399},{},[7400],{"type":21,"tag":64,"props":7401,"children":7402},{},[7403],{"type":26,"value":7404},"Our solution: use the header to identify the payload order, 0, 1, 2, etc.  On receiving a complete message (known based on count from payload-0) we can sort the payloads to reassemble the packed struct.",{"type":21,"tag":64,"props":7406,"children":7407},{},[7408,7410],{"type":26,"value":7409},"Individual payloads may be lost.\n",{"type":21,"tag":60,"props":7411,"children":7412},{},[7413],{"type":21,"tag":64,"props":7414,"children":7415},{},[7416],{"type":26,"value":7417},"Our solution: some data is required, some data is merely metadata not critical to a successful upload.  If we're missing a required payload (particularly payload-0) we log the incident with the message we did received, but otherwise we post the message.",{"type":21,"tag":22,"props":7419,"children":7420},{},[7421],{"type":26,"value":7422},"Putting this all together it becomes clear we need a data processing pipeline; something like this:",{"type":21,"tag":22,"props":7424,"children":7425},{},[7426],{"type":21,"tag":1681,"props":7427,"children":7430},{"alt":7428,"src":7429},"Pipeline","/dpopowich/2021-07-30/img/pipeline.png",[],{"type":21,"tag":22,"props":7432,"children":7433},{},[7434],{"type":26,"value":7435},"The components of the data-collector:",{"type":21,"tag":5622,"props":7437,"children":7438},{},[7439,7444,7462,7467],{"type":21,"tag":64,"props":7440,"children":7441},{},[7442],{"type":26,"value":7443},"An HTTP server accepting POSTs of payloads.  After validating a payload, it is stored in (2) cache.",{"type":21,"tag":64,"props":7445,"children":7446},{},[7447,7449],{"type":26,"value":7448},"The cache will store payloads for a message and will pass on the message ID to the (3) aggregator when one of two conditions are met:\n",{"type":21,"tag":5622,"props":7450,"children":7451},{},[7452,7457],{"type":21,"tag":64,"props":7453,"children":7454},{},[7455],{"type":26,"value":7456},"When it has received all payloads, based on having received the expected count of payloads for a message given by payload-0.",{"type":21,"tag":64,"props":7458,"children":7459},{},[7460],{"type":26,"value":7461},"Because payloads may be lost, we may never receive a full message, so the cache is configured with a timeout per message.  If a full message is never received the timeout will send the message, as-is, to the aggregator.",{"type":21,"tag":64,"props":7463,"children":7464},{},[7465],{"type":26,"value":7466},"The aggregator will collect all payloads, assemble them in order and, when possible, generate an XML document passing it on to (4) the Delivery Agent.",{"type":21,"tag":64,"props":7468,"children":7469},{},[7470],{"type":26,"value":7471},"The Delivery Agent, on receiving an XML document will manage a connection (and necessary authentication) with the Web App and upload the XML (via a POST call), managing retries and logging on any failure.",{"type":21,"tag":190,"props":7473,"children":7475},{"id":7474},"implementing-with-asynchronous-python",[7476],{"type":26,"value":7477},"Implementing with Asynchronous Python",{"type":21,"tag":22,"props":7479,"children":7480},{},[7481,7483,7490,7491,7498],{"type":26,"value":7482},"Modeling a pipeline in asynchronous Python is a simple matter of creating ",{"type":21,"tag":206,"props":7484,"children":7487},{"href":7485,"rel":7486},"https://docs.python.org/3.9/library/asyncio-task.html#creating-tasks",[210],[7488],{"type":26,"value":7489},"tasks",{"type":26,"value":2584},{"type":21,"tag":206,"props":7492,"children":7495},{"href":7493,"rel":7494},"https://docs.python.org/3.9/library/asyncio-queue.html#queue",[210],[7496],{"type":26,"value":7497},"queues",{"type":26,"value":7499},".  Let's look at the last two stages of the pipeline, the aggregator and delivery agent as examples.",{"type":21,"tag":22,"props":7501,"children":7502},{},[7503],{"type":26,"value":7504},"We will create two queues on application startup and two long-running tasks:",{"type":21,"tag":595,"props":7506,"children":7508},{"className":597,"code":7507,"language":16,"meta":8,"style":8},"\nimport asyncio\n\n# create queues - for simplicity we ignore sizing the queue here\naggregator_queue = asyncio.Queue()\ndelivery_queue = asyncio.Queue()\n\n# create tasks - two async functions discussed below\nasyncio.create_task(aggregator)\nasyncio.create_task(delivery_agent)\n\n",[7509],{"type":21,"tag":43,"props":7510,"children":7511},{"__ignoreMap":8},[7512,7519,7531,7538,7546,7563,7579,7586,7594,7602],{"type":21,"tag":399,"props":7513,"children":7514},{"class":605,"line":606},[7515],{"type":21,"tag":399,"props":7516,"children":7517},{"emptyLinePlaceholder":2690},[7518],{"type":26,"value":2693},{"type":21,"tag":399,"props":7520,"children":7521},{"class":605,"line":239},[7522,7526],{"type":21,"tag":399,"props":7523,"children":7524},{"style":616},[7525],{"type":26,"value":2679},{"type":21,"tag":399,"props":7527,"children":7528},{"style":610},[7529],{"type":26,"value":7530}," asyncio\n",{"type":21,"tag":399,"props":7532,"children":7533},{"class":605,"line":232},[7534],{"type":21,"tag":399,"props":7535,"children":7536},{"emptyLinePlaceholder":2690},[7537],{"type":26,"value":2693},{"type":21,"tag":399,"props":7539,"children":7540},{"class":605,"line":654},[7541],{"type":21,"tag":399,"props":7542,"children":7543},{"style":2818},[7544],{"type":26,"value":7545},"# create queues - for simplicity we ignore sizing the queue here\n",{"type":21,"tag":399,"props":7547,"children":7548},{"class":605,"line":698},[7549,7554,7558],{"type":21,"tag":399,"props":7550,"children":7551},{"style":610},[7552],{"type":26,"value":7553},"aggregator_queue ",{"type":21,"tag":399,"props":7555,"children":7556},{"style":616},[7557],{"type":26,"value":619},{"type":21,"tag":399,"props":7559,"children":7560},{"style":610},[7561],{"type":26,"value":7562}," asyncio.Queue()\n",{"type":21,"tag":399,"props":7564,"children":7565},{"class":605,"line":737},[7566,7571,7575],{"type":21,"tag":399,"props":7567,"children":7568},{"style":610},[7569],{"type":26,"value":7570},"delivery_queue ",{"type":21,"tag":399,"props":7572,"children":7573},{"style":616},[7574],{"type":26,"value":619},{"type":21,"tag":399,"props":7576,"children":7577},{"style":610},[7578],{"type":26,"value":7562},{"type":21,"tag":399,"props":7580,"children":7581},{"class":605,"line":755},[7582],{"type":21,"tag":399,"props":7583,"children":7584},{"emptyLinePlaceholder":2690},[7585],{"type":26,"value":2693},{"type":21,"tag":399,"props":7587,"children":7588},{"class":605,"line":773},[7589],{"type":21,"tag":399,"props":7590,"children":7591},{"style":2818},[7592],{"type":26,"value":7593},"# create tasks - two async functions discussed below\n",{"type":21,"tag":399,"props":7595,"children":7596},{"class":605,"line":795},[7597],{"type":21,"tag":399,"props":7598,"children":7599},{"style":610},[7600],{"type":26,"value":7601},"asyncio.create_task(aggregator)\n",{"type":21,"tag":399,"props":7603,"children":7604},{"class":605,"line":804},[7605],{"type":21,"tag":399,"props":7606,"children":7607},{"style":610},[7608],{"type":26,"value":7609},"asyncio.create_task(delivery_agent)\n",{"type":21,"tag":22,"props":7611,"children":7612},{},[7613,7615,7621],{"type":26,"value":7614},"The cache can push message IDs onto ",{"type":21,"tag":43,"props":7616,"children":7618},{"className":7617},[],[7619],{"type":26,"value":7620},"aggregator_queue",{"type":26,"value":7622}," whenever a\nmessage completes or times out:",{"type":21,"tag":595,"props":7624,"children":7627},{"className":7625,"code":7626,"language":26},[3074],"# in cache implementation...\naggregator_queue.put_nowait(message_id)\n",[7628],{"type":21,"tag":43,"props":7629,"children":7630},{"__ignoreMap":8},[7631],{"type":26,"value":7626},{"type":21,"tag":22,"props":7633,"children":7634},{},[7635],{"type":26,"value":7636},"Meanwhile, the aggregator can be implemented structurally like this:",{"type":21,"tag":595,"props":7638,"children":7640},{"className":597,"code":7639,"language":16,"meta":8,"style":8},"async def aggregator():\n   \"\"\"Aggregator: collect messages and generate XML documents.\n   \"\"\"\n   while True:\n      # wait for incoming message\n      message_id = await aggregator_queue.get()\n\n      try:\n         # fetch payloads and expected count from cache\n         payloads, count = await cache.get_message(message_id)\n\n         # generate full message from payloads\n         message = generate_message(payloads, count)\n\n         # we have a full message: generate XML\n         xml = generate_xml(message)\n\n         # pass on to the next step in the pipeline, a tuple of our\n         # message ID and generated xml\n         delivery_queue.put_nowait((message_id, xml))\n\n      except Exception as exc:\n         # we log any error that might have occurred from preceding calls\n         logger.error('Error processing message id %s: %s', message_id, exc)\n\n      finally:\n         # we tell the queue the task is complete\n         aggregator_queue.task_done()\n",[7641],{"type":21,"tag":43,"props":7642,"children":7643},{"__ignoreMap":8},[7644,7664,7672,7680,7697,7705,7726,7733,7745,7753,7774,7781,7789,7806,7813,7821,7838,7845,7853,7861,7869,7876,7899,7907,7942,7949,7961,7969],{"type":21,"tag":399,"props":7645,"children":7646},{"class":605,"line":606},[7647,7651,7655,7660],{"type":21,"tag":399,"props":7648,"children":7649},{"style":616},[7650],{"type":26,"value":2797},{"type":21,"tag":399,"props":7652,"children":7653},{"style":616},[7654],{"type":26,"value":2802},{"type":21,"tag":399,"props":7656,"children":7657},{"style":2704},[7658],{"type":26,"value":7659}," aggregator",{"type":21,"tag":399,"props":7661,"children":7662},{"style":610},[7663],{"type":26,"value":3290},{"type":21,"tag":399,"props":7665,"children":7666},{"class":605,"line":239},[7667],{"type":21,"tag":399,"props":7668,"children":7669},{"style":644},[7670],{"type":26,"value":7671},"   \"\"\"Aggregator: collect messages and generate XML documents.\n",{"type":21,"tag":399,"props":7673,"children":7674},{"class":605,"line":232},[7675],{"type":21,"tag":399,"props":7676,"children":7677},{"style":644},[7678],{"type":26,"value":7679},"   \"\"\"\n",{"type":21,"tag":399,"props":7681,"children":7682},{"class":605,"line":654},[7683,7688,7693],{"type":21,"tag":399,"props":7684,"children":7685},{"style":616},[7686],{"type":26,"value":7687},"   while",{"type":21,"tag":399,"props":7689,"children":7690},{"style":630},[7691],{"type":26,"value":7692}," True",{"type":21,"tag":399,"props":7694,"children":7695},{"style":610},[7696],{"type":26,"value":4683},{"type":21,"tag":399,"props":7698,"children":7699},{"class":605,"line":698},[7700],{"type":21,"tag":399,"props":7701,"children":7702},{"style":2818},[7703],{"type":26,"value":7704},"      # wait for incoming message\n",{"type":21,"tag":399,"props":7706,"children":7707},{"class":605,"line":737},[7708,7713,7717,7721],{"type":21,"tag":399,"props":7709,"children":7710},{"style":610},[7711],{"type":26,"value":7712},"      message_id ",{"type":21,"tag":399,"props":7714,"children":7715},{"style":616},[7716],{"type":26,"value":619},{"type":21,"tag":399,"props":7718,"children":7719},{"style":616},[7720],{"type":26,"value":3037},{"type":21,"tag":399,"props":7722,"children":7723},{"style":610},[7724],{"type":26,"value":7725}," aggregator_queue.get()\n",{"type":21,"tag":399,"props":7727,"children":7728},{"class":605,"line":755},[7729],{"type":21,"tag":399,"props":7730,"children":7731},{"emptyLinePlaceholder":2690},[7732],{"type":26,"value":2693},{"type":21,"tag":399,"props":7734,"children":7735},{"class":605,"line":773},[7736,7741],{"type":21,"tag":399,"props":7737,"children":7738},{"style":616},[7739],{"type":26,"value":7740},"      try",{"type":21,"tag":399,"props":7742,"children":7743},{"style":610},[7744],{"type":26,"value":4683},{"type":21,"tag":399,"props":7746,"children":7747},{"class":605,"line":795},[7748],{"type":21,"tag":399,"props":7749,"children":7750},{"style":2818},[7751],{"type":26,"value":7752},"         # fetch payloads and expected count from cache\n",{"type":21,"tag":399,"props":7754,"children":7755},{"class":605,"line":804},[7756,7761,7765,7769],{"type":21,"tag":399,"props":7757,"children":7758},{"style":610},[7759],{"type":26,"value":7760},"         payloads, count ",{"type":21,"tag":399,"props":7762,"children":7763},{"style":616},[7764],{"type":26,"value":619},{"type":21,"tag":399,"props":7766,"children":7767},{"style":616},[7768],{"type":26,"value":3037},{"type":21,"tag":399,"props":7770,"children":7771},{"style":610},[7772],{"type":26,"value":7773}," cache.get_message(message_id)\n",{"type":21,"tag":399,"props":7775,"children":7776},{"class":605,"line":843},[7777],{"type":21,"tag":399,"props":7778,"children":7779},{"emptyLinePlaceholder":2690},[7780],{"type":26,"value":2693},{"type":21,"tag":399,"props":7782,"children":7783},{"class":605,"line":882},[7784],{"type":21,"tag":399,"props":7785,"children":7786},{"style":2818},[7787],{"type":26,"value":7788},"         # generate full message from payloads\n",{"type":21,"tag":399,"props":7790,"children":7791},{"class":605,"line":900},[7792,7797,7801],{"type":21,"tag":399,"props":7793,"children":7794},{"style":610},[7795],{"type":26,"value":7796},"         message ",{"type":21,"tag":399,"props":7798,"children":7799},{"style":616},[7800],{"type":26,"value":619},{"type":21,"tag":399,"props":7802,"children":7803},{"style":610},[7804],{"type":26,"value":7805}," generate_message(payloads, count)\n",{"type":21,"tag":399,"props":7807,"children":7808},{"class":605,"line":923},[7809],{"type":21,"tag":399,"props":7810,"children":7811},{"emptyLinePlaceholder":2690},[7812],{"type":26,"value":2693},{"type":21,"tag":399,"props":7814,"children":7815},{"class":605,"line":969},[7816],{"type":21,"tag":399,"props":7817,"children":7818},{"style":2818},[7819],{"type":26,"value":7820},"         # we have a full message: generate XML\n",{"type":21,"tag":399,"props":7822,"children":7823},{"class":605,"line":991},[7824,7829,7833],{"type":21,"tag":399,"props":7825,"children":7826},{"style":610},[7827],{"type":26,"value":7828},"         xml ",{"type":21,"tag":399,"props":7830,"children":7831},{"style":616},[7832],{"type":26,"value":619},{"type":21,"tag":399,"props":7834,"children":7835},{"style":610},[7836],{"type":26,"value":7837}," generate_xml(message)\n",{"type":21,"tag":399,"props":7839,"children":7840},{"class":605,"line":1013},[7841],{"type":21,"tag":399,"props":7842,"children":7843},{"emptyLinePlaceholder":2690},[7844],{"type":26,"value":2693},{"type":21,"tag":399,"props":7846,"children":7847},{"class":605,"line":1035},[7848],{"type":21,"tag":399,"props":7849,"children":7850},{"style":2818},[7851],{"type":26,"value":7852},"         # pass on to the next step in the pipeline, a tuple of our\n",{"type":21,"tag":399,"props":7854,"children":7855},{"class":605,"line":1092},[7856],{"type":21,"tag":399,"props":7857,"children":7858},{"style":2818},[7859],{"type":26,"value":7860},"         # message ID and generated xml\n",{"type":21,"tag":399,"props":7862,"children":7863},{"class":605,"line":1114},[7864],{"type":21,"tag":399,"props":7865,"children":7866},{"style":610},[7867],{"type":26,"value":7868},"         delivery_queue.put_nowait((message_id, xml))\n",{"type":21,"tag":399,"props":7870,"children":7871},{"class":605,"line":1136},[7872],{"type":21,"tag":399,"props":7873,"children":7874},{"emptyLinePlaceholder":2690},[7875],{"type":26,"value":2693},{"type":21,"tag":399,"props":7877,"children":7878},{"class":605,"line":1158},[7879,7884,7889,7894],{"type":21,"tag":399,"props":7880,"children":7881},{"style":616},[7882],{"type":26,"value":7883},"      except",{"type":21,"tag":399,"props":7885,"children":7886},{"style":630},[7887],{"type":26,"value":7888}," Exception",{"type":21,"tag":399,"props":7890,"children":7891},{"style":616},[7892],{"type":26,"value":7893}," as",{"type":21,"tag":399,"props":7895,"children":7896},{"style":610},[7897],{"type":26,"value":7898}," exc:\n",{"type":21,"tag":399,"props":7900,"children":7901},{"class":605,"line":1180},[7902],{"type":21,"tag":399,"props":7903,"children":7904},{"style":2818},[7905],{"type":26,"value":7906},"         # we log any error that might have occurred from preceding calls\n",{"type":21,"tag":399,"props":7908,"children":7909},{"class":605,"line":1202},[7910,7915,7920,7925,7929,7933,7937],{"type":21,"tag":399,"props":7911,"children":7912},{"style":610},[7913],{"type":26,"value":7914},"         logger.error(",{"type":21,"tag":399,"props":7916,"children":7917},{"style":644},[7918],{"type":26,"value":7919},"'Error processing message id ",{"type":21,"tag":399,"props":7921,"children":7922},{"style":630},[7923],{"type":26,"value":7924},"%s",{"type":21,"tag":399,"props":7926,"children":7927},{"style":644},[7928],{"type":26,"value":911},{"type":21,"tag":399,"props":7930,"children":7931},{"style":630},[7932],{"type":26,"value":7924},{"type":21,"tag":399,"props":7934,"children":7935},{"style":644},[7936],{"type":26,"value":943},{"type":21,"tag":399,"props":7938,"children":7939},{"style":610},[7940],{"type":26,"value":7941},", message_id, exc)\n",{"type":21,"tag":399,"props":7943,"children":7944},{"class":605,"line":1211},[7945],{"type":21,"tag":399,"props":7946,"children":7947},{"emptyLinePlaceholder":2690},[7948],{"type":26,"value":2693},{"type":21,"tag":399,"props":7950,"children":7951},{"class":605,"line":1228},[7952,7957],{"type":21,"tag":399,"props":7953,"children":7954},{"style":616},[7955],{"type":26,"value":7956},"      finally",{"type":21,"tag":399,"props":7958,"children":7959},{"style":610},[7960],{"type":26,"value":4683},{"type":21,"tag":399,"props":7962,"children":7963},{"class":605,"line":1269},[7964],{"type":21,"tag":399,"props":7965,"children":7966},{"style":2818},[7967],{"type":26,"value":7968},"         # we tell the queue the task is complete\n",{"type":21,"tag":399,"props":7970,"children":7971},{"class":605,"line":1307},[7972],{"type":21,"tag":399,"props":7973,"children":7974},{"style":610},[7975],{"type":26,"value":7976},"         aggregator_queue.task_done()\n",{"type":21,"tag":22,"props":7978,"children":7979},{},[7980],{"type":26,"value":7981},"With a similar structure we can implement the delivery agent:",{"type":21,"tag":595,"props":7983,"children":7985},{"className":597,"code":7984,"language":16,"meta":8,"style":8},"async def delivery_agent():\n   \"\"\"Delivery agent: listen for incoming XML docs and deliver to Web App\n   \"\"\"\n   # set up initial connection/authentication here.  Let's imagine we\n   # have a factory function, `web_app()` that returns our current\n   # connection to the Web App by way of an [aiohttp\n   # ClientSession](https://docs.aiohttp.org/en/stable/client_advanced.html#client-session).\n\n   # having set up the connection management, we enter our loop:\n   while True:\n      # wait for incoming message\n      message_id, xml = await delivery_queue.get()\n\n      try:\n         # function to generate POST data for REST call\n         data = gen_post_data(xml)\n\n         # get current session from factory and upload\n         session = web_app()\n         resp = await session.post(URL, data=data)\n         # process resp here, handling non 2XX responses, perhaps\n         # running the above POST in a loop for N-attempts for\n         # recoverable errors, otherwise raising an appropriate exception.\n\n      except Exception as exc:\n         # we log any error that might have occurred from preceding calls\n         logger.error('Could not upload xml for message_id %s: %s', message_id, exc)\n         # perhaps we save the generated XML for review of the failure?\n\n      finally:\n         # we tell the queue the task is complete\n         delivery_queue.task_done()\n",[7986],{"type":21,"tag":43,"props":7987,"children":7988},{"__ignoreMap":8},[7989,8009,8017,8024,8032,8040,8048,8056,8063,8071,8086,8093,8114,8121,8132,8140,8157,8164,8172,8189,8233,8241,8249,8257,8264,8283,8290,8322,8330,8337,8348,8355],{"type":21,"tag":399,"props":7990,"children":7991},{"class":605,"line":606},[7992,7996,8000,8005],{"type":21,"tag":399,"props":7993,"children":7994},{"style":616},[7995],{"type":26,"value":2797},{"type":21,"tag":399,"props":7997,"children":7998},{"style":616},[7999],{"type":26,"value":2802},{"type":21,"tag":399,"props":8001,"children":8002},{"style":2704},[8003],{"type":26,"value":8004}," delivery_agent",{"type":21,"tag":399,"props":8006,"children":8007},{"style":610},[8008],{"type":26,"value":3290},{"type":21,"tag":399,"props":8010,"children":8011},{"class":605,"line":239},[8012],{"type":21,"tag":399,"props":8013,"children":8014},{"style":644},[8015],{"type":26,"value":8016},"   \"\"\"Delivery agent: listen for incoming XML docs and deliver to Web App\n",{"type":21,"tag":399,"props":8018,"children":8019},{"class":605,"line":232},[8020],{"type":21,"tag":399,"props":8021,"children":8022},{"style":644},[8023],{"type":26,"value":7679},{"type":21,"tag":399,"props":8025,"children":8026},{"class":605,"line":654},[8027],{"type":21,"tag":399,"props":8028,"children":8029},{"style":2818},[8030],{"type":26,"value":8031},"   # set up initial connection/authentication here.  Let's imagine we\n",{"type":21,"tag":399,"props":8033,"children":8034},{"class":605,"line":698},[8035],{"type":21,"tag":399,"props":8036,"children":8037},{"style":2818},[8038],{"type":26,"value":8039},"   # have a factory function, `web_app()` that returns our current\n",{"type":21,"tag":399,"props":8041,"children":8042},{"class":605,"line":737},[8043],{"type":21,"tag":399,"props":8044,"children":8045},{"style":2818},[8046],{"type":26,"value":8047},"   # connection to the Web App by way of an [aiohttp\n",{"type":21,"tag":399,"props":8049,"children":8050},{"class":605,"line":755},[8051],{"type":21,"tag":399,"props":8052,"children":8053},{"style":2818},[8054],{"type":26,"value":8055},"   # ClientSession](https://docs.aiohttp.org/en/stable/client_advanced.html#client-session).\n",{"type":21,"tag":399,"props":8057,"children":8058},{"class":605,"line":773},[8059],{"type":21,"tag":399,"props":8060,"children":8061},{"emptyLinePlaceholder":2690},[8062],{"type":26,"value":2693},{"type":21,"tag":399,"props":8064,"children":8065},{"class":605,"line":795},[8066],{"type":21,"tag":399,"props":8067,"children":8068},{"style":2818},[8069],{"type":26,"value":8070},"   # having set up the connection management, we enter our loop:\n",{"type":21,"tag":399,"props":8072,"children":8073},{"class":605,"line":804},[8074,8078,8082],{"type":21,"tag":399,"props":8075,"children":8076},{"style":616},[8077],{"type":26,"value":7687},{"type":21,"tag":399,"props":8079,"children":8080},{"style":630},[8081],{"type":26,"value":7692},{"type":21,"tag":399,"props":8083,"children":8084},{"style":610},[8085],{"type":26,"value":4683},{"type":21,"tag":399,"props":8087,"children":8088},{"class":605,"line":843},[8089],{"type":21,"tag":399,"props":8090,"children":8091},{"style":2818},[8092],{"type":26,"value":7704},{"type":21,"tag":399,"props":8094,"children":8095},{"class":605,"line":882},[8096,8101,8105,8109],{"type":21,"tag":399,"props":8097,"children":8098},{"style":610},[8099],{"type":26,"value":8100},"      message_id, xml ",{"type":21,"tag":399,"props":8102,"children":8103},{"style":616},[8104],{"type":26,"value":619},{"type":21,"tag":399,"props":8106,"children":8107},{"style":616},[8108],{"type":26,"value":3037},{"type":21,"tag":399,"props":8110,"children":8111},{"style":610},[8112],{"type":26,"value":8113}," delivery_queue.get()\n",{"type":21,"tag":399,"props":8115,"children":8116},{"class":605,"line":900},[8117],{"type":21,"tag":399,"props":8118,"children":8119},{"emptyLinePlaceholder":2690},[8120],{"type":26,"value":2693},{"type":21,"tag":399,"props":8122,"children":8123},{"class":605,"line":923},[8124,8128],{"type":21,"tag":399,"props":8125,"children":8126},{"style":616},[8127],{"type":26,"value":7740},{"type":21,"tag":399,"props":8129,"children":8130},{"style":610},[8131],{"type":26,"value":4683},{"type":21,"tag":399,"props":8133,"children":8134},{"class":605,"line":969},[8135],{"type":21,"tag":399,"props":8136,"children":8137},{"style":2818},[8138],{"type":26,"value":8139},"         # function to generate POST data for REST call\n",{"type":21,"tag":399,"props":8141,"children":8142},{"class":605,"line":991},[8143,8148,8152],{"type":21,"tag":399,"props":8144,"children":8145},{"style":610},[8146],{"type":26,"value":8147},"         data ",{"type":21,"tag":399,"props":8149,"children":8150},{"style":616},[8151],{"type":26,"value":619},{"type":21,"tag":399,"props":8153,"children":8154},{"style":610},[8155],{"type":26,"value":8156}," gen_post_data(xml)\n",{"type":21,"tag":399,"props":8158,"children":8159},{"class":605,"line":1013},[8160],{"type":21,"tag":399,"props":8161,"children":8162},{"emptyLinePlaceholder":2690},[8163],{"type":26,"value":2693},{"type":21,"tag":399,"props":8165,"children":8166},{"class":605,"line":1035},[8167],{"type":21,"tag":399,"props":8168,"children":8169},{"style":2818},[8170],{"type":26,"value":8171},"         # get current session from factory and upload\n",{"type":21,"tag":399,"props":8173,"children":8174},{"class":605,"line":1092},[8175,8180,8184],{"type":21,"tag":399,"props":8176,"children":8177},{"style":610},[8178],{"type":26,"value":8179},"         session ",{"type":21,"tag":399,"props":8181,"children":8182},{"style":616},[8183],{"type":26,"value":619},{"type":21,"tag":399,"props":8185,"children":8186},{"style":610},[8187],{"type":26,"value":8188}," web_app()\n",{"type":21,"tag":399,"props":8190,"children":8191},{"class":605,"line":1114},[8192,8197,8201,8205,8210,8215,8219,8224,8228],{"type":21,"tag":399,"props":8193,"children":8194},{"style":610},[8195],{"type":26,"value":8196},"         resp ",{"type":21,"tag":399,"props":8198,"children":8199},{"style":616},[8200],{"type":26,"value":619},{"type":21,"tag":399,"props":8202,"children":8203},{"style":616},[8204],{"type":26,"value":3037},{"type":21,"tag":399,"props":8206,"children":8207},{"style":610},[8208],{"type":26,"value":8209}," session.post(",{"type":21,"tag":399,"props":8211,"children":8212},{"style":630},[8213],{"type":26,"value":8214},"URL",{"type":21,"tag":399,"props":8216,"children":8217},{"style":610},[8218],{"type":26,"value":685},{"type":21,"tag":399,"props":8220,"children":8221},{"style":658},[8222],{"type":26,"value":8223},"data",{"type":21,"tag":399,"props":8225,"children":8226},{"style":616},[8227],{"type":26,"value":619},{"type":21,"tag":399,"props":8229,"children":8230},{"style":610},[8231],{"type":26,"value":8232},"data)\n",{"type":21,"tag":399,"props":8234,"children":8235},{"class":605,"line":1136},[8236],{"type":21,"tag":399,"props":8237,"children":8238},{"style":2818},[8239],{"type":26,"value":8240},"         # process resp here, handling non 2XX responses, perhaps\n",{"type":21,"tag":399,"props":8242,"children":8243},{"class":605,"line":1158},[8244],{"type":21,"tag":399,"props":8245,"children":8246},{"style":2818},[8247],{"type":26,"value":8248},"         # running the above POST in a loop for N-attempts for\n",{"type":21,"tag":399,"props":8250,"children":8251},{"class":605,"line":1180},[8252],{"type":21,"tag":399,"props":8253,"children":8254},{"style":2818},[8255],{"type":26,"value":8256},"         # recoverable errors, otherwise raising an appropriate exception.\n",{"type":21,"tag":399,"props":8258,"children":8259},{"class":605,"line":1202},[8260],{"type":21,"tag":399,"props":8261,"children":8262},{"emptyLinePlaceholder":2690},[8263],{"type":26,"value":2693},{"type":21,"tag":399,"props":8265,"children":8266},{"class":605,"line":1211},[8267,8271,8275,8279],{"type":21,"tag":399,"props":8268,"children":8269},{"style":616},[8270],{"type":26,"value":7883},{"type":21,"tag":399,"props":8272,"children":8273},{"style":630},[8274],{"type":26,"value":7888},{"type":21,"tag":399,"props":8276,"children":8277},{"style":616},[8278],{"type":26,"value":7893},{"type":21,"tag":399,"props":8280,"children":8281},{"style":610},[8282],{"type":26,"value":7898},{"type":21,"tag":399,"props":8284,"children":8285},{"class":605,"line":1228},[8286],{"type":21,"tag":399,"props":8287,"children":8288},{"style":2818},[8289],{"type":26,"value":7906},{"type":21,"tag":399,"props":8291,"children":8292},{"class":605,"line":1269},[8293,8297,8302,8306,8310,8314,8318],{"type":21,"tag":399,"props":8294,"children":8295},{"style":610},[8296],{"type":26,"value":7914},{"type":21,"tag":399,"props":8298,"children":8299},{"style":644},[8300],{"type":26,"value":8301},"'Could not upload xml for message_id ",{"type":21,"tag":399,"props":8303,"children":8304},{"style":630},[8305],{"type":26,"value":7924},{"type":21,"tag":399,"props":8307,"children":8308},{"style":644},[8309],{"type":26,"value":911},{"type":21,"tag":399,"props":8311,"children":8312},{"style":630},[8313],{"type":26,"value":7924},{"type":21,"tag":399,"props":8315,"children":8316},{"style":644},[8317],{"type":26,"value":943},{"type":21,"tag":399,"props":8319,"children":8320},{"style":610},[8321],{"type":26,"value":7941},{"type":21,"tag":399,"props":8323,"children":8324},{"class":605,"line":1307},[8325],{"type":21,"tag":399,"props":8326,"children":8327},{"style":2818},[8328],{"type":26,"value":8329},"         # perhaps we save the generated XML for review of the failure?\n",{"type":21,"tag":399,"props":8331,"children":8332},{"class":605,"line":1346},[8333],{"type":21,"tag":399,"props":8334,"children":8335},{"emptyLinePlaceholder":2690},[8336],{"type":26,"value":2693},{"type":21,"tag":399,"props":8338,"children":8339},{"class":605,"line":1355},[8340,8344],{"type":21,"tag":399,"props":8341,"children":8342},{"style":616},[8343],{"type":26,"value":7956},{"type":21,"tag":399,"props":8345,"children":8346},{"style":610},[8347],{"type":26,"value":4683},{"type":21,"tag":399,"props":8349,"children":8350},{"class":605,"line":1364},[8351],{"type":21,"tag":399,"props":8352,"children":8353},{"style":2818},[8354],{"type":26,"value":7968},{"type":21,"tag":399,"props":8356,"children":8357},{"class":605,"line":1403},[8358],{"type":21,"tag":399,"props":8359,"children":8360},{"style":610},[8361],{"type":26,"value":8362},"         delivery_queue.task_done()\n",{"type":21,"tag":8364,"props":8365,"children":8366},"hr",{},[],{"type":21,"tag":22,"props":8368,"children":8369},{},[8370],{"type":26,"value":8371},"As we complete our implementation we end up with a software architecture that mirrors our designed pipeline:",{"type":21,"tag":22,"props":8373,"children":8374},{},[8375],{"type":21,"tag":1681,"props":8376,"children":8379},{"alt":8377,"src":8378},"Data Collector Architecture","/dpopowich/2021-07-30/img/data-collector.png",[],{"type":21,"tag":2495,"props":8381,"children":8382},{},[8383],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":8385},[8386,8387,8388],{"id":2546,"depth":239,"text":2549},{"id":7341,"depth":239,"text":7344},{"id":7474,"depth":239,"text":7477},"content:dpopowich:2021-07-30:data-collector.md","dpopowich/2021-07-30/data-collector.md","dpopowich/2021-07-30/data-collector",{"user":7259,"name":7260},{"_path":8394,"_dir":8395,"_draft":7,"_partial":7,"_locale":8,"title":8396,"description":8397,"publishDate":8398,"tags":8399,"excerpt":8397,"body":8402,"_type":240,"_id":9461,"_source":242,"_file":9462,"_stem":9463,"_extension":245,"author":9464},"/ckeefer/2016-8/herokupdf","2016-8","Generating PDFs: wkhtmltopdf & Heroku","So, it has come to this.","2016-12-21",[8400,8401,16],"heroku","pdf",{"type":18,"children":8403,"toc":9455},[8404,8408,8419,8424,8429,8459,8478,8483,8488,8500,8529,8550,8555,8583,8589,8602,8616,8636,8659,8716,8721,8755,8760,8909,8914,8920,8932,9421,9425,9451],{"type":21,"tag":22,"props":8405,"children":8406},{},[8407],{"type":26,"value":8397},{"type":21,"tag":22,"props":8409,"children":8410},{},[8411,8413,8418],{"type":26,"value":8412},"Reports, yes, your application will have to have reports - in brand colours, with images and logos abounding, and probably festooned with graphs of various sizes, shapes and degrees of relevance to what was once a nice, streamlined set of data. This report has just become a part of the application 'product', meant not just to communicate, but also to entice and enthrall. Form has become just as important as function... and, did I forget to mention? It also needs to be ",{"type":21,"tag":166,"props":8414,"children":8415},{},[8416],{"type":26,"value":8417},"exportable",{"type":26,"value":2458},{"type":21,"tag":22,"props":8420,"children":8421},{},[8422],{"type":26,"value":8423},"Exportable, portable, downloadable, shareable - because as I mentioned, it's not just data now. It is now something clients/users need to be able to 'have', to attach to emails, send to their marketing departments, and incorporate into their powerpoints.",{"type":21,"tag":22,"props":8425,"children":8426},{},[8427],{"type":26,"value":8428},"There's a few ways to make this happen, but generally speaking, it's time to break out the PDFs.",{"type":21,"tag":22,"props":8430,"children":8431},{},[8432,8434,8441,8442,8449,8450,8457],{"type":26,"value":8433},"The Portable Document Format (PDF) has been a staple of web applications for some time, now - it's well-suited to the sharing of information in a self-contained package, and it's increasingly well-supported as ",{"type":21,"tag":206,"props":8435,"children":8438},{"href":8436,"rel":8437},"https://support.mozilla.org/en-US/kb/view-pdf-files-firefox",[210],[8439],{"type":26,"value":8440},"many",{"type":26,"value":5356},{"type":21,"tag":206,"props":8443,"children":8446},{"href":8444,"rel":8445},"https://pdfium.googlesource.com/pdfium/",[210],[8447],{"type":26,"value":8448},"modern",{"type":26,"value":5356},{"type":21,"tag":206,"props":8451,"children":8454},{"href":8452,"rel":8453},"http://blog.trendmicro.com/trendlabs-security-intelligence/windows-10s-new-browser-microsoft-edge-improved-but-also-new-risks/",[210],[8455],{"type":26,"value":8456},"browsers",{"type":26,"value":8458}," bake support for the display of PDFs directly into the browser.",{"type":21,"tag":22,"props":8460,"children":8461},{},[8462,8464,8470,8472,8476],{"type":26,"value":8463},"There are a number of ways to produce a PDF. We could use one of the various libraries for our language of choice - say, ",{"type":21,"tag":206,"props":8465,"children":8467},{"href":8466},"assets/pdf_reference_1-7.pdf",[8468],{"type":26,"value":8469},"published specification",{"type":26,"value":8471},", so if we really had a lot of free-time on our hands and/or a masochistic streak, we could even do the necessary bit-twiddling ourselves. If we needed to just output some simple text or static images, these might be the way to go (except for the bit-twiddling. Most projects will not benefit from you suddenly deciding you need to devote the next ",{"type":21,"tag":166,"props":8473,"children":8474},{},[8475],{"type":26,"value":5535},{"type":26,"value":8477}," months to creating a new PDF library from scratch, no matter how fun that might sound).",{"type":21,"tag":22,"props":8479,"children":8480},{},[8481],{"type":26,"value":8482},"In the scenario I posit above, however, we're transitioning from an HTML page which displayed the data, to a full, glossy report - but it still needs to show up in the browser first, and support PDF as an export option. We could have two completely separate code-bases to support the web page display, and to build the PDF, but wouldn't it be nice if we could just render our web page to a PDF?",{"type":21,"tag":87,"props":8484,"children":8486},{"id":8485},"wkhtmltopdf",[8487],{"type":26,"value":8485},{"type":21,"tag":22,"props":8489,"children":8490},{},[8491,8493,8498],{"type":26,"value":8492},"If that line of thinking seems familiar, you've probably also heard of ",{"type":21,"tag":206,"props":8494,"children":8496},{"href":8495},"wkhtmltopdf.org",[8497],{"type":26,"value":8485},{"type":26,"value":8499},". wkhtmltopdf is an open source command line tool (and C library) that relies on the WebKit rendering engine, which underlies Safari and, until the still fairly recent fork to Blink, powered the Chrome browser as well. The rendering engine uses the same HTML and CSS (and, optionally, JavaScript, with some limitations) that would be used to render your web page, and sends the output on to be converted into images and eventually, incorporating a number of specifiable configuration details, into a PDF.",{"type":21,"tag":22,"props":8501,"children":8502},{},[8503,8505,8511,8512,8518,8520,8527],{"type":26,"value":8504},"Assuming this sounds just like what the doctor ordered, installing wkhtmltopdf on most linux distros is just an ",{"type":21,"tag":43,"props":8506,"children":8508},{"className":8507},[],[8509],{"type":26,"value":8510},"apt-get",{"type":26,"value":685},{"type":21,"tag":43,"props":8513,"children":8515},{"className":8514},[],[8516],{"type":26,"value":8517},"yum install",{"type":26,"value":8519},", etc. away; or you can download a ",{"type":21,"tag":206,"props":8521,"children":8524},{"href":8522,"rel":8523},"http://wkhtmltopdf.org/downloads.html",[210],[8525],{"type":26,"value":8526},"pre-compiled binary",{"type":26,"value":8528}," if building from source is giving you trouble (and it might, particularly when attempting to build it on a server without any GUI libraries in place).",{"type":21,"tag":22,"props":8530,"children":8531},{},[8532,8534,8539,8541,8548],{"type":26,"value":8533},"Now that we have the binary available, we ",{"type":21,"tag":166,"props":8535,"children":8536},{},[8537],{"type":26,"value":8538},"could",{"type":26,"value":8540}," just call out to the command-line from our language of choice; we can make our lives a little easier by using a wrapper libary, however. I first used ",{"type":21,"tag":206,"props":8542,"children":8545},{"href":8543,"rel":8544},"https://pypi.python.org/pypi/pdfkit",[210],[8546],{"type":26,"value":8547},"pdfkit",{"type":26,"value":8549}," years ago with Ruby and it has been my go-to since for both Ruby and Python, but there are any number to choose from if pdfkit doesn't strike your fancy.",{"type":21,"tag":22,"props":8551,"children":8552},{},[8553],{"type":26,"value":8554},"For the purpose of this article, however, we're going to assume you're taking my suggestions, so let's add pdfkit to our requirements.txt file (update the version as needed):",{"type":21,"tag":595,"props":8556,"children":8558},{"className":597,"code":8557,"language":16,"meta":8,"style":8},"pdfkit==0.6.0\n",[8559],{"type":21,"tag":43,"props":8560,"children":8561},{"__ignoreMap":8},[8562],{"type":21,"tag":399,"props":8563,"children":8564},{"class":605,"line":606},[8565,8569,8573,8578],{"type":21,"tag":399,"props":8566,"children":8567},{"style":610},[8568],{"type":26,"value":8547},{"type":21,"tag":399,"props":8570,"children":8571},{"style":616},[8572],{"type":26,"value":1070},{"type":21,"tag":399,"props":8574,"children":8575},{"style":630},[8576],{"type":26,"value":8577},"0.6",{"type":21,"tag":399,"props":8579,"children":8580},{"style":610},[8581],{"type":26,"value":8582},".0\n",{"type":21,"tag":87,"props":8584,"children":8586},{"id":8585},"heroku-wkhtmlopdf-pack-and-which-wkhtmltopdf",[8587],{"type":26,"value":8588},"Heroku, wkhtmlopdf-pack, and which wkhtmltopdf",{"type":21,"tag":22,"props":8590,"children":8591},{},[8592,8594,8600],{"type":26,"value":8593},"So, this gets you set for working in your local 'nix dev box, but what about when it comes time to put this on a staging server or go live? If you're using a similar environment to your development system, the setup will be much the same - but let's add another piece to this puzzle, and posit that we're going to need to run on the still very popular ",{"type":21,"tag":206,"props":8595,"children":8597},{"href":8596},"heroku.com",[8598],{"type":26,"value":8599},"Heroku",{"type":26,"value":8601}," platform.",{"type":21,"tag":22,"props":8603,"children":8604},{},[8605,8607,8614],{"type":26,"value":8606},"In that case, we can't rely on the standard wkhtmltopdf binaries, as they aren't compatible. Instead, we need to 'vendor in' a pre-compiled binary for their platform - that's the purpose of the various Heroku ",{"type":21,"tag":206,"props":8608,"children":8611},{"href":8609,"rel":8610},"https://devcenter.heroku.com/articles/buildpacks",[210],[8612],{"type":26,"value":8613},"buildpacks",{"type":26,"value":8615}," that you may be familiar with if you've used Heroku before.",{"type":21,"tag":22,"props":8617,"children":8618},{},[8619,8621,8625,8627,8634],{"type":26,"value":8620},"We ",{"type":21,"tag":166,"props":8622,"children":8623},{},[8624],{"type":26,"value":8538},{"type":26,"value":8626}," compile the necessary binary for ourselves in a ",{"type":21,"tag":206,"props":8628,"children":8631},{"href":8629,"rel":8630},"https://devcenter.heroku.com/articles/buildpack-api#binaries",[210],[8632],{"type":26,"value":8633},"heroku dyno via Heroku run",{"type":26,"value":8635},", but it would be nicer not to spend the time/money if we don't have to; and luckily, we're not the first to think so.",{"type":21,"tag":22,"props":8637,"children":8638},{},[8639,8641,8648,8650,8657],{"type":26,"value":8640},"Enter ",{"type":21,"tag":206,"props":8642,"children":8645},{"href":8643,"rel":8644},"https://pypi.python.org/pypi/wkhtmltopdf-pack/0.12.3.0",[210],[8646],{"type":26,"value":8647},"wkhtmltopdf-pack",{"type":26,"value":8649},". Based on a ",{"type":21,"tag":206,"props":8651,"children":8654},{"href":8652,"rel":8653},"https://github.com/rposborne/wkhtmltopdf-heroku",[210],[8655],{"type":26,"value":8656},"similar project for Ruby",{"type":26,"value":8658},", this python package simply installs a pre-compiled wkhtmltopdf binary that's compatible with Heroku's Cedar-14 platform, quick and easy, so let's add that to our requirements.txt file as well.",{"type":21,"tag":595,"props":8660,"children":8662},{"className":597,"code":8661,"language":16,"meta":8,"style":8},"wkhtmltopdf-pack==0.12.3.0\npdfkit==0.6.0\n",[8663],{"type":21,"tag":43,"props":8664,"children":8665},{"__ignoreMap":8},[8666,8697],{"type":21,"tag":399,"props":8667,"children":8668},{"class":605,"line":606},[8669,8673,8678,8683,8687,8692],{"type":21,"tag":399,"props":8670,"children":8671},{"style":610},[8672],{"type":26,"value":8485},{"type":21,"tag":399,"props":8674,"children":8675},{"style":616},[8676],{"type":26,"value":8677},"-",{"type":21,"tag":399,"props":8679,"children":8680},{"style":610},[8681],{"type":26,"value":8682},"pack",{"type":21,"tag":399,"props":8684,"children":8685},{"style":616},[8686],{"type":26,"value":1070},{"type":21,"tag":399,"props":8688,"children":8689},{"style":630},[8690],{"type":26,"value":8691},"0.12",{"type":21,"tag":399,"props":8693,"children":8694},{"style":610},[8695],{"type":26,"value":8696},".3.0\n",{"type":21,"tag":399,"props":8698,"children":8699},{"class":605,"line":239},[8700,8704,8708,8712],{"type":21,"tag":399,"props":8701,"children":8702},{"style":610},[8703],{"type":26,"value":8547},{"type":21,"tag":399,"props":8705,"children":8706},{"style":616},[8707],{"type":26,"value":1070},{"type":21,"tag":399,"props":8709,"children":8710},{"style":630},[8711],{"type":26,"value":8577},{"type":21,"tag":399,"props":8713,"children":8714},{"style":610},[8715],{"type":26,"value":8582},{"type":21,"tag":22,"props":8717,"children":8718},{},[8719],{"type":26,"value":8720},"Now, we're faced with one additional hurdle, which is that we'll want to use the wkhtmltopdf-pack binary on heroku, but the wkhtmltopdf binary on our 'nix dev system. So, let's add an environment setting indicating the name of the binary we're looking for on heroku. Go to your dashboard, find the app to set this for, and click on settings. Then, add this config variable:",{"type":21,"tag":595,"props":8722,"children":8724},{"className":597,"code":8723,"language":16,"meta":8,"style":8},"Key: WKHTMLTOPDF_BINARY    Value: wkhtmltopdf-pack\n",[8725],{"type":21,"tag":43,"props":8726,"children":8727},{"__ignoreMap":8},[8728],{"type":21,"tag":399,"props":8729,"children":8730},{"class":605,"line":606},[8731,8736,8741,8746,8750],{"type":21,"tag":399,"props":8732,"children":8733},{"style":610},[8734],{"type":26,"value":8735},"Key: ",{"type":21,"tag":399,"props":8737,"children":8738},{"style":630},[8739],{"type":26,"value":8740},"WKHTMLTOPDF_BINARY",{"type":21,"tag":399,"props":8742,"children":8743},{"style":610},[8744],{"type":26,"value":8745},"    Value: wkhtmltopdf",{"type":21,"tag":399,"props":8747,"children":8748},{"style":616},[8749],{"type":26,"value":8677},{"type":21,"tag":399,"props":8751,"children":8752},{"style":610},[8753],{"type":26,"value":8754},"pack\n",{"type":21,"tag":22,"props":8756,"children":8757},{},[8758],{"type":26,"value":8759},"And now, let's add a little logic to ask the system which wkhtmltopdf binary we should use. This can go into whatever bootstrap config your framework might use (so, for instance, in Django's settings.py), or simply before your use of wkhtmltopdf/pdfkit.",{"type":21,"tag":595,"props":8761,"children":8763},{"className":597,"code":8762,"language":16,"meta":8,"style":8},"# Ensure virtualenv path is part of PATH env var\nos.environ['PATH'] += os.pathsep + os.path.dirname(sys.executable)  \nWKHTMLTOPDF_CMD = subprocess.Popen(\n    ['which', os.environ.get('WKHTMLTOPDF_BINARY', 'wkhtmltopdf')], # Note we default to 'wkhtmltopdf' as the binary name\n    stdout=subprocess.PIPE).communicate()[0].strip()\n",[8764],{"type":21,"tag":43,"props":8765,"children":8766},{"__ignoreMap":8},[8767,8775,8813,8830,8872],{"type":21,"tag":399,"props":8768,"children":8769},{"class":605,"line":606},[8770],{"type":21,"tag":399,"props":8771,"children":8772},{"style":2818},[8773],{"type":26,"value":8774},"# Ensure virtualenv path is part of PATH env var\n",{"type":21,"tag":399,"props":8776,"children":8777},{"class":605,"line":239},[8778,8783,8788,8793,8798,8803,8808],{"type":21,"tag":399,"props":8779,"children":8780},{"style":610},[8781],{"type":26,"value":8782},"os.environ[",{"type":21,"tag":399,"props":8784,"children":8785},{"style":644},[8786],{"type":26,"value":8787},"'PATH'",{"type":21,"tag":399,"props":8789,"children":8790},{"style":610},[8791],{"type":26,"value":8792},"] ",{"type":21,"tag":399,"props":8794,"children":8795},{"style":616},[8796],{"type":26,"value":8797},"+=",{"type":21,"tag":399,"props":8799,"children":8800},{"style":610},[8801],{"type":26,"value":8802}," os.pathsep ",{"type":21,"tag":399,"props":8804,"children":8805},{"style":616},[8806],{"type":26,"value":8807},"+",{"type":21,"tag":399,"props":8809,"children":8810},{"style":610},[8811],{"type":26,"value":8812}," os.path.dirname(sys.executable)  \n",{"type":21,"tag":399,"props":8814,"children":8815},{"class":605,"line":232},[8816,8821,8825],{"type":21,"tag":399,"props":8817,"children":8818},{"style":630},[8819],{"type":26,"value":8820},"WKHTMLTOPDF_CMD",{"type":21,"tag":399,"props":8822,"children":8823},{"style":616},[8824],{"type":26,"value":4224},{"type":21,"tag":399,"props":8826,"children":8827},{"style":610},[8828],{"type":26,"value":8829}," subprocess.Popen(\n",{"type":21,"tag":399,"props":8831,"children":8832},{"class":605,"line":654},[8833,8838,8843,8848,8853,8857,8862,8867],{"type":21,"tag":399,"props":8834,"children":8835},{"style":610},[8836],{"type":26,"value":8837},"    [",{"type":21,"tag":399,"props":8839,"children":8840},{"style":644},[8841],{"type":26,"value":8842},"'which'",{"type":21,"tag":399,"props":8844,"children":8845},{"style":610},[8846],{"type":26,"value":8847},", os.environ.get(",{"type":21,"tag":399,"props":8849,"children":8850},{"style":644},[8851],{"type":26,"value":8852},"'WKHTMLTOPDF_BINARY'",{"type":21,"tag":399,"props":8854,"children":8855},{"style":610},[8856],{"type":26,"value":685},{"type":21,"tag":399,"props":8858,"children":8859},{"style":644},[8860],{"type":26,"value":8861},"'wkhtmltopdf'",{"type":21,"tag":399,"props":8863,"children":8864},{"style":610},[8865],{"type":26,"value":8866},")], ",{"type":21,"tag":399,"props":8868,"children":8869},{"style":2818},[8870],{"type":26,"value":8871},"# Note we default to 'wkhtmltopdf' as the binary name\n",{"type":21,"tag":399,"props":8873,"children":8874},{"class":605,"line":698},[8875,8880,8884,8889,8894,8899,8904],{"type":21,"tag":399,"props":8876,"children":8877},{"style":658},[8878],{"type":26,"value":8879},"    stdout",{"type":21,"tag":399,"props":8881,"children":8882},{"style":616},[8883],{"type":26,"value":619},{"type":21,"tag":399,"props":8885,"children":8886},{"style":610},[8887],{"type":26,"value":8888},"subprocess.",{"type":21,"tag":399,"props":8890,"children":8891},{"style":630},[8892],{"type":26,"value":8893},"PIPE",{"type":21,"tag":399,"props":8895,"children":8896},{"style":610},[8897],{"type":26,"value":8898},").communicate()[",{"type":21,"tag":399,"props":8900,"children":8901},{"style":630},[8902],{"type":26,"value":8903},"0",{"type":21,"tag":399,"props":8905,"children":8906},{"style":610},[8907],{"type":26,"value":8908},"].strip()\n",{"type":21,"tag":22,"props":8910,"children":8911},{},[8912],{"type":26,"value":8913},"The value of WKHTMLTOPDF_CMD should now point to the binary we'll want to use.",{"type":21,"tag":87,"props":8915,"children":8917},{"id":8916},"pdfkit-example",[8918],{"type":26,"value":8919},"pdfkit Example",{"type":21,"tag":22,"props":8921,"children":8922},{},[8923,8925,8931],{"type":26,"value":8924},"Finally, let's take a look at a quick example of what using pdfkit with this binary might look like. PDFKit offers us fairly easy access to the various configuration options for the binary, and let's us set which binary we want to use, as shown below. ",{"type":21,"tag":206,"props":8926,"children":8928},{"href":8543,"rel":8927},[210],[8929],{"type":26,"value":8930},"See the documentation for more details",{"type":26,"value":2458},{"type":21,"tag":595,"props":8933,"children":8935},{"className":597,"code":8934,"language":16,"meta":8,"style":8},"    pdfkit_config = pdfkit.configuration(wkhtmltopdf=settings.WKHTMLTOPDF_CMD)\n    wk_options = {\n        'page-size': 'Letter',\n        'orientation': 'landscape',\n        'title': title,\n        # In order to specify command-line options that are simple toggles\n        # using this dict format, we give the option the value None\n        'no-outline': None,\n        'disable-javascript': None,\n        'encoding': 'UTF-8',\n        'margin-left': '0.1cm',\n        'margin-right': '0.1cm',\n        'margin-top': '0.1cm',\n        'margin-bottom': '0.1cm',\n        'lowquality': None,\n    }\n\n    # We can generate the pdf from a url, file or, as shown here, a string\n    pdfkit.from_string(\n        # This example uses Django's render_to_string function to return the result of\n        # rendering an HTML template as a string, which we can then pass to pdfkit and on\n        # into wkhtmltopdf\n        input=render_to_string('reportPDF.html', context=params, request=request),\n        # We can output to a variable or a file - in this case, we're outputting to a file\n        output_path=os.path.join('filepath', 'filename'),\n        options=wk_options,\n        configuration=pdfkit_config\n    )\n",[8936],{"type":21,"tag":43,"props":8937,"children":8938},{"__ignoreMap":8},[8939,8977,8994,9015,9036,9049,9057,9065,9086,9106,9127,9148,9168,9188,9208,9228,9235,9242,9250,9258,9266,9274,9282,9336,9344,9379,9396,9413],{"type":21,"tag":399,"props":8940,"children":8941},{"class":605,"line":606},[8942,8947,8951,8956,8960,8964,8969,8973],{"type":21,"tag":399,"props":8943,"children":8944},{"style":610},[8945],{"type":26,"value":8946},"    pdfkit_config ",{"type":21,"tag":399,"props":8948,"children":8949},{"style":616},[8950],{"type":26,"value":619},{"type":21,"tag":399,"props":8952,"children":8953},{"style":610},[8954],{"type":26,"value":8955}," pdfkit.configuration(",{"type":21,"tag":399,"props":8957,"children":8958},{"style":658},[8959],{"type":26,"value":8485},{"type":21,"tag":399,"props":8961,"children":8962},{"style":616},[8963],{"type":26,"value":619},{"type":21,"tag":399,"props":8965,"children":8966},{"style":610},[8967],{"type":26,"value":8968},"settings.",{"type":21,"tag":399,"props":8970,"children":8971},{"style":630},[8972],{"type":26,"value":8820},{"type":21,"tag":399,"props":8974,"children":8975},{"style":610},[8976],{"type":26,"value":1652},{"type":21,"tag":399,"props":8978,"children":8979},{"class":605,"line":239},[8980,8985,8989],{"type":21,"tag":399,"props":8981,"children":8982},{"style":610},[8983],{"type":26,"value":8984},"    wk_options ",{"type":21,"tag":399,"props":8986,"children":8987},{"style":616},[8988],{"type":26,"value":619},{"type":21,"tag":399,"props":8990,"children":8991},{"style":610},[8992],{"type":26,"value":8993}," {\n",{"type":21,"tag":399,"props":8995,"children":8996},{"class":605,"line":232},[8997,9002,9006,9011],{"type":21,"tag":399,"props":8998,"children":8999},{"style":644},[9000],{"type":26,"value":9001},"        'page-size'",{"type":21,"tag":399,"props":9003,"children":9004},{"style":610},[9005],{"type":26,"value":911},{"type":21,"tag":399,"props":9007,"children":9008},{"style":644},[9009],{"type":26,"value":9010},"'Letter'",{"type":21,"tag":399,"props":9012,"children":9013},{"style":610},[9014],{"type":26,"value":638},{"type":21,"tag":399,"props":9016,"children":9017},{"class":605,"line":654},[9018,9023,9027,9032],{"type":21,"tag":399,"props":9019,"children":9020},{"style":644},[9021],{"type":26,"value":9022},"        'orientation'",{"type":21,"tag":399,"props":9024,"children":9025},{"style":610},[9026],{"type":26,"value":911},{"type":21,"tag":399,"props":9028,"children":9029},{"style":644},[9030],{"type":26,"value":9031},"'landscape'",{"type":21,"tag":399,"props":9033,"children":9034},{"style":610},[9035],{"type":26,"value":638},{"type":21,"tag":399,"props":9037,"children":9038},{"class":605,"line":698},[9039,9044],{"type":21,"tag":399,"props":9040,"children":9041},{"style":644},[9042],{"type":26,"value":9043},"        'title'",{"type":21,"tag":399,"props":9045,"children":9046},{"style":610},[9047],{"type":26,"value":9048},": title,\n",{"type":21,"tag":399,"props":9050,"children":9051},{"class":605,"line":737},[9052],{"type":21,"tag":399,"props":9053,"children":9054},{"style":2818},[9055],{"type":26,"value":9056},"        # In order to specify command-line options that are simple toggles\n",{"type":21,"tag":399,"props":9058,"children":9059},{"class":605,"line":755},[9060],{"type":21,"tag":399,"props":9061,"children":9062},{"style":2818},[9063],{"type":26,"value":9064},"        # using this dict format, we give the option the value None\n",{"type":21,"tag":399,"props":9066,"children":9067},{"class":605,"line":773},[9068,9073,9077,9082],{"type":21,"tag":399,"props":9069,"children":9070},{"style":644},[9071],{"type":26,"value":9072},"        'no-outline'",{"type":21,"tag":399,"props":9074,"children":9075},{"style":610},[9076],{"type":26,"value":911},{"type":21,"tag":399,"props":9078,"children":9079},{"style":630},[9080],{"type":26,"value":9081},"None",{"type":21,"tag":399,"props":9083,"children":9084},{"style":610},[9085],{"type":26,"value":638},{"type":21,"tag":399,"props":9087,"children":9088},{"class":605,"line":795},[9089,9094,9098,9102],{"type":21,"tag":399,"props":9090,"children":9091},{"style":644},[9092],{"type":26,"value":9093},"        'disable-javascript'",{"type":21,"tag":399,"props":9095,"children":9096},{"style":610},[9097],{"type":26,"value":911},{"type":21,"tag":399,"props":9099,"children":9100},{"style":630},[9101],{"type":26,"value":9081},{"type":21,"tag":399,"props":9103,"children":9104},{"style":610},[9105],{"type":26,"value":638},{"type":21,"tag":399,"props":9107,"children":9108},{"class":605,"line":804},[9109,9114,9118,9123],{"type":21,"tag":399,"props":9110,"children":9111},{"style":644},[9112],{"type":26,"value":9113},"        'encoding'",{"type":21,"tag":399,"props":9115,"children":9116},{"style":610},[9117],{"type":26,"value":911},{"type":21,"tag":399,"props":9119,"children":9120},{"style":644},[9121],{"type":26,"value":9122},"'UTF-8'",{"type":21,"tag":399,"props":9124,"children":9125},{"style":610},[9126],{"type":26,"value":638},{"type":21,"tag":399,"props":9128,"children":9129},{"class":605,"line":843},[9130,9135,9139,9144],{"type":21,"tag":399,"props":9131,"children":9132},{"style":644},[9133],{"type":26,"value":9134},"        'margin-left'",{"type":21,"tag":399,"props":9136,"children":9137},{"style":610},[9138],{"type":26,"value":911},{"type":21,"tag":399,"props":9140,"children":9141},{"style":644},[9142],{"type":26,"value":9143},"'0.1cm'",{"type":21,"tag":399,"props":9145,"children":9146},{"style":610},[9147],{"type":26,"value":638},{"type":21,"tag":399,"props":9149,"children":9150},{"class":605,"line":882},[9151,9156,9160,9164],{"type":21,"tag":399,"props":9152,"children":9153},{"style":644},[9154],{"type":26,"value":9155},"        'margin-right'",{"type":21,"tag":399,"props":9157,"children":9158},{"style":610},[9159],{"type":26,"value":911},{"type":21,"tag":399,"props":9161,"children":9162},{"style":644},[9163],{"type":26,"value":9143},{"type":21,"tag":399,"props":9165,"children":9166},{"style":610},[9167],{"type":26,"value":638},{"type":21,"tag":399,"props":9169,"children":9170},{"class":605,"line":900},[9171,9176,9180,9184],{"type":21,"tag":399,"props":9172,"children":9173},{"style":644},[9174],{"type":26,"value":9175},"        'margin-top'",{"type":21,"tag":399,"props":9177,"children":9178},{"style":610},[9179],{"type":26,"value":911},{"type":21,"tag":399,"props":9181,"children":9182},{"style":644},[9183],{"type":26,"value":9143},{"type":21,"tag":399,"props":9185,"children":9186},{"style":610},[9187],{"type":26,"value":638},{"type":21,"tag":399,"props":9189,"children":9190},{"class":605,"line":923},[9191,9196,9200,9204],{"type":21,"tag":399,"props":9192,"children":9193},{"style":644},[9194],{"type":26,"value":9195},"        'margin-bottom'",{"type":21,"tag":399,"props":9197,"children":9198},{"style":610},[9199],{"type":26,"value":911},{"type":21,"tag":399,"props":9201,"children":9202},{"style":644},[9203],{"type":26,"value":9143},{"type":21,"tag":399,"props":9205,"children":9206},{"style":610},[9207],{"type":26,"value":638},{"type":21,"tag":399,"props":9209,"children":9210},{"class":605,"line":969},[9211,9216,9220,9224],{"type":21,"tag":399,"props":9212,"children":9213},{"style":644},[9214],{"type":26,"value":9215},"        'lowquality'",{"type":21,"tag":399,"props":9217,"children":9218},{"style":610},[9219],{"type":26,"value":911},{"type":21,"tag":399,"props":9221,"children":9222},{"style":630},[9223],{"type":26,"value":9081},{"type":21,"tag":399,"props":9225,"children":9226},{"style":610},[9227],{"type":26,"value":638},{"type":21,"tag":399,"props":9229,"children":9230},{"class":605,"line":991},[9231],{"type":21,"tag":399,"props":9232,"children":9233},{"style":610},[9234],{"type":26,"value":4328},{"type":21,"tag":399,"props":9236,"children":9237},{"class":605,"line":1013},[9238],{"type":21,"tag":399,"props":9239,"children":9240},{"emptyLinePlaceholder":2690},[9241],{"type":26,"value":2693},{"type":21,"tag":399,"props":9243,"children":9244},{"class":605,"line":1035},[9245],{"type":21,"tag":399,"props":9246,"children":9247},{"style":2818},[9248],{"type":26,"value":9249},"    # We can generate the pdf from a url, file or, as shown here, a string\n",{"type":21,"tag":399,"props":9251,"children":9252},{"class":605,"line":1092},[9253],{"type":21,"tag":399,"props":9254,"children":9255},{"style":610},[9256],{"type":26,"value":9257},"    pdfkit.from_string(\n",{"type":21,"tag":399,"props":9259,"children":9260},{"class":605,"line":1114},[9261],{"type":21,"tag":399,"props":9262,"children":9263},{"style":2818},[9264],{"type":26,"value":9265},"        # This example uses Django's render_to_string function to return the result of\n",{"type":21,"tag":399,"props":9267,"children":9268},{"class":605,"line":1136},[9269],{"type":21,"tag":399,"props":9270,"children":9271},{"style":2818},[9272],{"type":26,"value":9273},"        # rendering an HTML template as a string, which we can then pass to pdfkit and on\n",{"type":21,"tag":399,"props":9275,"children":9276},{"class":605,"line":1158},[9277],{"type":21,"tag":399,"props":9278,"children":9279},{"style":2818},[9280],{"type":26,"value":9281},"        # into wkhtmltopdf\n",{"type":21,"tag":399,"props":9283,"children":9284},{"class":605,"line":1180},[9285,9290,9294,9299,9304,9308,9313,9317,9322,9327,9331],{"type":21,"tag":399,"props":9286,"children":9287},{"style":658},[9288],{"type":26,"value":9289},"        input",{"type":21,"tag":399,"props":9291,"children":9292},{"style":616},[9293],{"type":26,"value":619},{"type":21,"tag":399,"props":9295,"children":9296},{"style":610},[9297],{"type":26,"value":9298},"render_to_string(",{"type":21,"tag":399,"props":9300,"children":9301},{"style":644},[9302],{"type":26,"value":9303},"'reportPDF.html'",{"type":21,"tag":399,"props":9305,"children":9306},{"style":610},[9307],{"type":26,"value":685},{"type":21,"tag":399,"props":9309,"children":9310},{"style":658},[9311],{"type":26,"value":9312},"context",{"type":21,"tag":399,"props":9314,"children":9315},{"style":616},[9316],{"type":26,"value":619},{"type":21,"tag":399,"props":9318,"children":9319},{"style":610},[9320],{"type":26,"value":9321},"params, ",{"type":21,"tag":399,"props":9323,"children":9324},{"style":658},[9325],{"type":26,"value":9326},"request",{"type":21,"tag":399,"props":9328,"children":9329},{"style":616},[9330],{"type":26,"value":619},{"type":21,"tag":399,"props":9332,"children":9333},{"style":610},[9334],{"type":26,"value":9335},"request),\n",{"type":21,"tag":399,"props":9337,"children":9338},{"class":605,"line":1202},[9339],{"type":21,"tag":399,"props":9340,"children":9341},{"style":2818},[9342],{"type":26,"value":9343},"        # We can output to a variable or a file - in this case, we're outputting to a file\n",{"type":21,"tag":399,"props":9345,"children":9346},{"class":605,"line":1211},[9347,9352,9356,9361,9366,9370,9375],{"type":21,"tag":399,"props":9348,"children":9349},{"style":658},[9350],{"type":26,"value":9351},"        output_path",{"type":21,"tag":399,"props":9353,"children":9354},{"style":616},[9355],{"type":26,"value":619},{"type":21,"tag":399,"props":9357,"children":9358},{"style":610},[9359],{"type":26,"value":9360},"os.path.join(",{"type":21,"tag":399,"props":9362,"children":9363},{"style":644},[9364],{"type":26,"value":9365},"'filepath'",{"type":21,"tag":399,"props":9367,"children":9368},{"style":610},[9369],{"type":26,"value":685},{"type":21,"tag":399,"props":9371,"children":9372},{"style":644},[9373],{"type":26,"value":9374},"'filename'",{"type":21,"tag":399,"props":9376,"children":9377},{"style":610},[9378],{"type":26,"value":695},{"type":21,"tag":399,"props":9380,"children":9381},{"class":605,"line":1228},[9382,9387,9391],{"type":21,"tag":399,"props":9383,"children":9384},{"style":658},[9385],{"type":26,"value":9386},"        options",{"type":21,"tag":399,"props":9388,"children":9389},{"style":616},[9390],{"type":26,"value":619},{"type":21,"tag":399,"props":9392,"children":9393},{"style":610},[9394],{"type":26,"value":9395},"wk_options,\n",{"type":21,"tag":399,"props":9397,"children":9398},{"class":605,"line":1269},[9399,9404,9408],{"type":21,"tag":399,"props":9400,"children":9401},{"style":658},[9402],{"type":26,"value":9403},"        configuration",{"type":21,"tag":399,"props":9405,"children":9406},{"style":616},[9407],{"type":26,"value":619},{"type":21,"tag":399,"props":9409,"children":9410},{"style":610},[9411],{"type":26,"value":9412},"pdfkit_config\n",{"type":21,"tag":399,"props":9414,"children":9415},{"class":605,"line":1307},[9416],{"type":21,"tag":399,"props":9417,"children":9418},{"style":610},[9419],{"type":26,"value":9420},"    )\n",{"type":21,"tag":87,"props":9422,"children":9423},{"id":2392},[9424],{"type":26,"value":2395},{"type":21,"tag":60,"props":9426,"children":9427},{},[9428,9435,9443],{"type":21,"tag":64,"props":9429,"children":9430},{},[9431],{"type":21,"tag":206,"props":9432,"children":9433},{"href":8495},[9434],{"type":26,"value":8485},{"type":21,"tag":64,"props":9436,"children":9437},{},[9438],{"type":21,"tag":206,"props":9439,"children":9441},{"href":8543,"rel":9440},[210],[9442],{"type":26,"value":8547},{"type":21,"tag":64,"props":9444,"children":9445},{},[9446],{"type":21,"tag":206,"props":9447,"children":9449},{"href":8643,"rel":9448},[210],[9450],{"type":26,"value":8647},{"type":21,"tag":2495,"props":9452,"children":9453},{},[9454],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":9456},[9457,9458,9459,9460],{"id":8485,"depth":232,"text":8485},{"id":8585,"depth":232,"text":8588},{"id":8916,"depth":232,"text":8919},{"id":2392,"depth":232,"text":2395},"content:ckeefer:2016-8:HerokuPDF.md","ckeefer/2016-8/HerokuPDF.md","ckeefer/2016-8/HerokuPDF",{"user":9465,"name":9466},"ckeefer","Christopher Keefer",{"_path":9468,"_dir":9469,"_draft":7,"_partial":7,"_locale":8,"title":9470,"description":9471,"publishDate":9472,"tags":9473,"excerpt":9471,"body":9475,"_type":240,"_id":12101,"_source":242,"_file":12102,"_stem":12103,"_extension":245,"author":12104},"/ckeefer/2016-4/djangochannels2","2016-4","Django Channels: From the Ground Up - Part 2","Last time, we decided to embark on a brave new adventure and give our Django framework a big upgrade with the inclusion of Django Channels. We got just far enough to get the development server running, but while this may be an adequate start, it's better to develop against something like what we intend to deploy, right?","2016-06-15",[2537,16,9474],"websockets",{"type":18,"children":9476,"toc":12092},[9477,9504,9509,9515,9520,9528,9729,9737,9899,9919,9925,9930,9935,10014,10019,10025,10036,10041,10046,10051,10191,10322,10335,10401,10406,10427,10433,10438,10459,10465,10478,10492,10513,10693,10698,10841,10846,10867,10872,11073,11093,11113,11126,11295,11316,11321,11418,11431,11451,11456,11462,11474,11479,11500,12008,12015,12020,12040,12045,12051,12063,12075,12088],{"type":21,"tag":22,"props":9478,"children":9479},{},[9480,9486,9488,9495,9497,9502],{"type":21,"tag":206,"props":9481,"children":9483},{"href":9482},"/search/user:ckeefer/django/channels/",[9484],{"type":26,"value":9485},"Last time",{"type":26,"value":9487},", we decided to embark on a brave new adventure and give our Django framework a big upgrade with the inclusion of ",{"type":21,"tag":206,"props":9489,"children":9492},{"href":9490,"rel":9491},"https://channels.readthedocs.io/en/latest/index.html",[210],[9493],{"type":26,"value":9494},"Django Channels",{"type":26,"value":9496},". We got just far enough to get the development server running, but while this may be an ",{"type":21,"tag":166,"props":9498,"children":9499},{},[9500],{"type":26,"value":9501},"adequate",{"type":26,"value":9503}," start, it's better to develop against something like what we intend to deploy, right?",{"type":21,"tag":22,"props":9505,"children":9506},{},[9507],{"type":26,"value":9508},"So, let's go the rest of the way and get ready to develop against something that at least resembles a standard production-ready environment with Django Channels.",{"type":21,"tag":87,"props":9510,"children":9512},{"id":9511},"post-receive-redux",[9513],{"type":26,"value":9514},"Post-Receive Redux",{"type":21,"tag":22,"props":9516,"children":9517},{},[9518],{"type":26,"value":9519},"Since we're transitioning away from the Django development server, we'll want to alter our post-receive scripts to tie into supervisorctl instead. So, let's edit our post-receive script like so:",{"type":21,"tag":22,"props":9521,"children":9522},{},[9523],{"type":21,"tag":195,"props":9524,"children":9525},{},[9526],{"type":26,"value":9527},"Ubuntu (bash) example:",{"type":21,"tag":595,"props":9529,"children":9533},{"className":9530,"code":9531,"language":9532,"meta":8,"style":8},"language-bash shiki shiki-themes github-light github-dark","#!/bin/bash\n\nGIT_WORK_TREE=/home/web/www/ git checkout -f\n\nsource /home/web/venv/bin/activate\npushd /home/web/www/\n\n# Install python libs via pip and perform database migrations\npip install --upgrade -r requirements.txt\npython manage.py migrate\n\npopd\ndeactivate\nsupervisorctl restart server_workers\nsupervisorctl restart server_interface\n","bash",[9534],{"type":21,"tag":43,"props":9535,"children":9536},{"__ignoreMap":8},[9537,9545,9552,9584,9591,9604,9612,9619,9627,9655,9672,9679,9687,9695,9713],{"type":21,"tag":399,"props":9538,"children":9539},{"class":605,"line":606},[9540],{"type":21,"tag":399,"props":9541,"children":9542},{"style":2818},[9543],{"type":26,"value":9544},"#!/bin/bash\n",{"type":21,"tag":399,"props":9546,"children":9547},{"class":605,"line":239},[9548],{"type":21,"tag":399,"props":9549,"children":9550},{"emptyLinePlaceholder":2690},[9551],{"type":26,"value":2693},{"type":21,"tag":399,"props":9553,"children":9554},{"class":605,"line":232},[9555,9560,9564,9569,9574,9579],{"type":21,"tag":399,"props":9556,"children":9557},{"style":610},[9558],{"type":26,"value":9559},"GIT_WORK_TREE",{"type":21,"tag":399,"props":9561,"children":9562},{"style":616},[9563],{"type":26,"value":619},{"type":21,"tag":399,"props":9565,"children":9566},{"style":644},[9567],{"type":26,"value":9568},"/home/web/www/",{"type":21,"tag":399,"props":9570,"children":9571},{"style":2704},[9572],{"type":26,"value":9573}," git",{"type":21,"tag":399,"props":9575,"children":9576},{"style":644},[9577],{"type":26,"value":9578}," checkout",{"type":21,"tag":399,"props":9580,"children":9581},{"style":630},[9582],{"type":26,"value":9583}," -f\n",{"type":21,"tag":399,"props":9585,"children":9586},{"class":605,"line":654},[9587],{"type":21,"tag":399,"props":9588,"children":9589},{"emptyLinePlaceholder":2690},[9590],{"type":26,"value":2693},{"type":21,"tag":399,"props":9592,"children":9593},{"class":605,"line":698},[9594,9599],{"type":21,"tag":399,"props":9595,"children":9596},{"style":630},[9597],{"type":26,"value":9598},"source",{"type":21,"tag":399,"props":9600,"children":9601},{"style":644},[9602],{"type":26,"value":9603}," /home/web/venv/bin/activate\n",{"type":21,"tag":399,"props":9605,"children":9606},{"class":605,"line":737},[9607],{"type":21,"tag":399,"props":9608,"children":9609},{"style":610},[9610],{"type":26,"value":9611},"pushd /home/web/www/\n",{"type":21,"tag":399,"props":9613,"children":9614},{"class":605,"line":755},[9615],{"type":21,"tag":399,"props":9616,"children":9617},{"emptyLinePlaceholder":2690},[9618],{"type":26,"value":2693},{"type":21,"tag":399,"props":9620,"children":9621},{"class":605,"line":773},[9622],{"type":21,"tag":399,"props":9623,"children":9624},{"style":2818},[9625],{"type":26,"value":9626},"# Install python libs via pip and perform database migrations\n",{"type":21,"tag":399,"props":9628,"children":9629},{"class":605,"line":795},[9630,9635,9640,9645,9650],{"type":21,"tag":399,"props":9631,"children":9632},{"style":2704},[9633],{"type":26,"value":9634},"pip",{"type":21,"tag":399,"props":9636,"children":9637},{"style":644},[9638],{"type":26,"value":9639}," install",{"type":21,"tag":399,"props":9641,"children":9642},{"style":630},[9643],{"type":26,"value":9644}," --upgrade",{"type":21,"tag":399,"props":9646,"children":9647},{"style":630},[9648],{"type":26,"value":9649}," -r",{"type":21,"tag":399,"props":9651,"children":9652},{"style":644},[9653],{"type":26,"value":9654}," requirements.txt\n",{"type":21,"tag":399,"props":9656,"children":9657},{"class":605,"line":804},[9658,9662,9667],{"type":21,"tag":399,"props":9659,"children":9660},{"style":2704},[9661],{"type":26,"value":16},{"type":21,"tag":399,"props":9663,"children":9664},{"style":644},[9665],{"type":26,"value":9666}," manage.py",{"type":21,"tag":399,"props":9668,"children":9669},{"style":644},[9670],{"type":26,"value":9671}," migrate\n",{"type":21,"tag":399,"props":9673,"children":9674},{"class":605,"line":843},[9675],{"type":21,"tag":399,"props":9676,"children":9677},{"emptyLinePlaceholder":2690},[9678],{"type":26,"value":2693},{"type":21,"tag":399,"props":9680,"children":9681},{"class":605,"line":882},[9682],{"type":21,"tag":399,"props":9683,"children":9684},{"style":610},[9685],{"type":26,"value":9686},"popd\n",{"type":21,"tag":399,"props":9688,"children":9689},{"class":605,"line":900},[9690],{"type":21,"tag":399,"props":9691,"children":9692},{"style":2704},[9693],{"type":26,"value":9694},"deactivate\n",{"type":21,"tag":399,"props":9696,"children":9697},{"class":605,"line":923},[9698,9703,9708],{"type":21,"tag":399,"props":9699,"children":9700},{"style":2704},[9701],{"type":26,"value":9702},"supervisorctl",{"type":21,"tag":399,"props":9704,"children":9705},{"style":644},[9706],{"type":26,"value":9707}," restart",{"type":21,"tag":399,"props":9709,"children":9710},{"style":644},[9711],{"type":26,"value":9712}," server_workers\n",{"type":21,"tag":399,"props":9714,"children":9715},{"class":605,"line":969},[9716,9720,9724],{"type":21,"tag":399,"props":9717,"children":9718},{"style":2704},[9719],{"type":26,"value":9702},{"type":21,"tag":399,"props":9721,"children":9722},{"style":644},[9723],{"type":26,"value":9707},{"type":21,"tag":399,"props":9725,"children":9726},{"style":644},[9727],{"type":26,"value":9728}," server_interface\n",{"type":21,"tag":22,"props":9730,"children":9731},{},[9732],{"type":21,"tag":195,"props":9733,"children":9734},{},[9735],{"type":26,"value":9736},"FreeBSD (csh) example:",{"type":21,"tag":595,"props":9738,"children":9740},{"className":9530,"code":9739,"language":9532,"meta":8,"style":8},"#!/bin/csh\n\nenv GIT_WORK_TREE=/home/web/www/ git checkout -f\nsource /home/web/venv/bin/activate.csh\npushd /home/web/www/\n\npip install --upgrade -r requirements.txt\npython manage.py migrate\n\npopd\ndeactivate\nsupervisorctl restart server_workers\nsupervisorctl restart server_interface\n",[9741],{"type":21,"tag":43,"props":9742,"children":9743},{"__ignoreMap":8},[9744,9752,9759,9784,9796,9803,9810,9833,9848,9855,9862,9869,9884],{"type":21,"tag":399,"props":9745,"children":9746},{"class":605,"line":606},[9747],{"type":21,"tag":399,"props":9748,"children":9749},{"style":2818},[9750],{"type":26,"value":9751},"#!/bin/csh\n",{"type":21,"tag":399,"props":9753,"children":9754},{"class":605,"line":239},[9755],{"type":21,"tag":399,"props":9756,"children":9757},{"emptyLinePlaceholder":2690},[9758],{"type":26,"value":2693},{"type":21,"tag":399,"props":9760,"children":9761},{"class":605,"line":232},[9762,9767,9772,9776,9780],{"type":21,"tag":399,"props":9763,"children":9764},{"style":2704},[9765],{"type":26,"value":9766},"env",{"type":21,"tag":399,"props":9768,"children":9769},{"style":644},[9770],{"type":26,"value":9771}," GIT_WORK_TREE=/home/web/www/",{"type":21,"tag":399,"props":9773,"children":9774},{"style":644},[9775],{"type":26,"value":9573},{"type":21,"tag":399,"props":9777,"children":9778},{"style":644},[9779],{"type":26,"value":9578},{"type":21,"tag":399,"props":9781,"children":9782},{"style":630},[9783],{"type":26,"value":9583},{"type":21,"tag":399,"props":9785,"children":9786},{"class":605,"line":654},[9787,9791],{"type":21,"tag":399,"props":9788,"children":9789},{"style":630},[9790],{"type":26,"value":9598},{"type":21,"tag":399,"props":9792,"children":9793},{"style":644},[9794],{"type":26,"value":9795}," /home/web/venv/bin/activate.csh\n",{"type":21,"tag":399,"props":9797,"children":9798},{"class":605,"line":698},[9799],{"type":21,"tag":399,"props":9800,"children":9801},{"style":610},[9802],{"type":26,"value":9611},{"type":21,"tag":399,"props":9804,"children":9805},{"class":605,"line":737},[9806],{"type":21,"tag":399,"props":9807,"children":9808},{"emptyLinePlaceholder":2690},[9809],{"type":26,"value":2693},{"type":21,"tag":399,"props":9811,"children":9812},{"class":605,"line":755},[9813,9817,9821,9825,9829],{"type":21,"tag":399,"props":9814,"children":9815},{"style":2704},[9816],{"type":26,"value":9634},{"type":21,"tag":399,"props":9818,"children":9819},{"style":644},[9820],{"type":26,"value":9639},{"type":21,"tag":399,"props":9822,"children":9823},{"style":630},[9824],{"type":26,"value":9644},{"type":21,"tag":399,"props":9826,"children":9827},{"style":630},[9828],{"type":26,"value":9649},{"type":21,"tag":399,"props":9830,"children":9831},{"style":644},[9832],{"type":26,"value":9654},{"type":21,"tag":399,"props":9834,"children":9835},{"class":605,"line":773},[9836,9840,9844],{"type":21,"tag":399,"props":9837,"children":9838},{"style":2704},[9839],{"type":26,"value":16},{"type":21,"tag":399,"props":9841,"children":9842},{"style":644},[9843],{"type":26,"value":9666},{"type":21,"tag":399,"props":9845,"children":9846},{"style":644},[9847],{"type":26,"value":9671},{"type":21,"tag":399,"props":9849,"children":9850},{"class":605,"line":795},[9851],{"type":21,"tag":399,"props":9852,"children":9853},{"emptyLinePlaceholder":2690},[9854],{"type":26,"value":2693},{"type":21,"tag":399,"props":9856,"children":9857},{"class":605,"line":804},[9858],{"type":21,"tag":399,"props":9859,"children":9860},{"style":610},[9861],{"type":26,"value":9686},{"type":21,"tag":399,"props":9863,"children":9864},{"class":605,"line":843},[9865],{"type":21,"tag":399,"props":9866,"children":9867},{"style":2704},[9868],{"type":26,"value":9694},{"type":21,"tag":399,"props":9870,"children":9871},{"class":605,"line":882},[9872,9876,9880],{"type":21,"tag":399,"props":9873,"children":9874},{"style":2704},[9875],{"type":26,"value":9702},{"type":21,"tag":399,"props":9877,"children":9878},{"style":644},[9879],{"type":26,"value":9707},{"type":21,"tag":399,"props":9881,"children":9882},{"style":644},[9883],{"type":26,"value":9712},{"type":21,"tag":399,"props":9885,"children":9886},{"class":605,"line":900},[9887,9891,9895],{"type":21,"tag":399,"props":9888,"children":9889},{"style":2704},[9890],{"type":26,"value":9702},{"type":21,"tag":399,"props":9892,"children":9893},{"style":644},[9894],{"type":26,"value":9707},{"type":21,"tag":399,"props":9896,"children":9897},{"style":644},[9898],{"type":26,"value":9728},{"type":21,"tag":22,"props":9900,"children":9901},{},[9902,9904,9910,9911,9917],{"type":26,"value":9903},"We can also work in things like running ",{"type":21,"tag":43,"props":9905,"children":9907},{"className":9906},[],[9908],{"type":26,"value":9909},"npm",{"type":26,"value":2584},{"type":21,"tag":43,"props":9912,"children":9914},{"className":9913},[],[9915],{"type":26,"value":9916},"grunt",{"type":26,"value":9918}," in there to build and minify files, or whatever other processes you need to do when pushing new code. This will likely be a little different for every project.",{"type":21,"tag":87,"props":9920,"children":9922},{"id":9921},"postgresql-setup",[9923],{"type":26,"value":9924},"Postgresql Setup",{"type":21,"tag":22,"props":9926,"children":9927},{},[9928],{"type":26,"value":9929},"Now, let's get our main database up and ready for our application. Up to this point, we've been relying on sqlite, which is a good starting place, but no match for a world-class hitter like PostgreSQL.",{"type":21,"tag":22,"props":9931,"children":9932},{},[9933],{"type":26,"value":9934},"We'll su in as the standard postgres user, and pass user and database creation commands to psql:",{"type":21,"tag":595,"props":9936,"children":9938},{"className":9530,"code":9937,"language":9532,"meta":8,"style":8},"vsudo su - postgres \u003C\u003CEOF\necho \"CREATE ROLE web LOGIN ENCRYPTED PASSWORD '';\" | psql\ncreatedb \"your_postgres_db\" --owner \"web\"\necho \"GRANT ALL PRIVILEGES ON DATABASE your_postgres_db TO web\" | psql\nservice postgresql reload\nEOF\n",[9939],{"type":21,"tag":43,"props":9940,"children":9941},{"__ignoreMap":8},[9942,9975,9983,9991,9999,10007],{"type":21,"tag":399,"props":9943,"children":9944},{"class":605,"line":606},[9945,9950,9955,9960,9965,9970],{"type":21,"tag":399,"props":9946,"children":9947},{"style":2704},[9948],{"type":26,"value":9949},"vsudo",{"type":21,"tag":399,"props":9951,"children":9952},{"style":644},[9953],{"type":26,"value":9954}," su",{"type":21,"tag":399,"props":9956,"children":9957},{"style":644},[9958],{"type":26,"value":9959}," -",{"type":21,"tag":399,"props":9961,"children":9962},{"style":644},[9963],{"type":26,"value":9964}," postgres",{"type":21,"tag":399,"props":9966,"children":9967},{"style":616},[9968],{"type":26,"value":9969}," \u003C\u003C",{"type":21,"tag":399,"props":9971,"children":9972},{"style":644},[9973],{"type":26,"value":9974},"EOF\n",{"type":21,"tag":399,"props":9976,"children":9977},{"class":605,"line":239},[9978],{"type":21,"tag":399,"props":9979,"children":9980},{"style":644},[9981],{"type":26,"value":9982},"echo \"CREATE ROLE web LOGIN ENCRYPTED PASSWORD '';\" | psql\n",{"type":21,"tag":399,"props":9984,"children":9985},{"class":605,"line":232},[9986],{"type":21,"tag":399,"props":9987,"children":9988},{"style":644},[9989],{"type":26,"value":9990},"createdb \"your_postgres_db\" --owner \"web\"\n",{"type":21,"tag":399,"props":9992,"children":9993},{"class":605,"line":654},[9994],{"type":21,"tag":399,"props":9995,"children":9996},{"style":644},[9997],{"type":26,"value":9998},"echo \"GRANT ALL PRIVILEGES ON DATABASE your_postgres_db TO web\" | psql\n",{"type":21,"tag":399,"props":10000,"children":10001},{"class":605,"line":698},[10002],{"type":21,"tag":399,"props":10003,"children":10004},{"style":644},[10005],{"type":26,"value":10006},"service postgresql reload\n",{"type":21,"tag":399,"props":10008,"children":10009},{"class":605,"line":737},[10010],{"type":21,"tag":399,"props":10011,"children":10012},{"style":644},[10013],{"type":26,"value":9974},{"type":21,"tag":22,"props":10015,"children":10016},{},[10017],{"type":26,"value":10018},"And we should be good to go.",{"type":21,"tag":87,"props":10020,"children":10022},{"id":10021},"setup-supervisord",[10023],{"type":26,"value":10024},"Setup Supervisord",{"type":21,"tag":22,"props":10026,"children":10027},{},[10028,10030,10035],{"type":26,"value":10029},"Okay, we're getting closer - now we're going to get supervisor to not only start our programs when it runs, but allow our regular web user to start and stop the programs we're setting up with",{"type":21,"tag":43,"props":10031,"children":10033},{"className":10032},[],[10034],{"type":26,"value":9702},{"type":26,"value":2458},{"type":21,"tag":22,"props":10037,"children":10038},{},[10039],{"type":26,"value":10040},"Supervisord is an important part of the puzzle, as it will keep our server interface and workers up and running. If workers fail or die - which is certainly a possibility - our interface will simply sit and hang, waiting for workers to hand it something to do. Inversely, nothing going in or out of the workers is going to get back to your users without the interface, so it's important we have something monitoring both of them.",{"type":21,"tag":22,"props":10042,"children":10043},{},[10044],{"type":26,"value":10045},"We'll need to open up the supervisord.conf file, or add some new files which will be imported into said conf file - on Ubuntu, for example, we can locate our new conf files at /etc/supervisor/conf.d. On FreeBSD, we might just open up the conf file itself - be careful, the install instruction tell us about /etc/supervisord.conf, but their may also be another conf file at /usr/local/etc/ that overrides it.",{"type":21,"tag":22,"props":10047,"children":10048},{},[10049],{"type":26,"value":10050},"Either way, we're going to be adding two new program entries - one for the server workers (which handle the requests in the background) and one for the server interface (which consumes the requests and responses from clients and workers alike).",{"type":21,"tag":595,"props":10052,"children":10054},{"className":9530,"code":10053,"language":9532,"meta":8,"style":8},"[program:server_workers]\ncommand=/home/web/venv/bin/python /home/web/www/manage.py runworker\ndirectory=/home/web/www/\nuser=web\nautostart=true\nautorestart=true\nredirect_stderr=true\nstopasgroup=true\n",[10055],{"type":21,"tag":43,"props":10056,"children":10057},{"__ignoreMap":8},[10058,10066,10093,10110,10126,10143,10159,10175],{"type":21,"tag":399,"props":10059,"children":10060},{"class":605,"line":606},[10061],{"type":21,"tag":399,"props":10062,"children":10063},{"style":610},[10064],{"type":26,"value":10065},"[program:server_workers]\n",{"type":21,"tag":399,"props":10067,"children":10068},{"class":605,"line":239},[10069,10074,10078,10083,10088],{"type":21,"tag":399,"props":10070,"children":10071},{"style":610},[10072],{"type":26,"value":10073},"command",{"type":21,"tag":399,"props":10075,"children":10076},{"style":616},[10077],{"type":26,"value":619},{"type":21,"tag":399,"props":10079,"children":10080},{"style":644},[10081],{"type":26,"value":10082},"/home/web/venv/bin/python",{"type":21,"tag":399,"props":10084,"children":10085},{"style":2704},[10086],{"type":26,"value":10087}," /home/web/www/manage.py",{"type":21,"tag":399,"props":10089,"children":10090},{"style":644},[10091],{"type":26,"value":10092}," runworker\n",{"type":21,"tag":399,"props":10094,"children":10095},{"class":605,"line":232},[10096,10101,10105],{"type":21,"tag":399,"props":10097,"children":10098},{"style":610},[10099],{"type":26,"value":10100},"directory",{"type":21,"tag":399,"props":10102,"children":10103},{"style":616},[10104],{"type":26,"value":619},{"type":21,"tag":399,"props":10106,"children":10107},{"style":644},[10108],{"type":26,"value":10109},"/home/web/www/\n",{"type":21,"tag":399,"props":10111,"children":10112},{"class":605,"line":654},[10113,10117,10121],{"type":21,"tag":399,"props":10114,"children":10115},{"style":610},[10116],{"type":26,"value":2845},{"type":21,"tag":399,"props":10118,"children":10119},{"style":616},[10120],{"type":26,"value":619},{"type":21,"tag":399,"props":10122,"children":10123},{"style":644},[10124],{"type":26,"value":10125},"web\n",{"type":21,"tag":399,"props":10127,"children":10128},{"class":605,"line":698},[10129,10134,10138],{"type":21,"tag":399,"props":10130,"children":10131},{"style":610},[10132],{"type":26,"value":10133},"autostart",{"type":21,"tag":399,"props":10135,"children":10136},{"style":616},[10137],{"type":26,"value":619},{"type":21,"tag":399,"props":10139,"children":10140},{"style":644},[10141],{"type":26,"value":10142},"true\n",{"type":21,"tag":399,"props":10144,"children":10145},{"class":605,"line":737},[10146,10151,10155],{"type":21,"tag":399,"props":10147,"children":10148},{"style":610},[10149],{"type":26,"value":10150},"autorestart",{"type":21,"tag":399,"props":10152,"children":10153},{"style":616},[10154],{"type":26,"value":619},{"type":21,"tag":399,"props":10156,"children":10157},{"style":644},[10158],{"type":26,"value":10142},{"type":21,"tag":399,"props":10160,"children":10161},{"class":605,"line":755},[10162,10167,10171],{"type":21,"tag":399,"props":10163,"children":10164},{"style":610},[10165],{"type":26,"value":10166},"redirect_stderr",{"type":21,"tag":399,"props":10168,"children":10169},{"style":616},[10170],{"type":26,"value":619},{"type":21,"tag":399,"props":10172,"children":10173},{"style":644},[10174],{"type":26,"value":10142},{"type":21,"tag":399,"props":10176,"children":10177},{"class":605,"line":773},[10178,10183,10187],{"type":21,"tag":399,"props":10179,"children":10180},{"style":610},[10181],{"type":26,"value":10182},"stopasgroup",{"type":21,"tag":399,"props":10184,"children":10185},{"style":616},[10186],{"type":26,"value":619},{"type":21,"tag":399,"props":10188,"children":10189},{"style":644},[10190],{"type":26,"value":10142},{"type":21,"tag":595,"props":10192,"children":10194},{"className":9530,"code":10193,"language":9532,"meta":8,"style":8},"[program:server_interface]\ncommand=/home/web/venv/bin/daphne -b 127.0.0.1 -p 8000 yourapp.asgi:channel_layer\ndirectory=/home/web/www/\nautostart=true\nautorestart=true\nstopasgroup=true\nuser=web\n",[10195],{"type":21,"tag":43,"props":10196,"children":10197},{"__ignoreMap":8},[10198,10206,10247,10262,10277,10292,10307],{"type":21,"tag":399,"props":10199,"children":10200},{"class":605,"line":606},[10201],{"type":21,"tag":399,"props":10202,"children":10203},{"style":610},[10204],{"type":26,"value":10205},"[program:server_interface]\n",{"type":21,"tag":399,"props":10207,"children":10208},{"class":605,"line":239},[10209,10213,10217,10222,10227,10232,10237,10242],{"type":21,"tag":399,"props":10210,"children":10211},{"style":610},[10212],{"type":26,"value":10073},{"type":21,"tag":399,"props":10214,"children":10215},{"style":616},[10216],{"type":26,"value":619},{"type":21,"tag":399,"props":10218,"children":10219},{"style":644},[10220],{"type":26,"value":10221},"/home/web/venv/bin/daphne",{"type":21,"tag":399,"props":10223,"children":10224},{"style":2704},[10225],{"type":26,"value":10226}," -b",{"type":21,"tag":399,"props":10228,"children":10229},{"style":630},[10230],{"type":26,"value":10231}," 127.0.0.1",{"type":21,"tag":399,"props":10233,"children":10234},{"style":630},[10235],{"type":26,"value":10236}," -p",{"type":21,"tag":399,"props":10238,"children":10239},{"style":630},[10240],{"type":26,"value":10241}," 8000",{"type":21,"tag":399,"props":10243,"children":10244},{"style":644},[10245],{"type":26,"value":10246}," yourapp.asgi:channel_layer\n",{"type":21,"tag":399,"props":10248,"children":10249},{"class":605,"line":232},[10250,10254,10258],{"type":21,"tag":399,"props":10251,"children":10252},{"style":610},[10253],{"type":26,"value":10100},{"type":21,"tag":399,"props":10255,"children":10256},{"style":616},[10257],{"type":26,"value":619},{"type":21,"tag":399,"props":10259,"children":10260},{"style":644},[10261],{"type":26,"value":10109},{"type":21,"tag":399,"props":10263,"children":10264},{"class":605,"line":654},[10265,10269,10273],{"type":21,"tag":399,"props":10266,"children":10267},{"style":610},[10268],{"type":26,"value":10133},{"type":21,"tag":399,"props":10270,"children":10271},{"style":616},[10272],{"type":26,"value":619},{"type":21,"tag":399,"props":10274,"children":10275},{"style":644},[10276],{"type":26,"value":10142},{"type":21,"tag":399,"props":10278,"children":10279},{"class":605,"line":698},[10280,10284,10288],{"type":21,"tag":399,"props":10281,"children":10282},{"style":610},[10283],{"type":26,"value":10150},{"type":21,"tag":399,"props":10285,"children":10286},{"style":616},[10287],{"type":26,"value":619},{"type":21,"tag":399,"props":10289,"children":10290},{"style":644},[10291],{"type":26,"value":10142},{"type":21,"tag":399,"props":10293,"children":10294},{"class":605,"line":737},[10295,10299,10303],{"type":21,"tag":399,"props":10296,"children":10297},{"style":610},[10298],{"type":26,"value":10182},{"type":21,"tag":399,"props":10300,"children":10301},{"style":616},[10302],{"type":26,"value":619},{"type":21,"tag":399,"props":10304,"children":10305},{"style":644},[10306],{"type":26,"value":10142},{"type":21,"tag":399,"props":10308,"children":10309},{"class":605,"line":755},[10310,10314,10318],{"type":21,"tag":399,"props":10311,"children":10312},{"style":610},[10313],{"type":26,"value":2845},{"type":21,"tag":399,"props":10315,"children":10316},{"style":616},[10317],{"type":26,"value":619},{"type":21,"tag":399,"props":10319,"children":10320},{"style":644},[10321],{"type":26,"value":10125},{"type":21,"tag":22,"props":10323,"children":10324},{},[10325,10327,10333],{"type":26,"value":10326},"And finally, up near the top of the supervisord.conf file, beneath the ",{"type":21,"tag":43,"props":10328,"children":10330},{"className":10329},[],[10331],{"type":26,"value":10332},"[unix_http_server]",{"type":26,"value":10334}," heading, we need to make a few small changes to allow our web user to start and stop these processes.",{"type":21,"tag":595,"props":10336,"children":10338},{"className":9530,"code":10337,"language":9532,"meta":8,"style":8},"[unix_http_server]\nfile=/var/run/supervisor/supervisor.sock\nchmod=0770\nchown=nobody:web\n",[10339],{"type":21,"tag":43,"props":10340,"children":10341},{"__ignoreMap":8},[10342,10350,10367,10384],{"type":21,"tag":399,"props":10343,"children":10344},{"class":605,"line":606},[10345],{"type":21,"tag":399,"props":10346,"children":10347},{"style":610},[10348],{"type":26,"value":10349},"[unix_http_server]\n",{"type":21,"tag":399,"props":10351,"children":10352},{"class":605,"line":239},[10353,10358,10362],{"type":21,"tag":399,"props":10354,"children":10355},{"style":610},[10356],{"type":26,"value":10357},"file",{"type":21,"tag":399,"props":10359,"children":10360},{"style":616},[10361],{"type":26,"value":619},{"type":21,"tag":399,"props":10363,"children":10364},{"style":644},[10365],{"type":26,"value":10366},"/var/run/supervisor/supervisor.sock\n",{"type":21,"tag":399,"props":10368,"children":10369},{"class":605,"line":232},[10370,10375,10379],{"type":21,"tag":399,"props":10371,"children":10372},{"style":610},[10373],{"type":26,"value":10374},"chmod",{"type":21,"tag":399,"props":10376,"children":10377},{"style":616},[10378],{"type":26,"value":619},{"type":21,"tag":399,"props":10380,"children":10381},{"style":644},[10382],{"type":26,"value":10383},"0770\n",{"type":21,"tag":399,"props":10385,"children":10386},{"class":605,"line":654},[10387,10392,10396],{"type":21,"tag":399,"props":10388,"children":10389},{"style":610},[10390],{"type":26,"value":10391},"chown",{"type":21,"tag":399,"props":10393,"children":10394},{"style":616},[10395],{"type":26,"value":619},{"type":21,"tag":399,"props":10397,"children":10398},{"style":644},[10399],{"type":26,"value":10400},"nobody:web\n",{"type":21,"tag":22,"props":10402,"children":10403},{},[10404],{"type":26,"value":10405},"and restart the supervisord process.",{"type":21,"tag":22,"props":10407,"children":10408},{},[10409,10411,10417,10419,10425],{"type":26,"value":10410},"You can also opt to add your web user to a group like ",{"type":21,"tag":43,"props":10412,"children":10414},{"className":10413},[],[10415],{"type":26,"value":10416},"wheel",{"type":26,"value":10418},", and then alter the chown line to something like ",{"type":21,"tag":43,"props":10420,"children":10422},{"className":10421},[],[10423],{"type":26,"value":10424},"chown=nobody:wheel",{"type":26,"value":10426},". The key here is simply to make the socket available to the appropriate user group so our web user can interact with it.",{"type":21,"tag":87,"props":10428,"children":10430},{"id":10429},"setup-redis",[10431],{"type":26,"value":10432},"Setup Redis",{"type":21,"tag":22,"props":10434,"children":10435},{},[10436],{"type":26,"value":10437},"Getting Redis up and ready is smooth and easy - in fact, at this point you're already done. See how easy that was?",{"type":21,"tag":22,"props":10439,"children":10440},{},[10441,10443,10449,10451,10457],{"type":26,"value":10442},"If you used the requirements.txt in part 1, you already have the needed ",{"type":21,"tag":43,"props":10444,"children":10446},{"className":10445},[],[10447],{"type":26,"value":10448},"asgi_redis",{"type":26,"value":10450}," package installed in your virtual environment, and the defaults that Redis installs with are fine for us. I do encourage you to take a look through the ",{"type":21,"tag":206,"props":10452,"children":10454},{"href":10453},"redis.io/documentation",[10455],{"type":26,"value":10456},"Redis docs",{"type":26,"value":10458},", however, and configure it as you need.",{"type":21,"tag":87,"props":10460,"children":10462},{"id":10461},"django-setup",[10463],{"type":26,"value":10464},"Django Setup",{"type":21,"tag":22,"props":10466,"children":10467},{},[10468,10470,10476],{"type":26,"value":10469},"If we were to check our supervisord status with ",{"type":21,"tag":43,"props":10471,"children":10473},{"className":10472},[],[10474],{"type":26,"value":10475},"supervisorctl status",{"type":26,"value":10477}," right now, we can expect it will be showing a FATAL error for our server_interface program entry. We need to add some new files to get daphne up and running, and while we're in there we'll make the changes necessary to get Django playing nicely with Postgres as our database and Redis as our channel backend.",{"type":21,"tag":22,"props":10479,"children":10480},{},[10481,10483,10490],{"type":26,"value":10482},"For reference, much of the below can also be found in the ",{"type":21,"tag":206,"props":10484,"children":10487},{"href":10485,"rel":10486},"https://channels.readthedocs.io/en/latest/deploying.html#setting-up-a-channel-backend",[210],[10488],{"type":26,"value":10489},"Channels docs",{"type":26,"value":10491},", with the addition of our postgres details.",{"type":21,"tag":22,"props":10493,"children":10494},{},[10495,10497,10503,10505,10511],{"type":26,"value":10496},"Let's start with PostgreSQL. Open up your ",{"type":21,"tag":43,"props":10498,"children":10500},{"className":10499},[],[10501],{"type":26,"value":10502},"settings.py",{"type":26,"value":10504}," file, and alter the ",{"type":21,"tag":43,"props":10506,"children":10508},{"className":10507},[],[10509],{"type":26,"value":10510},"DATABASES",{"type":26,"value":10512}," dict to the following:",{"type":21,"tag":595,"props":10514,"children":10516},{"className":597,"code":10515,"language":16,"meta":8,"style":8},"DATABASES = {\n'default': {\n'ENGINE': 'django.db.backends.postgresql_psycopg2',\n'NAME': 'your_postgres_db',\n'USER': 'web',\n'PASSWORD': 'your_postgres_pass',\n'HOST': 'localhost',\n'PORT': '', # Empty string == default (5432)\n}\n}\n",[10517],{"type":21,"tag":43,"props":10518,"children":10519},{"__ignoreMap":8},[10520,10535,10548,10569,10590,10611,10632,10653,10679,10686],{"type":21,"tag":399,"props":10521,"children":10522},{"class":605,"line":606},[10523,10527,10531],{"type":21,"tag":399,"props":10524,"children":10525},{"style":630},[10526],{"type":26,"value":10510},{"type":21,"tag":399,"props":10528,"children":10529},{"style":616},[10530],{"type":26,"value":4224},{"type":21,"tag":399,"props":10532,"children":10533},{"style":610},[10534],{"type":26,"value":8993},{"type":21,"tag":399,"props":10536,"children":10537},{"class":605,"line":239},[10538,10543],{"type":21,"tag":399,"props":10539,"children":10540},{"style":644},[10541],{"type":26,"value":10542},"'default'",{"type":21,"tag":399,"props":10544,"children":10545},{"style":610},[10546],{"type":26,"value":10547},": {\n",{"type":21,"tag":399,"props":10549,"children":10550},{"class":605,"line":232},[10551,10556,10560,10565],{"type":21,"tag":399,"props":10552,"children":10553},{"style":644},[10554],{"type":26,"value":10555},"'ENGINE'",{"type":21,"tag":399,"props":10557,"children":10558},{"style":610},[10559],{"type":26,"value":911},{"type":21,"tag":399,"props":10561,"children":10562},{"style":644},[10563],{"type":26,"value":10564},"'django.db.backends.postgresql_psycopg2'",{"type":21,"tag":399,"props":10566,"children":10567},{"style":610},[10568],{"type":26,"value":638},{"type":21,"tag":399,"props":10570,"children":10571},{"class":605,"line":654},[10572,10577,10581,10586],{"type":21,"tag":399,"props":10573,"children":10574},{"style":644},[10575],{"type":26,"value":10576},"'NAME'",{"type":21,"tag":399,"props":10578,"children":10579},{"style":610},[10580],{"type":26,"value":911},{"type":21,"tag":399,"props":10582,"children":10583},{"style":644},[10584],{"type":26,"value":10585},"'your_postgres_db'",{"type":21,"tag":399,"props":10587,"children":10588},{"style":610},[10589],{"type":26,"value":638},{"type":21,"tag":399,"props":10591,"children":10592},{"class":605,"line":698},[10593,10598,10602,10607],{"type":21,"tag":399,"props":10594,"children":10595},{"style":644},[10596],{"type":26,"value":10597},"'USER'",{"type":21,"tag":399,"props":10599,"children":10600},{"style":610},[10601],{"type":26,"value":911},{"type":21,"tag":399,"props":10603,"children":10604},{"style":644},[10605],{"type":26,"value":10606},"'web'",{"type":21,"tag":399,"props":10608,"children":10609},{"style":610},[10610],{"type":26,"value":638},{"type":21,"tag":399,"props":10612,"children":10613},{"class":605,"line":737},[10614,10619,10623,10628],{"type":21,"tag":399,"props":10615,"children":10616},{"style":644},[10617],{"type":26,"value":10618},"'PASSWORD'",{"type":21,"tag":399,"props":10620,"children":10621},{"style":610},[10622],{"type":26,"value":911},{"type":21,"tag":399,"props":10624,"children":10625},{"style":644},[10626],{"type":26,"value":10627},"'your_postgres_pass'",{"type":21,"tag":399,"props":10629,"children":10630},{"style":610},[10631],{"type":26,"value":638},{"type":21,"tag":399,"props":10633,"children":10634},{"class":605,"line":755},[10635,10640,10644,10649],{"type":21,"tag":399,"props":10636,"children":10637},{"style":644},[10638],{"type":26,"value":10639},"'HOST'",{"type":21,"tag":399,"props":10641,"children":10642},{"style":610},[10643],{"type":26,"value":911},{"type":21,"tag":399,"props":10645,"children":10646},{"style":644},[10647],{"type":26,"value":10648},"'localhost'",{"type":21,"tag":399,"props":10650,"children":10651},{"style":610},[10652],{"type":26,"value":638},{"type":21,"tag":399,"props":10654,"children":10655},{"class":605,"line":773},[10656,10661,10665,10670,10674],{"type":21,"tag":399,"props":10657,"children":10658},{"style":644},[10659],{"type":26,"value":10660},"'PORT'",{"type":21,"tag":399,"props":10662,"children":10663},{"style":610},[10664],{"type":26,"value":911},{"type":21,"tag":399,"props":10666,"children":10667},{"style":644},[10668],{"type":26,"value":10669},"''",{"type":21,"tag":399,"props":10671,"children":10672},{"style":610},[10673],{"type":26,"value":685},{"type":21,"tag":399,"props":10675,"children":10676},{"style":2818},[10677],{"type":26,"value":10678},"# Empty string == default (5432)\n",{"type":21,"tag":399,"props":10680,"children":10681},{"class":605,"line":795},[10682],{"type":21,"tag":399,"props":10683,"children":10684},{"style":610},[10685],{"type":26,"value":7145},{"type":21,"tag":399,"props":10687,"children":10688},{"class":605,"line":804},[10689],{"type":21,"tag":399,"props":10690,"children":10691},{"style":610},[10692],{"type":26,"value":7145},{"type":21,"tag":22,"props":10694,"children":10695},{},[10696],{"type":26,"value":10697},"Now, add the following dict beneath that to setup our redis channel layer:",{"type":21,"tag":595,"props":10699,"children":10701},{"className":597,"code":10700,"language":16,"meta":8,"style":8},"CHANNEL_LAYERS = {\n\"default\": {\n\"BACKEND\": \"asgi_redis.RedisChannelLayer\",\n\"CONFIG\": {\n\"hosts\": [(\"localhost\", 6379)],\n},\n\"ROUTING\": \"yourapp.routing.channel_routing\",\n},\n}\n",[10702],{"type":21,"tag":43,"props":10703,"children":10704},{"__ignoreMap":8},[10705,10721,10733,10754,10766,10798,10806,10827,10834],{"type":21,"tag":399,"props":10706,"children":10707},{"class":605,"line":606},[10708,10713,10717],{"type":21,"tag":399,"props":10709,"children":10710},{"style":630},[10711],{"type":26,"value":10712},"CHANNEL_LAYERS",{"type":21,"tag":399,"props":10714,"children":10715},{"style":616},[10716],{"type":26,"value":4224},{"type":21,"tag":399,"props":10718,"children":10719},{"style":610},[10720],{"type":26,"value":8993},{"type":21,"tag":399,"props":10722,"children":10723},{"class":605,"line":239},[10724,10729],{"type":21,"tag":399,"props":10725,"children":10726},{"style":644},[10727],{"type":26,"value":10728},"\"default\"",{"type":21,"tag":399,"props":10730,"children":10731},{"style":610},[10732],{"type":26,"value":10547},{"type":21,"tag":399,"props":10734,"children":10735},{"class":605,"line":232},[10736,10741,10745,10750],{"type":21,"tag":399,"props":10737,"children":10738},{"style":644},[10739],{"type":26,"value":10740},"\"BACKEND\"",{"type":21,"tag":399,"props":10742,"children":10743},{"style":610},[10744],{"type":26,"value":911},{"type":21,"tag":399,"props":10746,"children":10747},{"style":644},[10748],{"type":26,"value":10749},"\"asgi_redis.RedisChannelLayer\"",{"type":21,"tag":399,"props":10751,"children":10752},{"style":610},[10753],{"type":26,"value":638},{"type":21,"tag":399,"props":10755,"children":10756},{"class":605,"line":654},[10757,10762],{"type":21,"tag":399,"props":10758,"children":10759},{"style":644},[10760],{"type":26,"value":10761},"\"CONFIG\"",{"type":21,"tag":399,"props":10763,"children":10764},{"style":610},[10765],{"type":26,"value":10547},{"type":21,"tag":399,"props":10767,"children":10768},{"class":605,"line":698},[10769,10774,10779,10784,10788,10793],{"type":21,"tag":399,"props":10770,"children":10771},{"style":644},[10772],{"type":26,"value":10773},"\"hosts\"",{"type":21,"tag":399,"props":10775,"children":10776},{"style":610},[10777],{"type":26,"value":10778},": [(",{"type":21,"tag":399,"props":10780,"children":10781},{"style":644},[10782],{"type":26,"value":10783},"\"localhost\"",{"type":21,"tag":399,"props":10785,"children":10786},{"style":610},[10787],{"type":26,"value":685},{"type":21,"tag":399,"props":10789,"children":10790},{"style":630},[10791],{"type":26,"value":10792},"6379",{"type":21,"tag":399,"props":10794,"children":10795},{"style":610},[10796],{"type":26,"value":10797},")],\n",{"type":21,"tag":399,"props":10799,"children":10800},{"class":605,"line":737},[10801],{"type":21,"tag":399,"props":10802,"children":10803},{"style":610},[10804],{"type":26,"value":10805},"},\n",{"type":21,"tag":399,"props":10807,"children":10808},{"class":605,"line":755},[10809,10814,10818,10823],{"type":21,"tag":399,"props":10810,"children":10811},{"style":644},[10812],{"type":26,"value":10813},"\"ROUTING\"",{"type":21,"tag":399,"props":10815,"children":10816},{"style":610},[10817],{"type":26,"value":911},{"type":21,"tag":399,"props":10819,"children":10820},{"style":644},[10821],{"type":26,"value":10822},"\"yourapp.routing.channel_routing\"",{"type":21,"tag":399,"props":10824,"children":10825},{"style":610},[10826],{"type":26,"value":638},{"type":21,"tag":399,"props":10828,"children":10829},{"class":605,"line":773},[10830],{"type":21,"tag":399,"props":10831,"children":10832},{"style":610},[10833],{"type":26,"value":10805},{"type":21,"tag":399,"props":10835,"children":10836},{"class":605,"line":795},[10837],{"type":21,"tag":399,"props":10838,"children":10839},{"style":610},[10840],{"type":26,"value":7145},{"type":21,"tag":22,"props":10842,"children":10843},{},[10844],{"type":26,"value":10845},"By default, channels uses process memory as it's communication channel, which is fine for simple development cases, but obviously can't be shared between processes and is unsuitable for production.",{"type":21,"tag":22,"props":10847,"children":10848},{},[10849,10851,10857,10859,10865],{"type":26,"value":10850},"Notice the \"ROUTING\" component of the above config? That's a reference to the ",{"type":21,"tag":43,"props":10852,"children":10854},{"className":10853},[],[10855],{"type":26,"value":10856},"routing.py",{"type":26,"value":10858}," file we're going to slap into the application directory - it should live next to your ",{"type":21,"tag":43,"props":10860,"children":10862},{"className":10861},[],[10863],{"type":26,"value":10864},"urls.py",{"type":26,"value":10866}," file.",{"type":21,"tag":22,"props":10868,"children":10869},{},[10870],{"type":26,"value":10871},"Here's an example:",{"type":21,"tag":595,"props":10873,"children":10875},{"className":597,"code":10874,"language":16,"meta":8,"style":8},"from channels.routing import route\nfrom channels.routing import include\n\nfrom yourapp import consumers\n\nhttp_routing = [\nroute('http.request', consumers.index)\n]\n\nstream_routing = [\n\n]\n\nchannel_routing = [\ninclude(stream_routing)\n]\n",[10876],{"type":21,"tag":43,"props":10877,"children":10878},{"__ignoreMap":8},[10879,10900,10920,10927,10948,10955,10972,10990,10998,11005,11021,11028,11035,11042,11058,11066],{"type":21,"tag":399,"props":10880,"children":10881},{"class":605,"line":606},[10882,10886,10891,10895],{"type":21,"tag":399,"props":10883,"children":10884},{"style":616},[10885],{"type":26,"value":2669},{"type":21,"tag":399,"props":10887,"children":10888},{"style":610},[10889],{"type":26,"value":10890}," channels.routing ",{"type":21,"tag":399,"props":10892,"children":10893},{"style":616},[10894],{"type":26,"value":2679},{"type":21,"tag":399,"props":10896,"children":10897},{"style":610},[10898],{"type":26,"value":10899}," route\n",{"type":21,"tag":399,"props":10901,"children":10902},{"class":605,"line":239},[10903,10907,10911,10915],{"type":21,"tag":399,"props":10904,"children":10905},{"style":616},[10906],{"type":26,"value":2669},{"type":21,"tag":399,"props":10908,"children":10909},{"style":610},[10910],{"type":26,"value":10890},{"type":21,"tag":399,"props":10912,"children":10913},{"style":616},[10914],{"type":26,"value":2679},{"type":21,"tag":399,"props":10916,"children":10917},{"style":610},[10918],{"type":26,"value":10919}," include\n",{"type":21,"tag":399,"props":10921,"children":10922},{"class":605,"line":232},[10923],{"type":21,"tag":399,"props":10924,"children":10925},{"emptyLinePlaceholder":2690},[10926],{"type":26,"value":2693},{"type":21,"tag":399,"props":10928,"children":10929},{"class":605,"line":654},[10930,10934,10939,10943],{"type":21,"tag":399,"props":10931,"children":10932},{"style":616},[10933],{"type":26,"value":2669},{"type":21,"tag":399,"props":10935,"children":10936},{"style":610},[10937],{"type":26,"value":10938}," yourapp ",{"type":21,"tag":399,"props":10940,"children":10941},{"style":616},[10942],{"type":26,"value":2679},{"type":21,"tag":399,"props":10944,"children":10945},{"style":610},[10946],{"type":26,"value":10947}," consumers\n",{"type":21,"tag":399,"props":10949,"children":10950},{"class":605,"line":698},[10951],{"type":21,"tag":399,"props":10952,"children":10953},{"emptyLinePlaceholder":2690},[10954],{"type":26,"value":2693},{"type":21,"tag":399,"props":10956,"children":10957},{"class":605,"line":737},[10958,10963,10967],{"type":21,"tag":399,"props":10959,"children":10960},{"style":610},[10961],{"type":26,"value":10962},"http_routing ",{"type":21,"tag":399,"props":10964,"children":10965},{"style":616},[10966],{"type":26,"value":619},{"type":21,"tag":399,"props":10968,"children":10969},{"style":610},[10970],{"type":26,"value":10971}," [\n",{"type":21,"tag":399,"props":10973,"children":10974},{"class":605,"line":755},[10975,10980,10985],{"type":21,"tag":399,"props":10976,"children":10977},{"style":610},[10978],{"type":26,"value":10979},"route(",{"type":21,"tag":399,"props":10981,"children":10982},{"style":644},[10983],{"type":26,"value":10984},"'http.request'",{"type":21,"tag":399,"props":10986,"children":10987},{"style":610},[10988],{"type":26,"value":10989},", consumers.index)\n",{"type":21,"tag":399,"props":10991,"children":10992},{"class":605,"line":773},[10993],{"type":21,"tag":399,"props":10994,"children":10995},{"style":610},[10996],{"type":26,"value":10997},"]\n",{"type":21,"tag":399,"props":10999,"children":11000},{"class":605,"line":795},[11001],{"type":21,"tag":399,"props":11002,"children":11003},{"emptyLinePlaceholder":2690},[11004],{"type":26,"value":2693},{"type":21,"tag":399,"props":11006,"children":11007},{"class":605,"line":804},[11008,11013,11017],{"type":21,"tag":399,"props":11009,"children":11010},{"style":610},[11011],{"type":26,"value":11012},"stream_routing ",{"type":21,"tag":399,"props":11014,"children":11015},{"style":616},[11016],{"type":26,"value":619},{"type":21,"tag":399,"props":11018,"children":11019},{"style":610},[11020],{"type":26,"value":10971},{"type":21,"tag":399,"props":11022,"children":11023},{"class":605,"line":843},[11024],{"type":21,"tag":399,"props":11025,"children":11026},{"emptyLinePlaceholder":2690},[11027],{"type":26,"value":2693},{"type":21,"tag":399,"props":11029,"children":11030},{"class":605,"line":882},[11031],{"type":21,"tag":399,"props":11032,"children":11033},{"style":610},[11034],{"type":26,"value":10997},{"type":21,"tag":399,"props":11036,"children":11037},{"class":605,"line":900},[11038],{"type":21,"tag":399,"props":11039,"children":11040},{"emptyLinePlaceholder":2690},[11041],{"type":26,"value":2693},{"type":21,"tag":399,"props":11043,"children":11044},{"class":605,"line":923},[11045,11050,11054],{"type":21,"tag":399,"props":11046,"children":11047},{"style":610},[11048],{"type":26,"value":11049},"channel_routing ",{"type":21,"tag":399,"props":11051,"children":11052},{"style":616},[11053],{"type":26,"value":619},{"type":21,"tag":399,"props":11055,"children":11056},{"style":610},[11057],{"type":26,"value":10971},{"type":21,"tag":399,"props":11059,"children":11060},{"class":605,"line":969},[11061],{"type":21,"tag":399,"props":11062,"children":11063},{"style":610},[11064],{"type":26,"value":11065},"include(stream_routing)\n",{"type":21,"tag":399,"props":11067,"children":11068},{"class":605,"line":991},[11069],{"type":21,"tag":399,"props":11070,"children":11071},{"style":610},[11072],{"type":26,"value":10997},{"type":21,"tag":22,"props":11074,"children":11075},{},[11076,11078,11083,11085,11091],{"type":26,"value":11077},"We'll be fleshing this file out in Part 3, so stay tuned. In the meantime, you can also add urls as normal to ",{"type":21,"tag":43,"props":11079,"children":11081},{"className":11080},[],[11082],{"type":26,"value":10864},{"type":26,"value":11084},", referencing standard views, either in a ",{"type":21,"tag":43,"props":11086,"children":11088},{"className":11087},[],[11089],{"type":26,"value":11090},"views.py",{"type":26,"value":11092}," file, or in a directory - this is all exactly the same as it is in standard Django, without any need for changes to accommodate the new ASGI server.",{"type":21,"tag":22,"props":11094,"children":11095},{},[11096,11098,11103,11105,11111],{"type":26,"value":11097},"Next to the ",{"type":21,"tag":43,"props":11099,"children":11101},{"className":11100},[],[11102],{"type":26,"value":11090},{"type":26,"value":11104}," file in your application, add a new file named ",{"type":21,"tag":43,"props":11106,"children":11108},{"className":11107},[],[11109],{"type":26,"value":11110},"consumers.py",{"type":26,"value":11112},". These consumers will be the callables handling the routing we'll be setting up in routing.py - these are what will enable something like websocket requests and replies, rather than being tied into the standard Django view structure.",{"type":21,"tag":22,"props":11114,"children":11115},{},[11116,11118,11125],{"type":26,"value":11117},"For now, we'll put in a simple example of handling an http request, straight from the ",{"type":21,"tag":206,"props":11119,"children":11122},{"href":11120,"rel":11121},"https://channels.readthedocs.io/en/latest/getting-started.html#first-consumers",[210],[11123],{"type":26,"value":11124},"channels docs",{"type":26,"value":434},{"type":21,"tag":595,"props":11127,"children":11129},{"className":597,"code":11128,"language":16,"meta":8,"style":8},"from django.http import HttpResponse\nfrom channels.handler import AsgiHandler\n\ndef http_consumer(message):\n# Make standard HTTP response - access ASGI path attribute directly\nresponse = HttpResponse(\"Hello world! You asked for %s\" % message.content['path'])\n# Encode that response into message format (ASGI)\nfor chunk in AsgiHandler.encode_response(response):\nmessage.reply_channel.send(chunk)\n",[11130],{"type":21,"tag":43,"props":11131,"children":11132},{"__ignoreMap":8},[11133,11154,11175,11182,11199,11207,11256,11264,11287],{"type":21,"tag":399,"props":11134,"children":11135},{"class":605,"line":606},[11136,11140,11145,11149],{"type":21,"tag":399,"props":11137,"children":11138},{"style":616},[11139],{"type":26,"value":2669},{"type":21,"tag":399,"props":11141,"children":11142},{"style":610},[11143],{"type":26,"value":11144}," django.http ",{"type":21,"tag":399,"props":11146,"children":11147},{"style":616},[11148],{"type":26,"value":2679},{"type":21,"tag":399,"props":11150,"children":11151},{"style":610},[11152],{"type":26,"value":11153}," HttpResponse\n",{"type":21,"tag":399,"props":11155,"children":11156},{"class":605,"line":239},[11157,11161,11166,11170],{"type":21,"tag":399,"props":11158,"children":11159},{"style":616},[11160],{"type":26,"value":2669},{"type":21,"tag":399,"props":11162,"children":11163},{"style":610},[11164],{"type":26,"value":11165}," channels.handler ",{"type":21,"tag":399,"props":11167,"children":11168},{"style":616},[11169],{"type":26,"value":2679},{"type":21,"tag":399,"props":11171,"children":11172},{"style":610},[11173],{"type":26,"value":11174}," AsgiHandler\n",{"type":21,"tag":399,"props":11176,"children":11177},{"class":605,"line":232},[11178],{"type":21,"tag":399,"props":11179,"children":11180},{"emptyLinePlaceholder":2690},[11181],{"type":26,"value":2693},{"type":21,"tag":399,"props":11183,"children":11184},{"class":605,"line":654},[11185,11189,11194],{"type":21,"tag":399,"props":11186,"children":11187},{"style":616},[11188],{"type":26,"value":3281},{"type":21,"tag":399,"props":11190,"children":11191},{"style":2704},[11192],{"type":26,"value":11193}," http_consumer",{"type":21,"tag":399,"props":11195,"children":11196},{"style":610},[11197],{"type":26,"value":11198},"(message):\n",{"type":21,"tag":399,"props":11200,"children":11201},{"class":605,"line":698},[11202],{"type":21,"tag":399,"props":11203,"children":11204},{"style":2818},[11205],{"type":26,"value":11206},"# Make standard HTTP response - access ASGI path attribute directly\n",{"type":21,"tag":399,"props":11208,"children":11209},{"class":605,"line":737},[11210,11215,11219,11224,11229,11233,11237,11242,11247,11252],{"type":21,"tag":399,"props":11211,"children":11212},{"style":610},[11213],{"type":26,"value":11214},"response ",{"type":21,"tag":399,"props":11216,"children":11217},{"style":616},[11218],{"type":26,"value":619},{"type":21,"tag":399,"props":11220,"children":11221},{"style":610},[11222],{"type":26,"value":11223}," HttpResponse(",{"type":21,"tag":399,"props":11225,"children":11226},{"style":644},[11227],{"type":26,"value":11228},"\"Hello world! You asked for ",{"type":21,"tag":399,"props":11230,"children":11231},{"style":630},[11232],{"type":26,"value":7924},{"type":21,"tag":399,"props":11234,"children":11235},{"style":644},[11236],{"type":26,"value":2986},{"type":21,"tag":399,"props":11238,"children":11239},{"style":616},[11240],{"type":26,"value":11241}," %",{"type":21,"tag":399,"props":11243,"children":11244},{"style":610},[11245],{"type":26,"value":11246}," message.content[",{"type":21,"tag":399,"props":11248,"children":11249},{"style":644},[11250],{"type":26,"value":11251},"'path'",{"type":21,"tag":399,"props":11253,"children":11254},{"style":610},[11255],{"type":26,"value":4432},{"type":21,"tag":399,"props":11257,"children":11258},{"class":605,"line":755},[11259],{"type":21,"tag":399,"props":11260,"children":11261},{"style":2818},[11262],{"type":26,"value":11263},"# Encode that response into message format (ASGI)\n",{"type":21,"tag":399,"props":11265,"children":11266},{"class":605,"line":773},[11267,11272,11277,11282],{"type":21,"tag":399,"props":11268,"children":11269},{"style":616},[11270],{"type":26,"value":11271},"for",{"type":21,"tag":399,"props":11273,"children":11274},{"style":610},[11275],{"type":26,"value":11276}," chunk ",{"type":21,"tag":399,"props":11278,"children":11279},{"style":616},[11280],{"type":26,"value":11281},"in",{"type":21,"tag":399,"props":11283,"children":11284},{"style":610},[11285],{"type":26,"value":11286}," AsgiHandler.encode_response(response):\n",{"type":21,"tag":399,"props":11288,"children":11289},{"class":605,"line":795},[11290],{"type":21,"tag":399,"props":11291,"children":11292},{"style":610},[11293],{"type":26,"value":11294},"message.reply_channel.send(chunk)\n",{"type":21,"tag":22,"props":11296,"children":11297},{},[11298,11300,11306,11308,11314],{"type":26,"value":11299},"Finally, we need to create an ",{"type":21,"tag":43,"props":11301,"children":11303},{"className":11302},[],[11304],{"type":26,"value":11305},"asgi.py",{"type":26,"value":11307}," file - this should sit right next to the ",{"type":21,"tag":43,"props":11309,"children":11311},{"className":11310},[],[11312],{"type":26,"value":11313},"wsgi.py",{"type":26,"value":11315}," file that was automatically created for you by Django.",{"type":21,"tag":22,"props":11317,"children":11318},{},[11319],{"type":26,"value":11320},"It's pretty simple:",{"type":21,"tag":595,"props":11322,"children":11324},{"className":597,"code":11323,"language":16,"meta":8,"style":8},"import os\nfrom channels.asgi import get_channel_layer\n\nos.environ.setdefault(\"DJANGO_SETTINGS_MODULE\", \"yourapp.settings\")\n\nchannel_layer = get_channel_layer()\n",[11325],{"type":21,"tag":43,"props":11326,"children":11327},{"__ignoreMap":8},[11328,11340,11361,11368,11394,11401],{"type":21,"tag":399,"props":11329,"children":11330},{"class":605,"line":606},[11331,11335],{"type":21,"tag":399,"props":11332,"children":11333},{"style":616},[11334],{"type":26,"value":2679},{"type":21,"tag":399,"props":11336,"children":11337},{"style":610},[11338],{"type":26,"value":11339}," os\n",{"type":21,"tag":399,"props":11341,"children":11342},{"class":605,"line":239},[11343,11347,11352,11356],{"type":21,"tag":399,"props":11344,"children":11345},{"style":616},[11346],{"type":26,"value":2669},{"type":21,"tag":399,"props":11348,"children":11349},{"style":610},[11350],{"type":26,"value":11351}," channels.asgi ",{"type":21,"tag":399,"props":11353,"children":11354},{"style":616},[11355],{"type":26,"value":2679},{"type":21,"tag":399,"props":11357,"children":11358},{"style":610},[11359],{"type":26,"value":11360}," get_channel_layer\n",{"type":21,"tag":399,"props":11362,"children":11363},{"class":605,"line":232},[11364],{"type":21,"tag":399,"props":11365,"children":11366},{"emptyLinePlaceholder":2690},[11367],{"type":26,"value":2693},{"type":21,"tag":399,"props":11369,"children":11370},{"class":605,"line":654},[11371,11376,11381,11385,11390],{"type":21,"tag":399,"props":11372,"children":11373},{"style":610},[11374],{"type":26,"value":11375},"os.environ.setdefault(",{"type":21,"tag":399,"props":11377,"children":11378},{"style":644},[11379],{"type":26,"value":11380},"\"DJANGO_SETTINGS_MODULE\"",{"type":21,"tag":399,"props":11382,"children":11383},{"style":610},[11384],{"type":26,"value":685},{"type":21,"tag":399,"props":11386,"children":11387},{"style":644},[11388],{"type":26,"value":11389},"\"yourapp.settings\"",{"type":21,"tag":399,"props":11391,"children":11392},{"style":610},[11393],{"type":26,"value":1652},{"type":21,"tag":399,"props":11395,"children":11396},{"class":605,"line":698},[11397],{"type":21,"tag":399,"props":11398,"children":11399},{"emptyLinePlaceholder":2690},[11400],{"type":26,"value":2693},{"type":21,"tag":399,"props":11402,"children":11403},{"class":605,"line":737},[11404,11409,11413],{"type":21,"tag":399,"props":11405,"children":11406},{"style":610},[11407],{"type":26,"value":11408},"channel_layer ",{"type":21,"tag":399,"props":11410,"children":11411},{"style":616},[11412],{"type":26,"value":619},{"type":21,"tag":399,"props":11414,"children":11415},{"style":610},[11416],{"type":26,"value":11417}," get_channel_layer()\n",{"type":21,"tag":22,"props":11419,"children":11420},{},[11421,11423,11429],{"type":26,"value":11422},"This is the file we're referencing in the supervisord config when we wrote ",{"type":21,"tag":43,"props":11424,"children":11426},{"className":11425},[],[11427],{"type":26,"value":11428},"command=/home/web/venv/bin/daphne -b 127.0.0.1 -p 8000 yourapp.asgi:channel_layer",{"type":26,"value":11430},", so now that it's in place, we have all the pieces in place.",{"type":21,"tag":22,"props":11432,"children":11433},{},[11434,11436,11442,11444,11449],{"type":26,"value":11435},"Restart the supervisord service (",{"type":21,"tag":43,"props":11437,"children":11439},{"className":11438},[],[11440],{"type":26,"value":11441},"service supervisord restart",{"type":26,"value":11443},"), and in a few seconds you should be able to check status (",{"type":21,"tag":43,"props":11445,"children":11447},{"className":11446},[],[11448],{"type":26,"value":10475},{"type":26,"value":11450},") and see both of our program entries up and running!",{"type":21,"tag":22,"props":11452,"children":11453},{},[11454],{"type":26,"value":11455},"Which is great! Give yourself a pat on the back. However, we have one step left. Right now, our interace server is bound to port 8000, which is fine for development, but we need to serve up requests on port 80 in production. Now, we could bind to port 80 instead, but that will require running Daphne with root permissions. Instead, let's borrow the common approach with WSGI servers, and setup a reverse proxy with Nginx.",{"type":21,"tag":87,"props":11457,"children":11459},{"id":11458},"last-step-nginx-setup",[11460],{"type":26,"value":11461},"Last Step - Nginx Setup",{"type":21,"tag":22,"props":11463,"children":11464},{},[11465,11467,11472],{"type":26,"value":11466},"Now, it doesn't ",{"type":21,"tag":166,"props":11468,"children":11469},{},[11470],{"type":26,"value":11471},"have",{"type":26,"value":11473}," to be Nginx - Apache could handle this duty, as could a number of others.",{"type":21,"tag":22,"props":11475,"children":11476},{},[11477],{"type":26,"value":11478},"In the meantime, however, Nginx it is, so let's take a look at the nicely straightforward setup.",{"type":21,"tag":22,"props":11480,"children":11481},{},[11482,11484,11490,11492,11498],{"type":26,"value":11483},"Head to ",{"type":21,"tag":43,"props":11485,"children":11487},{"className":11486},[],[11488],{"type":26,"value":11489},"/etc/nginx/sites-available/",{"type":26,"value":11491}," and create a new, appropriately named file (e.g. ",{"type":21,"tag":43,"props":11493,"children":11495},{"className":11494},[],[11496],{"type":26,"value":11497},"yourapp",{"type":26,"value":11499},"), and edit it to include the following:",{"type":21,"tag":595,"props":11501,"children":11503},{"className":597,"code":11502,"language":16,"meta":8,"style":8},"# Enable upgrading of connection (and websocket proxying) depending on the\n# presence of the upgrade field in the client request header\nmap \\$http_upgrade \\$connection_upgrade {\ndefault upgrade;\n'' close;\n}\n\n# Create an upstream alias to where we've set daphne to bind to\nupstream yourapp {\nserver 127.0.0.1:8000;\n}\n\nserver {\n\nlisten 80;\n# If you have a domain name, this is where to add it\nserver_name localhost;\n\nlocation / {\n# Pass request to the upstream alias\nproxy_pass http://yourapp;\n\n# Require http version 1.1 to allow for upgrade requests\nproxy_http_version 1.1;\n\n# We want proxy_buffering off for proxying to websockets.\nproxy_buffering off;\n\n# http://en.wikipedia.org/wiki/X-Forwarded-For\nproxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;\n\n# enable this if you use HTTPS:\n# proxy_set_header X-Forwarded-Proto https;\n\n# pass the Host: header from the client for the sake of redirects\nproxy_set_header Host $http_host;\n\n# We've set the Host header, so we don't need Nginx to muddle\n# about with redirects\nproxy_redirect off;\n\n# Depending on the request value, set the Upgrade and\n# connection headers\nproxy_set_header Upgrade $http_upgrade;\n\nproxy_set_header Connection $connection_upgrade;\n}\n}\n",[11504],{"type":21,"tag":43,"props":11505,"children":11506},{"__ignoreMap":8},[11507,11515,11523,11542,11554,11570,11577,11584,11592,11600,11627,11634,11641,11649,11656,11672,11680,11688,11695,11711,11719,11737,11744,11752,11769,11776,11784,11792,11799,11807,11843,11850,11858,11866,11873,11881,11898,11905,11913,11921,11929,11936,11944,11952,11969,11976,11993,12000],{"type":21,"tag":399,"props":11508,"children":11509},{"class":605,"line":606},[11510],{"type":21,"tag":399,"props":11511,"children":11512},{"style":2818},[11513],{"type":26,"value":11514},"# Enable upgrading of connection (and websocket proxying) depending on the\n",{"type":21,"tag":399,"props":11516,"children":11517},{"class":605,"line":239},[11518],{"type":21,"tag":399,"props":11519,"children":11520},{"style":2818},[11521],{"type":26,"value":11522},"# presence of the upgrade field in the client request header\n",{"type":21,"tag":399,"props":11524,"children":11525},{"class":605,"line":232},[11526,11531,11536],{"type":21,"tag":399,"props":11527,"children":11528},{"style":630},[11529],{"type":26,"value":11530},"map",{"type":21,"tag":399,"props":11532,"children":11533},{"style":610},[11534],{"type":26,"value":11535}," \\",{"type":21,"tag":399,"props":11537,"children":11539},{"style":11538},"--shiki-default:#B31D28;--shiki-default-font-style:italic;--shiki-dark:#FDAEB7;--shiki-dark-font-style:italic",[11540],{"type":26,"value":11541},"$http_upgrade \\$connection_upgrade {\n",{"type":21,"tag":399,"props":11543,"children":11544},{"class":605,"line":654},[11545,11550],{"type":21,"tag":399,"props":11546,"children":11547},{"style":610},[11548],{"type":26,"value":11549},"default upgrade",{"type":21,"tag":399,"props":11551,"children":11552},{"style":11538},[11553],{"type":26,"value":6581},{"type":21,"tag":399,"props":11555,"children":11556},{"class":605,"line":698},[11557,11561,11566],{"type":21,"tag":399,"props":11558,"children":11559},{"style":644},[11560],{"type":26,"value":10669},{"type":21,"tag":399,"props":11562,"children":11563},{"style":610},[11564],{"type":26,"value":11565}," close",{"type":21,"tag":399,"props":11567,"children":11568},{"style":11538},[11569],{"type":26,"value":6581},{"type":21,"tag":399,"props":11571,"children":11572},{"class":605,"line":737},[11573],{"type":21,"tag":399,"props":11574,"children":11575},{"style":610},[11576],{"type":26,"value":7145},{"type":21,"tag":399,"props":11578,"children":11579},{"class":605,"line":755},[11580],{"type":21,"tag":399,"props":11581,"children":11582},{"emptyLinePlaceholder":2690},[11583],{"type":26,"value":2693},{"type":21,"tag":399,"props":11585,"children":11586},{"class":605,"line":773},[11587],{"type":21,"tag":399,"props":11588,"children":11589},{"style":2818},[11590],{"type":26,"value":11591},"# Create an upstream alias to where we've set daphne to bind to\n",{"type":21,"tag":399,"props":11593,"children":11594},{"class":605,"line":795},[11595],{"type":21,"tag":399,"props":11596,"children":11597},{"style":610},[11598],{"type":26,"value":11599},"upstream yourapp {\n",{"type":21,"tag":399,"props":11601,"children":11602},{"class":605,"line":804},[11603,11608,11613,11618,11623],{"type":21,"tag":399,"props":11604,"children":11605},{"style":610},[11606],{"type":26,"value":11607},"server ",{"type":21,"tag":399,"props":11609,"children":11610},{"style":630},[11611],{"type":26,"value":11612},"127.0",{"type":21,"tag":399,"props":11614,"children":11615},{"style":610},[11616],{"type":26,"value":11617},".0.1:",{"type":21,"tag":399,"props":11619,"children":11620},{"style":630},[11621],{"type":26,"value":11622},"8000",{"type":21,"tag":399,"props":11624,"children":11625},{"style":610},[11626],{"type":26,"value":6581},{"type":21,"tag":399,"props":11628,"children":11629},{"class":605,"line":843},[11630],{"type":21,"tag":399,"props":11631,"children":11632},{"style":610},[11633],{"type":26,"value":7145},{"type":21,"tag":399,"props":11635,"children":11636},{"class":605,"line":882},[11637],{"type":21,"tag":399,"props":11638,"children":11639},{"emptyLinePlaceholder":2690},[11640],{"type":26,"value":2693},{"type":21,"tag":399,"props":11642,"children":11643},{"class":605,"line":900},[11644],{"type":21,"tag":399,"props":11645,"children":11646},{"style":610},[11647],{"type":26,"value":11648},"server {\n",{"type":21,"tag":399,"props":11650,"children":11651},{"class":605,"line":923},[11652],{"type":21,"tag":399,"props":11653,"children":11654},{"emptyLinePlaceholder":2690},[11655],{"type":26,"value":2693},{"type":21,"tag":399,"props":11657,"children":11658},{"class":605,"line":969},[11659,11664,11668],{"type":21,"tag":399,"props":11660,"children":11661},{"style":610},[11662],{"type":26,"value":11663},"listen ",{"type":21,"tag":399,"props":11665,"children":11666},{"style":630},[11667],{"type":26,"value":836},{"type":21,"tag":399,"props":11669,"children":11670},{"style":610},[11671],{"type":26,"value":6581},{"type":21,"tag":399,"props":11673,"children":11674},{"class":605,"line":991},[11675],{"type":21,"tag":399,"props":11676,"children":11677},{"style":2818},[11678],{"type":26,"value":11679},"# If you have a domain name, this is where to add it\n",{"type":21,"tag":399,"props":11681,"children":11682},{"class":605,"line":1013},[11683],{"type":21,"tag":399,"props":11684,"children":11685},{"style":610},[11686],{"type":26,"value":11687},"server_name localhost;\n",{"type":21,"tag":399,"props":11689,"children":11690},{"class":605,"line":1035},[11691],{"type":21,"tag":399,"props":11692,"children":11693},{"emptyLinePlaceholder":2690},[11694],{"type":26,"value":2693},{"type":21,"tag":399,"props":11696,"children":11697},{"class":605,"line":1092},[11698,11703,11707],{"type":21,"tag":399,"props":11699,"children":11700},{"style":610},[11701],{"type":26,"value":11702},"location ",{"type":21,"tag":399,"props":11704,"children":11705},{"style":616},[11706],{"type":26,"value":2944},{"type":21,"tag":399,"props":11708,"children":11709},{"style":610},[11710],{"type":26,"value":8993},{"type":21,"tag":399,"props":11712,"children":11713},{"class":605,"line":1114},[11714],{"type":21,"tag":399,"props":11715,"children":11716},{"style":2818},[11717],{"type":26,"value":11718},"# Pass request to the upstream alias\n",{"type":21,"tag":399,"props":11720,"children":11721},{"class":605,"line":1136},[11722,11727,11732],{"type":21,"tag":399,"props":11723,"children":11724},{"style":610},[11725],{"type":26,"value":11726},"proxy_pass http:",{"type":21,"tag":399,"props":11728,"children":11729},{"style":616},[11730],{"type":26,"value":11731},"//",{"type":21,"tag":399,"props":11733,"children":11734},{"style":610},[11735],{"type":26,"value":11736},"yourapp;\n",{"type":21,"tag":399,"props":11738,"children":11739},{"class":605,"line":1158},[11740],{"type":21,"tag":399,"props":11741,"children":11742},{"emptyLinePlaceholder":2690},[11743],{"type":26,"value":2693},{"type":21,"tag":399,"props":11745,"children":11746},{"class":605,"line":1180},[11747],{"type":21,"tag":399,"props":11748,"children":11749},{"style":2818},[11750],{"type":26,"value":11751},"# Require http version 1.1 to allow for upgrade requests\n",{"type":21,"tag":399,"props":11753,"children":11754},{"class":605,"line":1202},[11755,11760,11765],{"type":21,"tag":399,"props":11756,"children":11757},{"style":610},[11758],{"type":26,"value":11759},"proxy_http_version ",{"type":21,"tag":399,"props":11761,"children":11762},{"style":630},[11763],{"type":26,"value":11764},"1.1",{"type":21,"tag":399,"props":11766,"children":11767},{"style":610},[11768],{"type":26,"value":6581},{"type":21,"tag":399,"props":11770,"children":11771},{"class":605,"line":1211},[11772],{"type":21,"tag":399,"props":11773,"children":11774},{"emptyLinePlaceholder":2690},[11775],{"type":26,"value":2693},{"type":21,"tag":399,"props":11777,"children":11778},{"class":605,"line":1228},[11779],{"type":21,"tag":399,"props":11780,"children":11781},{"style":2818},[11782],{"type":26,"value":11783},"# We want proxy_buffering off for proxying to websockets.\n",{"type":21,"tag":399,"props":11785,"children":11786},{"class":605,"line":1269},[11787],{"type":21,"tag":399,"props":11788,"children":11789},{"style":610},[11790],{"type":26,"value":11791},"proxy_buffering off;\n",{"type":21,"tag":399,"props":11793,"children":11794},{"class":605,"line":1307},[11795],{"type":21,"tag":399,"props":11796,"children":11797},{"emptyLinePlaceholder":2690},[11798],{"type":26,"value":2693},{"type":21,"tag":399,"props":11800,"children":11801},{"class":605,"line":1346},[11802],{"type":21,"tag":399,"props":11803,"children":11804},{"style":2818},[11805],{"type":26,"value":11806},"# http://en.wikipedia.org/wiki/X-Forwarded-For\n",{"type":21,"tag":399,"props":11808,"children":11809},{"class":605,"line":1355},[11810,11815,11819,11824,11828,11833,11838],{"type":21,"tag":399,"props":11811,"children":11812},{"style":610},[11813],{"type":26,"value":11814},"proxy_set_header X",{"type":21,"tag":399,"props":11816,"children":11817},{"style":616},[11818],{"type":26,"value":8677},{"type":21,"tag":399,"props":11820,"children":11821},{"style":610},[11822],{"type":26,"value":11823},"Forwarded",{"type":21,"tag":399,"props":11825,"children":11826},{"style":616},[11827],{"type":26,"value":8677},{"type":21,"tag":399,"props":11829,"children":11830},{"style":610},[11831],{"type":26,"value":11832},"For ",{"type":21,"tag":399,"props":11834,"children":11835},{"style":11538},[11836],{"type":26,"value":11837},"$",{"type":21,"tag":399,"props":11839,"children":11840},{"style":610},[11841],{"type":26,"value":11842},"proxy_add_x_forwarded_for;\n",{"type":21,"tag":399,"props":11844,"children":11845},{"class":605,"line":1364},[11846],{"type":21,"tag":399,"props":11847,"children":11848},{"emptyLinePlaceholder":2690},[11849],{"type":26,"value":2693},{"type":21,"tag":399,"props":11851,"children":11852},{"class":605,"line":1403},[11853],{"type":21,"tag":399,"props":11854,"children":11855},{"style":2818},[11856],{"type":26,"value":11857},"# enable this if you use HTTPS:\n",{"type":21,"tag":399,"props":11859,"children":11860},{"class":605,"line":1442},[11861],{"type":21,"tag":399,"props":11862,"children":11863},{"style":2818},[11864],{"type":26,"value":11865},"# proxy_set_header X-Forwarded-Proto https;\n",{"type":21,"tag":399,"props":11867,"children":11868},{"class":605,"line":1463},[11869],{"type":21,"tag":399,"props":11870,"children":11871},{"emptyLinePlaceholder":2690},[11872],{"type":26,"value":2693},{"type":21,"tag":399,"props":11874,"children":11875},{"class":605,"line":1501},[11876],{"type":21,"tag":399,"props":11877,"children":11878},{"style":2818},[11879],{"type":26,"value":11880},"# pass the Host: header from the client for the sake of redirects\n",{"type":21,"tag":399,"props":11882,"children":11883},{"class":605,"line":1546},[11884,11889,11893],{"type":21,"tag":399,"props":11885,"children":11886},{"style":610},[11887],{"type":26,"value":11888},"proxy_set_header Host ",{"type":21,"tag":399,"props":11890,"children":11891},{"style":11538},[11892],{"type":26,"value":11837},{"type":21,"tag":399,"props":11894,"children":11895},{"style":610},[11896],{"type":26,"value":11897},"http_host;\n",{"type":21,"tag":399,"props":11899,"children":11900},{"class":605,"line":1585},[11901],{"type":21,"tag":399,"props":11902,"children":11903},{"emptyLinePlaceholder":2690},[11904],{"type":26,"value":2693},{"type":21,"tag":399,"props":11906,"children":11907},{"class":605,"line":1624},[11908],{"type":21,"tag":399,"props":11909,"children":11910},{"style":2818},[11911],{"type":26,"value":11912},"# We've set the Host header, so we don't need Nginx to muddle\n",{"type":21,"tag":399,"props":11914,"children":11915},{"class":605,"line":1646},[11916],{"type":21,"tag":399,"props":11917,"children":11918},{"style":2818},[11919],{"type":26,"value":11920},"# about with redirects\n",{"type":21,"tag":399,"props":11922,"children":11923},{"class":605,"line":4599},[11924],{"type":21,"tag":399,"props":11925,"children":11926},{"style":610},[11927],{"type":26,"value":11928},"proxy_redirect off;\n",{"type":21,"tag":399,"props":11930,"children":11931},{"class":605,"line":4608},[11932],{"type":21,"tag":399,"props":11933,"children":11934},{"emptyLinePlaceholder":2690},[11935],{"type":26,"value":2693},{"type":21,"tag":399,"props":11937,"children":11938},{"class":605,"line":4617},[11939],{"type":21,"tag":399,"props":11940,"children":11941},{"style":2818},[11942],{"type":26,"value":11943},"# Depending on the request value, set the Upgrade and\n",{"type":21,"tag":399,"props":11945,"children":11946},{"class":605,"line":4626},[11947],{"type":21,"tag":399,"props":11948,"children":11949},{"style":2818},[11950],{"type":26,"value":11951},"# connection headers\n",{"type":21,"tag":399,"props":11953,"children":11954},{"class":605,"line":4635},[11955,11960,11964],{"type":21,"tag":399,"props":11956,"children":11957},{"style":610},[11958],{"type":26,"value":11959},"proxy_set_header Upgrade ",{"type":21,"tag":399,"props":11961,"children":11962},{"style":11538},[11963],{"type":26,"value":11837},{"type":21,"tag":399,"props":11965,"children":11966},{"style":610},[11967],{"type":26,"value":11968},"http_upgrade;\n",{"type":21,"tag":399,"props":11970,"children":11971},{"class":605,"line":4649},[11972],{"type":21,"tag":399,"props":11973,"children":11974},{"emptyLinePlaceholder":2690},[11975],{"type":26,"value":2693},{"type":21,"tag":399,"props":11977,"children":11978},{"class":605,"line":4657},[11979,11984,11988],{"type":21,"tag":399,"props":11980,"children":11981},{"style":610},[11982],{"type":26,"value":11983},"proxy_set_header Connection ",{"type":21,"tag":399,"props":11985,"children":11986},{"style":11538},[11987],{"type":26,"value":11837},{"type":21,"tag":399,"props":11989,"children":11990},{"style":610},[11991],{"type":26,"value":11992},"connection_upgrade;\n",{"type":21,"tag":399,"props":11994,"children":11995},{"class":605,"line":4686},[11996],{"type":21,"tag":399,"props":11997,"children":11998},{"style":610},[11999],{"type":26,"value":7145},{"type":21,"tag":399,"props":12001,"children":12003},{"class":605,"line":12002},48,[12004],{"type":21,"tag":399,"props":12005,"children":12006},{"style":610},[12007],{"type":26,"value":7145},{"type":21,"tag":12009,"props":12010,"children":12012},"h5",{"id":12011},"freebsd-note",[12013],{"type":26,"value":12014},"FreeBSD note:",{"type":21,"tag":22,"props":12016,"children":12017},{},[12018],{"type":26,"value":12019},"Chances are good that your Nginx conf may be in a directory like /usr/local/etc/nginx - you'll either need to alter it to support imports, or simply edit the file to include the above directives.",{"type":21,"tag":22,"props":12021,"children":12022},{},[12023,12025,12031,12033,12039],{"type":26,"value":12024},"Now, check your config for sanity with ",{"type":21,"tag":43,"props":12026,"children":12028},{"className":12027},[],[12029],{"type":26,"value":12030},"nginx -t",{"type":26,"value":12032}," and, if all is well, restart the nginx service ",{"type":21,"tag":43,"props":12034,"children":12036},{"className":12035},[],[12037],{"type":26,"value":12038},"service nginx restart",{"type":26,"value":2458},{"type":21,"tag":22,"props":12041,"children":12042},{},[12043],{"type":26,"value":12044},"Now, push your changes to the server, and navigate to it in your browser. If is all well, you should see the simple message from the http.request consumer you set up. What is this feeling. Is this... joy?",{"type":21,"tag":190,"props":12046,"children":12048},{"id":12047},"next-time",[12049],{"type":26,"value":12050},"Next Time",{"type":21,"tag":22,"props":12052,"children":12053},{},[12054,12056,12061],{"type":26,"value":12055},"Well, now, all set up with a stack that wouldn't make a dev-op cry, not bad! It... doesn't really ",{"type":21,"tag":166,"props":12057,"children":12058},{},[12059],{"type":26,"value":12060},"do",{"type":26,"value":12062}," anything yet, though, does it? Next time, we'll be looking at getting some simple websocket communication up and running.",{"type":21,"tag":22,"props":12064,"children":12065},{},[12066,12068,12073],{"type":26,"value":12067},"Not a chat server, though. Everyone does a chat server. Instead, let's try something a little more ambitious - how about real time media from an ",{"type":21,"tag":166,"props":12069,"children":12070},{},[12071],{"type":26,"value":12072},"rtsp stream",{"type":26,"value":12074},", straight into the browser?",{"type":21,"tag":22,"props":12076,"children":12077},{},[12078,12080,12086],{"type":26,"value":12079},"If you're running into any issues with the above, I suggest that a quick question to the good folks at ",{"type":21,"tag":206,"props":12081,"children":12083},{"href":12082},"stackoverflow.com",[12084],{"type":26,"value":12085},"Stack Overflow",{"type":26,"value":12087}," will likely avail you best; and as always, if you notice any mistakes in the article, or just have something to say, the comment section awaits.",{"type":21,"tag":2495,"props":12089,"children":12090},{},[12091],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":12093},[12094,12095,12096,12097,12098,12099,12100],{"id":9511,"depth":232,"text":9514},{"id":9921,"depth":232,"text":9924},{"id":10021,"depth":232,"text":10024},{"id":10429,"depth":232,"text":10432},{"id":10461,"depth":232,"text":10464},{"id":11458,"depth":232,"text":11461},{"id":12047,"depth":239,"text":12050},"content:ckeefer:2016-4:djangochannels2.md","ckeefer/2016-4/djangochannels2.md","ckeefer/2016-4/djangochannels2",{"user":9465,"name":9466},{"_path":12106,"_dir":12107,"_draft":7,"_partial":7,"_locale":8,"title":12108,"description":12109,"publishDate":12110,"tags":12111,"excerpt":12109,"body":12112,"_type":240,"_id":13766,"_source":242,"_file":13767,"_stem":13768,"_extension":245,"author":13769},"/ckeefer/2016-3/djangochannels1","2016-3","Django Channels: From the Ground Up - Part 1","You stare mournfully into the mass of code you've inherited. At some point, it's clear, the requirements called for the server to push information to the client, because there's an unholy mix of Server-Side Events, long-polling, hidden iframes and even a Java applet in there, all supporting some level of long-term connectivity with the server. It's almost fascinating in its barely functional hideousness, and you would be inclined to leave well enough alone... except for the new feature specifications you've been assigned, which require the client to be able to send data back to the server in response to the received events, in as close to real-time as you can get.","2016-06-13",[2537,16,9474],{"type":18,"children":12113,"toc":13753},[12114,12135,12149,12155,12199,12204,12209,12223,12236,12249,12254,12261,12267,12281,12286,12292,12306,12311,12316,12354,12359,12377,12382,12387,12393,12398,12403,12494,12499,12538,12543,12567,12589,12594,12600,12605,12610,12636,12647,12652,12700,12706,12719,12724,12918,12923,12967,12971,13015,13021,13034,13062,13076,13081,13086,13097,13103,13117,13122,13161,13166,13207,13220,13224,13457,13461,13666,13686,13707,13719,13725,13737,13749],{"type":21,"tag":22,"props":12115,"children":12116},{},[12117,12119,12126,12128,12133],{"type":26,"value":12118},"You stare mournfully into the mass of code you've inherited. At some point, it's clear, the requirements called for the server to push information to the client, because there's an unholy mix of ",{"type":21,"tag":206,"props":12120,"children":12123},{"href":12121,"rel":12122},"https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events",[210],[12124],{"type":26,"value":12125},"Server-Side Events",{"type":26,"value":12127},", long-polling, hidden iframes and even a Java applet in there, all supporting some level of long-term connectivity with the server. It's almost fascinating in its barely functional hideousness, and you would be inclined to leave well enough alone... except for the ",{"type":21,"tag":166,"props":12129,"children":12130},{},[12131],{"type":26,"value":12132},"new",{"type":26,"value":12134}," feature specifications you've been assigned, which require the client to be able to send data back to the server in response to the received events, in as close to real-time as you can get.",{"type":21,"tag":22,"props":12136,"children":12137},{},[12138,12140,12147],{"type":26,"value":12139},"It's time for a major overhaul. Only ",{"type":21,"tag":206,"props":12141,"children":12144},{"href":12142,"rel":12143},"https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API",[210],[12145],{"type":26,"value":12146},"Websockets",{"type":26,"value":12148}," can save us now. And, as it so happens, one of your favourite frameworks just added support for real-time, bi-directional communication...",{"type":21,"tag":190,"props":12150,"children":12152},{"id":12151},"adding-more-batteries",[12153],{"type":26,"value":12154},"Adding More Batteries",{"type":21,"tag":22,"props":12156,"children":12157},{},[12158,12160,12165,12167,12172,12174,12180,12182,12189,12191,12198],{"type":26,"value":12159},"If you've been paying attention to the ",{"type":21,"tag":206,"props":12161,"children":12163},{"href":2601,"rel":12162},[210],[12164],{"type":26,"value":2605},{"type":26,"value":12166}," project, you've probably heard about ",{"type":21,"tag":206,"props":12168,"children":12170},{"href":9490,"rel":12169},[210],[12171],{"type":26,"value":9494},{"type":26,"value":12173},". A new server gateway interface (",{"type":21,"tag":206,"props":12175,"children":12178},{"href":12176,"rel":12177},"https://channels.readthedocs.io/en/latest/asgi.html",[210],[12179],{"type":26,"value":3086},{"type":26,"value":12181},"), a new server (",{"type":21,"tag":206,"props":12183,"children":12186},{"href":12184,"rel":12185},"https://github.com/andrewgodwin/daphne",[210],[12187],{"type":26,"value":12188},"Daphne",{"type":26,"value":12190},") and, as a result, new functionality - including support for Websockets and (at some near-future point) ",{"type":21,"tag":206,"props":12192,"children":12195},{"href":12193,"rel":12194},"https://en.wikipedia.org/wiki/HTTP/2",[210],[12196],{"type":26,"value":12197},"HTTP2",{"type":26,"value":2458},{"type":21,"tag":22,"props":12200,"children":12201},{},[12202],{"type":26,"value":12203},"Support for websockets isn't new - Node.js (and thus, Express, Meteor, Sails, etc.) had them pretty much out of the box, and its event-based model is well suited to them. Even on the Python side of things, if you needed websockets, you could have them - Gevent, Tornado/Cyclone and Twisted had your back, amongst others. In fact, the preferred server for Django Channels, Daphne, is based on Twisted.",{"type":21,"tag":22,"props":12205,"children":12206},{},[12207],{"type":26,"value":12208},"So, the big news here isn't OMG Websockets, but rather Websockets + Django. Pyramid and Flask fans may scoff, but Django's batteries-included approach echoes that of Python itself, and enables some solid work that tends to be easier to maintain than, for instance, the special snowflakes that a large Flask project can end up as (your mileage may vary).",{"type":21,"tag":22,"props":12210,"children":12211},{},[12212,12214,12221],{"type":26,"value":12213},"So, if you're sold on Django, there's good reason to be excited about Channels. Although its inclusion in Django core has been ",{"type":21,"tag":206,"props":12215,"children":12218},{"href":12216,"rel":12217},"https://groups.google.com/d/topic/django-developers/QRd4v7OErT8/discussion",[210],[12219],{"type":26,"value":12220},"deferred",{"type":26,"value":12222}," for now, chances are good that it will see integration with the Django core project at some near-future point, making now a good time to get up to speed.",{"type":21,"tag":22,"props":12224,"children":12225},{},[12226,12228,12234],{"type":26,"value":12227},"For this short series of articles, we'll be looking at doing just that. While the ",{"type":21,"tag":206,"props":12229,"children":12231},{"href":9490,"rel":12230},[210],[12232],{"type":26,"value":12233},"Django Channels documentation",{"type":26,"value":12235}," lives up to the high standard set by the core Django documentation, it doesn't spend much time describing what a production-ready setup might look like, or how to achieve it. Additionally, like most tutorials for websockets, it focuses on a simple echo or chat server. We're going to go a step beyond, and take a look at what we need to setup real-time streaming video in the browser.",{"type":21,"tag":22,"props":12237,"children":12238},{},[12239,12241,12247],{"type":26,"value":12240},"For the first two articles, though, we'll focus on how to get our shiny new Django Channels installation up and running. We're going to cover a lot of ground, so we won't be able to focus too closely on the details. If you run into trouble, a quick search might do it for you - if not, I suggest you bring your question to the good people at ",{"type":21,"tag":206,"props":12242,"children":12245},{"href":12243,"rel":12244},"https://stackoverflow.com",[210],[12246],{"type":26,"value":12085},{"type":26,"value":12248},"; and if you note any mistakes in the article, or just have something to say, the comment section below awaits you.",{"type":21,"tag":22,"props":12250,"children":12251},{},[12252],{"type":26,"value":12253},"Let's get started, shall we?",{"type":21,"tag":12255,"props":12256,"children":12258},"h4",{"id":12257},"penguins-or-daemons",[12259],{"type":26,"value":12260},"Penguins or Daemons?",{"type":21,"tag":12009,"props":12262,"children":12264},{"id":12263},"its-true-penguins-are-cuter-and-ive-never-had-to-kill-9-a-penguin",[12265],{"type":26,"value":12266},"It's true, penguins are cuter, and I've never had to kill -9 a penguin.",{"type":21,"tag":22,"props":12268,"children":12269},{},[12270,12272,12279],{"type":26,"value":12271},"One small additional wrinkle to the following documentation is that we're going to add some details for this same setup with ",{"type":21,"tag":206,"props":12273,"children":12276},{"href":12274,"rel":12275},"https://www.freebsd.org/",[210],[12277],{"type":26,"value":12278},"FreeBSD",{"type":26,"value":12280},". I've had some recent projects that have caused me to dip my toes into the BSD pool, and I noticed there's a relative lack of documentation for this sort of thing there, so we're going to help rectify that a little.",{"type":21,"tag":22,"props":12282,"children":12283},{},[12284],{"type":26,"value":12285},"Don't worry if you're planning to use a Linux flavour - in most cases, the instructions are either identical, or the differences will be called out.",{"type":21,"tag":190,"props":12287,"children":12289},{"id":12288},"from-the-ground-up",[12290],{"type":26,"value":12291},"From the Ground Up",{"type":21,"tag":22,"props":12293,"children":12294},{},[12295,12297,12304],{"type":26,"value":12296},"Assumption: You have a relatively modern unix-like operating system. Windows folks, as ever in server-land, I'm afraid you're on your own (maybe now's a good time to look into ",{"type":21,"tag":206,"props":12298,"children":12301},{"href":12299,"rel":12300},"https://msdn.microsoft.com/en-us/commandline/wsl/about",[210],[12302],{"type":26,"value":12303},"Ubuntu on Windows",{"type":26,"value":12305},"?).",{"type":21,"tag":22,"props":12307,"children":12308},{},[12309],{"type":26,"value":12310},"We're going to be re-treading some ground that is likely very familiar for some of you, so bear with me - we're going to start up from scratch here.",{"type":21,"tag":22,"props":12312,"children":12313},{},[12314],{"type":26,"value":12315},"Now, let's discuss our stack. We're going to go with a set of software that should look familiar to anyone who's set up a basic, fairly production-worthy server with Django:",{"type":21,"tag":60,"props":12317,"children":12318},{},[12319,12324,12329,12334,12339,12344,12349],{"type":21,"tag":64,"props":12320,"children":12321},{},[12322],{"type":26,"value":12323},"Python (2.7+ or 3.4+);",{"type":21,"tag":64,"props":12325,"children":12326},{},[12327],{"type":26,"value":12328},"Virtualenv;",{"type":21,"tag":64,"props":12330,"children":12331},{},[12332],{"type":26,"value":12333},"Pip;",{"type":21,"tag":64,"props":12335,"children":12336},{},[12337],{"type":26,"value":12338},"Nginx;",{"type":21,"tag":64,"props":12340,"children":12341},{},[12342],{"type":26,"value":12343},"Postgresql;",{"type":21,"tag":64,"props":12345,"children":12346},{},[12347],{"type":26,"value":12348},"Supervisord;",{"type":21,"tag":64,"props":12350,"children":12351},{},[12352],{"type":26,"value":12353},"Django (1.8+).",{"type":21,"tag":22,"props":12355,"children":12356},{},[12357],{"type":26,"value":12358},"And adding some new elements to support Channels:",{"type":21,"tag":60,"props":12360,"children":12361},{},[12362,12367,12372],{"type":21,"tag":64,"props":12363,"children":12364},{},[12365],{"type":26,"value":12366},"Redis;",{"type":21,"tag":64,"props":12368,"children":12369},{},[12370],{"type":26,"value":12371},"Daphne;",{"type":21,"tag":64,"props":12373,"children":12374},{},[12375],{"type":26,"value":12376},"channels.",{"type":21,"tag":22,"props":12378,"children":12379},{},[12380],{"type":26,"value":12381},"Let's go through the setup step-by-step, and discuss the how and, in some cases, the why of our software stack.",{"type":21,"tag":22,"props":12383,"children":12384},{},[12385],{"type":26,"value":12386},"For this first part, we're going to go get us far enough to run Django using our shiny new ASGI server, via the usual development invocation; and in part 2, we'll be getting our stack somewhere approaching production ready.",{"type":21,"tag":87,"props":12388,"children":12390},{"id":12389},"package-installation",[12391],{"type":26,"value":12392},"Package Installation",{"type":21,"tag":22,"props":12394,"children":12395},{},[12396],{"type":26,"value":12397},"This step is nice and straightforward. Ask your friendly neighbourhood package manager for more details.",{"type":21,"tag":22,"props":12399,"children":12400},{},[12401],{"type":26,"value":12402},"Debian/Ubuntu one-liner:",{"type":21,"tag":595,"props":12404,"children":12408},{"className":12405,"code":12406,"language":12407,"meta":8,"style":8},"language-shell shiki shiki-themes github-light github-dark","sudo apt-get -y -q install python python-dev python-pip python-virtualenv libpq-dev postgresql postgresql-contrib nginx supervisor python-software-properties redis-server\n","shell",[12409],{"type":21,"tag":43,"props":12410,"children":12411},{"__ignoreMap":8},[12412],{"type":21,"tag":399,"props":12413,"children":12414},{"class":605,"line":606},[12415,12420,12425,12430,12435,12439,12444,12449,12454,12459,12464,12469,12474,12479,12484,12489],{"type":21,"tag":399,"props":12416,"children":12417},{"style":2704},[12418],{"type":26,"value":12419},"sudo",{"type":21,"tag":399,"props":12421,"children":12422},{"style":644},[12423],{"type":26,"value":12424}," apt-get",{"type":21,"tag":399,"props":12426,"children":12427},{"style":630},[12428],{"type":26,"value":12429}," -y",{"type":21,"tag":399,"props":12431,"children":12432},{"style":630},[12433],{"type":26,"value":12434}," -q",{"type":21,"tag":399,"props":12436,"children":12437},{"style":644},[12438],{"type":26,"value":9639},{"type":21,"tag":399,"props":12440,"children":12441},{"style":644},[12442],{"type":26,"value":12443}," python",{"type":21,"tag":399,"props":12445,"children":12446},{"style":644},[12447],{"type":26,"value":12448}," python-dev",{"type":21,"tag":399,"props":12450,"children":12451},{"style":644},[12452],{"type":26,"value":12453}," python-pip",{"type":21,"tag":399,"props":12455,"children":12456},{"style":644},[12457],{"type":26,"value":12458}," python-virtualenv",{"type":21,"tag":399,"props":12460,"children":12461},{"style":644},[12462],{"type":26,"value":12463}," libpq-dev",{"type":21,"tag":399,"props":12465,"children":12466},{"style":644},[12467],{"type":26,"value":12468}," postgresql",{"type":21,"tag":399,"props":12470,"children":12471},{"style":644},[12472],{"type":26,"value":12473}," postgresql-contrib",{"type":21,"tag":399,"props":12475,"children":12476},{"style":644},[12477],{"type":26,"value":12478}," nginx",{"type":21,"tag":399,"props":12480,"children":12481},{"style":644},[12482],{"type":26,"value":12483}," supervisor",{"type":21,"tag":399,"props":12485,"children":12486},{"style":644},[12487],{"type":26,"value":12488}," python-software-properties",{"type":21,"tag":399,"props":12490,"children":12491},{"style":644},[12492],{"type":26,"value":12493}," redis-server\n",{"type":21,"tag":22,"props":12495,"children":12496},{},[12497],{"type":26,"value":12498},"FreeBSD example:",{"type":21,"tag":595,"props":12500,"children":12502},{"className":12405,"code":12501,"language":12407,"meta":8,"style":8},"cd /usr/ports/databases/postgresql95-server/ && make install clean\n",[12503],{"type":21,"tag":43,"props":12504,"children":12505},{"__ignoreMap":8},[12506],{"type":21,"tag":399,"props":12507,"children":12508},{"class":605,"line":606},[12509,12514,12519,12524,12529,12533],{"type":21,"tag":399,"props":12510,"children":12511},{"style":630},[12512],{"type":26,"value":12513},"cd",{"type":21,"tag":399,"props":12515,"children":12516},{"style":644},[12517],{"type":26,"value":12518}," /usr/ports/databases/postgresql95-server/",{"type":21,"tag":399,"props":12520,"children":12521},{"style":610},[12522],{"type":26,"value":12523}," && ",{"type":21,"tag":399,"props":12525,"children":12526},{"style":2704},[12527],{"type":26,"value":12528},"make",{"type":21,"tag":399,"props":12530,"children":12531},{"style":644},[12532],{"type":26,"value":9639},{"type":21,"tag":399,"props":12534,"children":12535},{"style":644},[12536],{"type":26,"value":12537}," clean\n",{"type":21,"tag":22,"props":12539,"children":12540},{},[12541],{"type":26,"value":12542},"OR",{"type":21,"tag":595,"props":12544,"children":12546},{"className":12405,"code":12545,"language":12407,"meta":8,"style":8},"pkg install postgresql95-server\n",[12547],{"type":21,"tag":43,"props":12548,"children":12549},{"__ignoreMap":8},[12550],{"type":21,"tag":399,"props":12551,"children":12552},{"class":605,"line":606},[12553,12558,12562],{"type":21,"tag":399,"props":12554,"children":12555},{"style":2704},[12556],{"type":26,"value":12557},"pkg",{"type":21,"tag":399,"props":12559,"children":12560},{"style":644},[12561],{"type":26,"value":9639},{"type":21,"tag":399,"props":12563,"children":12564},{"style":644},[12565],{"type":26,"value":12566}," postgresql95-server\n",{"type":21,"tag":22,"props":12568,"children":12569},{},[12570,12572,12578,12580,12587],{"type":26,"value":12571},"and repeat for python27 (e.g. ",{"type":21,"tag":43,"props":12573,"children":12575},{"className":12574},[],[12576],{"type":26,"value":12577},"pkg install python27",{"type":26,"value":12579},"), nginx, supervisord, etc. I suggest searching ",{"type":21,"tag":206,"props":12581,"children":12584},{"href":12582,"rel":12583},"https://www.freshports.org/",[210],[12585],{"type":26,"value":12586},"FreshPorts",{"type":26,"value":12588}," for the specific package names.",{"type":21,"tag":22,"props":12590,"children":12591},{},[12592],{"type":26,"value":12593},"If you're on FreeBSD, you'll also want to add the appropriate enable commands to rc.conf to allow these applications to start with the server.",{"type":21,"tag":87,"props":12595,"children":12597},{"id":12596},"user-virtualenv-setup",[12598],{"type":26,"value":12599},"User & VirtualEnv setup",{"type":21,"tag":22,"props":12601,"children":12602},{},[12603],{"type":26,"value":12604},"Now that we have our system packages installed, let's set up our python virtual environment in preparation for getting our python packages into place.",{"type":21,"tag":22,"props":12606,"children":12607},{},[12608],{"type":26,"value":12609},"First, we'll create a new, low-priviledged user who we want to run our server software - running web-facing processes as root can be a security faux pas.",{"type":21,"tag":22,"props":12611,"children":12612},{},[12613,12619,12621,12626,12628,12634],{"type":21,"tag":43,"props":12614,"children":12616},{"className":12615},[],[12617],{"type":26,"value":12618},"sudo adduser web",{"type":26,"value":12620}," (If using FreeBSD, drop the ",{"type":21,"tag":43,"props":12622,"children":12624},{"className":12623},[],[12625],{"type":26,"value":12419},{"type":26,"value":12627}," and execute this (and other commands requiring sudo) as root by ",{"type":21,"tag":43,"props":12629,"children":12631},{"className":12630},[],[12632],{"type":26,"value":12633},"su",{"type":26,"value":12635},"ing into root account, or else install sudo package).",{"type":21,"tag":22,"props":12637,"children":12638},{},[12639,12641],{"type":26,"value":12640},"Let's su in as this user... ",{"type":21,"tag":43,"props":12642,"children":12644},{"className":12643},[],[12645],{"type":26,"value":12646},"su web",{"type":21,"tag":22,"props":12648,"children":12649},{},[12650],{"type":26,"value":12651},"We're now going to drop our virtualenv directory (and, in a moment, our deploy directory) into this users home dir - we don't have to do it this way, of course, but this avoids some bothersome chmod'ing and chown'ing we'd have to do otherwise.",{"type":21,"tag":595,"props":12653,"children":12655},{"className":9530,"code":12654,"language":9532,"meta":8,"style":8},"mkdir -p /home/web/venv/\n\nvirtualenv --no-site-packages /home/web/venv/\n",[12656],{"type":21,"tag":43,"props":12657,"children":12658},{"__ignoreMap":8},[12659,12676,12683],{"type":21,"tag":399,"props":12660,"children":12661},{"class":605,"line":606},[12662,12667,12671],{"type":21,"tag":399,"props":12663,"children":12664},{"style":2704},[12665],{"type":26,"value":12666},"mkdir",{"type":21,"tag":399,"props":12668,"children":12669},{"style":630},[12670],{"type":26,"value":10236},{"type":21,"tag":399,"props":12672,"children":12673},{"style":644},[12674],{"type":26,"value":12675}," /home/web/venv/\n",{"type":21,"tag":399,"props":12677,"children":12678},{"class":605,"line":239},[12679],{"type":21,"tag":399,"props":12680,"children":12681},{"emptyLinePlaceholder":2690},[12682],{"type":26,"value":2693},{"type":21,"tag":399,"props":12684,"children":12685},{"class":605,"line":232},[12686,12691,12696],{"type":21,"tag":399,"props":12687,"children":12688},{"style":2704},[12689],{"type":26,"value":12690},"virtualenv",{"type":21,"tag":399,"props":12692,"children":12693},{"style":630},[12694],{"type":26,"value":12695}," --no-site-packages",{"type":21,"tag":399,"props":12697,"children":12698},{"style":644},[12699],{"type":26,"value":12675},{"type":21,"tag":87,"props":12701,"children":12703},{"id":12702},"python-package-installation",[12704],{"type":26,"value":12705},"Python Package Installation",{"type":21,"tag":22,"props":12707,"children":12708},{},[12709,12711,12718],{"type":26,"value":12710},"Now that we have our virtualenv in place, we can install our python packages via pip. If we follow the git deploy setup, below, these can also be automatically installed/upgraded for us by processing a ",{"type":21,"tag":206,"props":12712,"children":12715},{"href":12713,"rel":12714},"https://pip.readthedocs.io/en/1.1/requirements.html",[210],[12716],{"type":26,"value":12717},"requirements.txt file",{"type":26,"value":2458},{"type":21,"tag":22,"props":12720,"children":12721},{},[12722],{"type":26,"value":12723},"Let's make a requirements.txt file that looks like the following:",{"type":21,"tag":595,"props":12725,"children":12727},{"className":12405,"code":12726,"language":12407,"meta":8,"style":8},"asgi-redis==0.12.0\nasgiref==0.13.0\nautobahn==0.14.1\nchannels==0.14.0\ndaphne==0.12.1\nDjango>=1.9,=2.6,\u003C2.7\nredis==2.10.5\nsix==1.10.0\nTwisted==16.2.0\ntxaio==2.5.1\nzope.interface==4.1.3\n",[12728],{"type":21,"tag":43,"props":12729,"children":12730},{"__ignoreMap":8},[12731,12748,12765,12782,12799,12816,12838,12854,12871,12888,12905],{"type":21,"tag":399,"props":12732,"children":12733},{"class":605,"line":606},[12734,12739,12743],{"type":21,"tag":399,"props":12735,"children":12736},{"style":610},[12737],{"type":26,"value":12738},"asgi-redis",{"type":21,"tag":399,"props":12740,"children":12741},{"style":616},[12742],{"type":26,"value":619},{"type":21,"tag":399,"props":12744,"children":12745},{"style":644},[12746],{"type":26,"value":12747},"=0.12.0\n",{"type":21,"tag":399,"props":12749,"children":12750},{"class":605,"line":239},[12751,12756,12760],{"type":21,"tag":399,"props":12752,"children":12753},{"style":610},[12754],{"type":26,"value":12755},"asgiref",{"type":21,"tag":399,"props":12757,"children":12758},{"style":616},[12759],{"type":26,"value":619},{"type":21,"tag":399,"props":12761,"children":12762},{"style":644},[12763],{"type":26,"value":12764},"=0.13.0\n",{"type":21,"tag":399,"props":12766,"children":12767},{"class":605,"line":232},[12768,12773,12777],{"type":21,"tag":399,"props":12769,"children":12770},{"style":610},[12771],{"type":26,"value":12772},"autobahn",{"type":21,"tag":399,"props":12774,"children":12775},{"style":616},[12776],{"type":26,"value":619},{"type":21,"tag":399,"props":12778,"children":12779},{"style":644},[12780],{"type":26,"value":12781},"=0.14.1\n",{"type":21,"tag":399,"props":12783,"children":12784},{"class":605,"line":654},[12785,12790,12794],{"type":21,"tag":399,"props":12786,"children":12787},{"style":610},[12788],{"type":26,"value":12789},"channels",{"type":21,"tag":399,"props":12791,"children":12792},{"style":616},[12793],{"type":26,"value":619},{"type":21,"tag":399,"props":12795,"children":12796},{"style":644},[12797],{"type":26,"value":12798},"=0.14.0\n",{"type":21,"tag":399,"props":12800,"children":12801},{"class":605,"line":698},[12802,12807,12811],{"type":21,"tag":399,"props":12803,"children":12804},{"style":610},[12805],{"type":26,"value":12806},"daphne",{"type":21,"tag":399,"props":12808,"children":12809},{"style":616},[12810],{"type":26,"value":619},{"type":21,"tag":399,"props":12812,"children":12813},{"style":644},[12814],{"type":26,"value":12815},"=0.12.1\n",{"type":21,"tag":399,"props":12817,"children":12818},{"class":605,"line":737},[12819,12823,12828,12833],{"type":21,"tag":399,"props":12820,"children":12821},{"style":2704},[12822],{"type":26,"value":2605},{"type":21,"tag":399,"props":12824,"children":12825},{"style":610},[12826],{"type":26,"value":12827},">",{"type":21,"tag":399,"props":12829,"children":12830},{"style":644},[12831],{"type":26,"value":12832},"=1.9,=2.6,",{"type":21,"tag":399,"props":12834,"children":12835},{"style":610},[12836],{"type":26,"value":12837},"\u003C2.7\n",{"type":21,"tag":399,"props":12839,"children":12840},{"class":605,"line":755},[12841,12845,12849],{"type":21,"tag":399,"props":12842,"children":12843},{"style":610},[12844],{"type":26,"value":5597},{"type":21,"tag":399,"props":12846,"children":12847},{"style":616},[12848],{"type":26,"value":619},{"type":21,"tag":399,"props":12850,"children":12851},{"style":644},[12852],{"type":26,"value":12853},"=2.10.5\n",{"type":21,"tag":399,"props":12855,"children":12856},{"class":605,"line":773},[12857,12862,12866],{"type":21,"tag":399,"props":12858,"children":12859},{"style":610},[12860],{"type":26,"value":12861},"six",{"type":21,"tag":399,"props":12863,"children":12864},{"style":616},[12865],{"type":26,"value":619},{"type":21,"tag":399,"props":12867,"children":12868},{"style":644},[12869],{"type":26,"value":12870},"=1.10.0\n",{"type":21,"tag":399,"props":12872,"children":12873},{"class":605,"line":795},[12874,12879,12883],{"type":21,"tag":399,"props":12875,"children":12876},{"style":610},[12877],{"type":26,"value":12878},"Twisted",{"type":21,"tag":399,"props":12880,"children":12881},{"style":616},[12882],{"type":26,"value":619},{"type":21,"tag":399,"props":12884,"children":12885},{"style":644},[12886],{"type":26,"value":12887},"=16.2.0\n",{"type":21,"tag":399,"props":12889,"children":12890},{"class":605,"line":804},[12891,12896,12900],{"type":21,"tag":399,"props":12892,"children":12893},{"style":610},[12894],{"type":26,"value":12895},"txaio",{"type":21,"tag":399,"props":12897,"children":12898},{"style":616},[12899],{"type":26,"value":619},{"type":21,"tag":399,"props":12901,"children":12902},{"style":644},[12903],{"type":26,"value":12904},"=2.5.1\n",{"type":21,"tag":399,"props":12906,"children":12907},{"class":605,"line":843},[12908,12913],{"type":21,"tag":399,"props":12909,"children":12910},{"style":2704},[12911],{"type":26,"value":12912},"zope.interface",{"type":21,"tag":399,"props":12914,"children":12915},{"style":644},[12916],{"type":26,"value":12917},"==4.1.3\n",{"type":21,"tag":22,"props":12919,"children":12920},{},[12921],{"type":26,"value":12922},"Now, we can activate the virtual environment and install these requirements with pip like so:",{"type":21,"tag":595,"props":12924,"children":12926},{"className":12405,"code":12925,"language":12407,"meta":8,"style":8},"source /home/web/venv/bin/activate\npip install -r requirements.txt\ndeactivate\n",[12927],{"type":21,"tag":43,"props":12928,"children":12929},{"__ignoreMap":8},[12930,12941,12960],{"type":21,"tag":399,"props":12931,"children":12932},{"class":605,"line":606},[12933,12937],{"type":21,"tag":399,"props":12934,"children":12935},{"style":630},[12936],{"type":26,"value":9598},{"type":21,"tag":399,"props":12938,"children":12939},{"style":644},[12940],{"type":26,"value":9603},{"type":21,"tag":399,"props":12942,"children":12943},{"class":605,"line":239},[12944,12948,12952,12956],{"type":21,"tag":399,"props":12945,"children":12946},{"style":2704},[12947],{"type":26,"value":9634},{"type":21,"tag":399,"props":12949,"children":12950},{"style":644},[12951],{"type":26,"value":9639},{"type":21,"tag":399,"props":12953,"children":12954},{"style":630},[12955],{"type":26,"value":9649},{"type":21,"tag":399,"props":12957,"children":12958},{"style":644},[12959],{"type":26,"value":9654},{"type":21,"tag":399,"props":12961,"children":12962},{"class":605,"line":232},[12963],{"type":21,"tag":399,"props":12964,"children":12965},{"style":2704},[12966],{"type":26,"value":9694},{"type":21,"tag":22,"props":12968,"children":12969},{},[12970],{"type":26,"value":9736},{"type":21,"tag":595,"props":12972,"children":12974},{"className":12405,"code":12973,"language":12407,"meta":8,"style":8},"source /home/web/venv/bin/activate.csh\npip install -r requirements.txt\ndeactivate\n",[12975],{"type":21,"tag":43,"props":12976,"children":12977},{"__ignoreMap":8},[12978,12989,13008],{"type":21,"tag":399,"props":12979,"children":12980},{"class":605,"line":606},[12981,12985],{"type":21,"tag":399,"props":12982,"children":12983},{"style":630},[12984],{"type":26,"value":9598},{"type":21,"tag":399,"props":12986,"children":12987},{"style":644},[12988],{"type":26,"value":9795},{"type":21,"tag":399,"props":12990,"children":12991},{"class":605,"line":239},[12992,12996,13000,13004],{"type":21,"tag":399,"props":12993,"children":12994},{"style":2704},[12995],{"type":26,"value":9634},{"type":21,"tag":399,"props":12997,"children":12998},{"style":644},[12999],{"type":26,"value":9639},{"type":21,"tag":399,"props":13001,"children":13002},{"style":630},[13003],{"type":26,"value":9649},{"type":21,"tag":399,"props":13005,"children":13006},{"style":644},[13007],{"type":26,"value":9654},{"type":21,"tag":399,"props":13009,"children":13010},{"class":605,"line":232},[13011],{"type":21,"tag":399,"props":13012,"children":13013},{"style":2704},[13014],{"type":26,"value":9694},{"type":21,"tag":87,"props":13016,"children":13018},{"id":13017},"django-project-setup",[13019],{"type":26,"value":13020},"Django Project Setup",{"type":21,"tag":22,"props":13022,"children":13023},{},[13024,13026,13033],{"type":26,"value":13025},"Now, we're going to get a bare minimal Django project structure in place. Thankfully, Django has some built-in ability to generate it's own boilerplate, which we'll take advantage of. For this, I refer you to the ",{"type":21,"tag":206,"props":13027,"children":13030},{"href":13028,"rel":13029},"https://docs.djangoproject.com/en/1.9/intro/tutorial01/#creating-a-project",[210],[13031],{"type":26,"value":13032},"official Django docs",{"type":26,"value":2458},{"type":21,"tag":22,"props":13035,"children":13036},{},[13037,13039,13044,13046,13052,13054,13060],{"type":26,"value":13038},"For our purposes, the only file of interest is ",{"type":21,"tag":43,"props":13040,"children":13042},{"className":13041},[],[13043],{"type":26,"value":10502},{"type":26,"value":13045}," in your app directory. Open that up in your favourite editor, and find the ",{"type":21,"tag":43,"props":13047,"children":13049},{"className":13048},[],[13050],{"type":26,"value":13051},"INSTALLED_APPS",{"type":26,"value":13053}," tuple. Installing channels into Django is as simple as adding ",{"type":21,"tag":43,"props":13055,"children":13057},{"className":13056},[],[13058],{"type":26,"value":13059},"'channels'",{"type":26,"value":13061}," to the end of the tuple.",{"type":21,"tag":22,"props":13063,"children":13064},{},[13065,13067,13074],{"type":26,"value":13066},"You may also need/want to add your 'app_name' to this tuple, especially if you need static file discovery. You can take a look at the ",{"type":21,"tag":206,"props":13068,"children":13071},{"href":13069,"rel":13070},"https://channels.readthedocs.io/en/latest/installation.html",[210],[13072],{"type":26,"value":13073},"Channels Installation",{"type":26,"value":13075}," documentation for a little more detail here, but it's nicely straightforward.",{"type":21,"tag":22,"props":13077,"children":13078},{},[13079],{"type":26,"value":13080},"You should also copy that requirements.txt file we made earlier into the root of your app directory. If nothing else, it serves as a reference as to what packages you're using.",{"type":21,"tag":12009,"props":13082,"children":13083},{"id":12011},[13084],{"type":26,"value":13085},"FreeBSD Note:",{"type":21,"tag":22,"props":13087,"children":13088},{},[13089,13091],{"type":26,"value":13090},"At this point we'll be relying on SQLite to provide our database solution - in many distributions, this is built-in to Python, but on FreeBSD we'll also need to install the py-sqlite3 package: ",{"type":21,"tag":43,"props":13092,"children":13094},{"className":13093},[],[13095],{"type":26,"value":13096},"pkg install py27-sqlite3",{"type":21,"tag":87,"props":13098,"children":13100},{"id":13099},"deploy-setup-optional",[13101],{"type":26,"value":13102},"Deploy Setup [Optional]",{"type":21,"tag":22,"props":13104,"children":13105},{},[13106,13108,13115],{"type":26,"value":13107},"This step is purely optional, but I like it as an easy way to ",{"type":21,"tag":206,"props":13109,"children":13112},{"href":13110,"rel":13111},"https://artandlogic.com/2013/12/deploying-websites-with-git/",[210],[13113],{"type":26,"value":13114},"deploy via git push",{"type":26,"value":13116},". That link has more info, so we'll just sketch out the implementation here:",{"type":21,"tag":22,"props":13118,"children":13119},{},[13120],{"type":26,"value":13121},"Make the necessary directories:",{"type":21,"tag":595,"props":13123,"children":13125},{"className":12405,"code":13124,"language":12407,"meta":8,"style":8},"mkdir -p /home/web/www.git/\nmkdir -p /home/web/www/\n",[13126],{"type":21,"tag":43,"props":13127,"children":13128},{"__ignoreMap":8},[13129,13145],{"type":21,"tag":399,"props":13130,"children":13131},{"class":605,"line":606},[13132,13136,13140],{"type":21,"tag":399,"props":13133,"children":13134},{"style":2704},[13135],{"type":26,"value":12666},{"type":21,"tag":399,"props":13137,"children":13138},{"style":630},[13139],{"type":26,"value":10236},{"type":21,"tag":399,"props":13141,"children":13142},{"style":644},[13143],{"type":26,"value":13144}," /home/web/www.git/\n",{"type":21,"tag":399,"props":13146,"children":13147},{"class":605,"line":239},[13148,13152,13156],{"type":21,"tag":399,"props":13149,"children":13150},{"style":2704},[13151],{"type":26,"value":12666},{"type":21,"tag":399,"props":13153,"children":13154},{"style":630},[13155],{"type":26,"value":10236},{"type":21,"tag":399,"props":13157,"children":13158},{"style":644},[13159],{"type":26,"value":13160}," /home/web/www/\n",{"type":21,"tag":22,"props":13162,"children":13163},{},[13164],{"type":26,"value":13165},"Setup a bare git repository:",{"type":21,"tag":595,"props":13167,"children":13169},{"className":12405,"code":13168,"language":12407,"meta":8,"style":8},"cd /home/web/www.git/\ngit config core.sharedRepository group\n",[13170],{"type":21,"tag":43,"props":13171,"children":13172},{"__ignoreMap":8},[13173,13184],{"type":21,"tag":399,"props":13174,"children":13175},{"class":605,"line":606},[13176,13180],{"type":21,"tag":399,"props":13177,"children":13178},{"style":630},[13179],{"type":26,"value":12513},{"type":21,"tag":399,"props":13181,"children":13182},{"style":644},[13183],{"type":26,"value":13144},{"type":21,"tag":399,"props":13185,"children":13186},{"class":605,"line":239},[13187,13192,13197,13202],{"type":21,"tag":399,"props":13188,"children":13189},{"style":2704},[13190],{"type":26,"value":13191},"git",{"type":21,"tag":399,"props":13193,"children":13194},{"style":644},[13195],{"type":26,"value":13196}," config",{"type":21,"tag":399,"props":13198,"children":13199},{"style":644},[13200],{"type":26,"value":13201}," core.sharedRepository",{"type":21,"tag":399,"props":13203,"children":13204},{"style":644},[13205],{"type":26,"value":13206}," group\n",{"type":21,"tag":22,"props":13208,"children":13209},{},[13210,13212,13218],{"type":26,"value":13211},"And create a post-receive hook that will ",{"type":21,"tag":43,"props":13213,"children":13215},{"className":13214},[],[13216],{"type":26,"value":13217},"install/update",{"type":26,"value":13219}," our packages, run migrations for us, and kill and restart the development server:",{"type":21,"tag":22,"props":13221,"children":13222},{},[13223],{"type":26,"value":9527},{"type":21,"tag":595,"props":13225,"children":13227},{"className":9530,"code":13226,"language":9532,"meta":8,"style":8},"#!/bin/bash\n\nGIT_WORK_TREE=/home/web/www/ git checkout -f\n\nsource /home/web/venv/bin/activate\npushd /home/web/www/\n\n# Install python libs via pip and perform database migrations\npip install --upgrade -r requirements.txt\npython manage.py migrate\n\nkill ps aux | grep runserver | grep -v grep | awk '{print $2}'\npython manage.py runserver\n\npopd\ndeactivate\n",[13228],{"type":21,"tag":43,"props":13229,"children":13230},{"__ignoreMap":8},[13231,13238,13245,13272,13279,13290,13297,13304,13311,13334,13349,13356,13420,13436,13443,13450],{"type":21,"tag":399,"props":13232,"children":13233},{"class":605,"line":606},[13234],{"type":21,"tag":399,"props":13235,"children":13236},{"style":2818},[13237],{"type":26,"value":9544},{"type":21,"tag":399,"props":13239,"children":13240},{"class":605,"line":239},[13241],{"type":21,"tag":399,"props":13242,"children":13243},{"emptyLinePlaceholder":2690},[13244],{"type":26,"value":2693},{"type":21,"tag":399,"props":13246,"children":13247},{"class":605,"line":232},[13248,13252,13256,13260,13264,13268],{"type":21,"tag":399,"props":13249,"children":13250},{"style":610},[13251],{"type":26,"value":9559},{"type":21,"tag":399,"props":13253,"children":13254},{"style":616},[13255],{"type":26,"value":619},{"type":21,"tag":399,"props":13257,"children":13258},{"style":644},[13259],{"type":26,"value":9568},{"type":21,"tag":399,"props":13261,"children":13262},{"style":2704},[13263],{"type":26,"value":9573},{"type":21,"tag":399,"props":13265,"children":13266},{"style":644},[13267],{"type":26,"value":9578},{"type":21,"tag":399,"props":13269,"children":13270},{"style":630},[13271],{"type":26,"value":9583},{"type":21,"tag":399,"props":13273,"children":13274},{"class":605,"line":654},[13275],{"type":21,"tag":399,"props":13276,"children":13277},{"emptyLinePlaceholder":2690},[13278],{"type":26,"value":2693},{"type":21,"tag":399,"props":13280,"children":13281},{"class":605,"line":698},[13282,13286],{"type":21,"tag":399,"props":13283,"children":13284},{"style":630},[13285],{"type":26,"value":9598},{"type":21,"tag":399,"props":13287,"children":13288},{"style":644},[13289],{"type":26,"value":9603},{"type":21,"tag":399,"props":13291,"children":13292},{"class":605,"line":737},[13293],{"type":21,"tag":399,"props":13294,"children":13295},{"style":610},[13296],{"type":26,"value":9611},{"type":21,"tag":399,"props":13298,"children":13299},{"class":605,"line":755},[13300],{"type":21,"tag":399,"props":13301,"children":13302},{"emptyLinePlaceholder":2690},[13303],{"type":26,"value":2693},{"type":21,"tag":399,"props":13305,"children":13306},{"class":605,"line":773},[13307],{"type":21,"tag":399,"props":13308,"children":13309},{"style":2818},[13310],{"type":26,"value":9626},{"type":21,"tag":399,"props":13312,"children":13313},{"class":605,"line":795},[13314,13318,13322,13326,13330],{"type":21,"tag":399,"props":13315,"children":13316},{"style":2704},[13317],{"type":26,"value":9634},{"type":21,"tag":399,"props":13319,"children":13320},{"style":644},[13321],{"type":26,"value":9639},{"type":21,"tag":399,"props":13323,"children":13324},{"style":630},[13325],{"type":26,"value":9644},{"type":21,"tag":399,"props":13327,"children":13328},{"style":630},[13329],{"type":26,"value":9649},{"type":21,"tag":399,"props":13331,"children":13332},{"style":644},[13333],{"type":26,"value":9654},{"type":21,"tag":399,"props":13335,"children":13336},{"class":605,"line":804},[13337,13341,13345],{"type":21,"tag":399,"props":13338,"children":13339},{"style":2704},[13340],{"type":26,"value":16},{"type":21,"tag":399,"props":13342,"children":13343},{"style":644},[13344],{"type":26,"value":9666},{"type":21,"tag":399,"props":13346,"children":13347},{"style":644},[13348],{"type":26,"value":9671},{"type":21,"tag":399,"props":13350,"children":13351},{"class":605,"line":843},[13352],{"type":21,"tag":399,"props":13353,"children":13354},{"emptyLinePlaceholder":2690},[13355],{"type":26,"value":2693},{"type":21,"tag":399,"props":13357,"children":13358},{"class":605,"line":882},[13359,13364,13369,13374,13379,13384,13389,13393,13397,13402,13406,13410,13415],{"type":21,"tag":399,"props":13360,"children":13361},{"style":630},[13362],{"type":26,"value":13363},"kill",{"type":21,"tag":399,"props":13365,"children":13366},{"style":644},[13367],{"type":26,"value":13368}," ps",{"type":21,"tag":399,"props":13370,"children":13371},{"style":644},[13372],{"type":26,"value":13373}," aux",{"type":21,"tag":399,"props":13375,"children":13376},{"style":616},[13377],{"type":26,"value":13378}," |",{"type":21,"tag":399,"props":13380,"children":13381},{"style":2704},[13382],{"type":26,"value":13383}," grep",{"type":21,"tag":399,"props":13385,"children":13386},{"style":644},[13387],{"type":26,"value":13388}," runserver",{"type":21,"tag":399,"props":13390,"children":13391},{"style":616},[13392],{"type":26,"value":13378},{"type":21,"tag":399,"props":13394,"children":13395},{"style":2704},[13396],{"type":26,"value":13383},{"type":21,"tag":399,"props":13398,"children":13399},{"style":630},[13400],{"type":26,"value":13401}," -v",{"type":21,"tag":399,"props":13403,"children":13404},{"style":644},[13405],{"type":26,"value":13383},{"type":21,"tag":399,"props":13407,"children":13408},{"style":616},[13409],{"type":26,"value":13378},{"type":21,"tag":399,"props":13411,"children":13412},{"style":2704},[13413],{"type":26,"value":13414}," awk",{"type":21,"tag":399,"props":13416,"children":13417},{"style":644},[13418],{"type":26,"value":13419}," '{print $2}'\n",{"type":21,"tag":399,"props":13421,"children":13422},{"class":605,"line":900},[13423,13427,13431],{"type":21,"tag":399,"props":13424,"children":13425},{"style":2704},[13426],{"type":26,"value":16},{"type":21,"tag":399,"props":13428,"children":13429},{"style":644},[13430],{"type":26,"value":9666},{"type":21,"tag":399,"props":13432,"children":13433},{"style":644},[13434],{"type":26,"value":13435}," runserver\n",{"type":21,"tag":399,"props":13437,"children":13438},{"class":605,"line":923},[13439],{"type":21,"tag":399,"props":13440,"children":13441},{"emptyLinePlaceholder":2690},[13442],{"type":26,"value":2693},{"type":21,"tag":399,"props":13444,"children":13445},{"class":605,"line":969},[13446],{"type":21,"tag":399,"props":13447,"children":13448},{"style":610},[13449],{"type":26,"value":9686},{"type":21,"tag":399,"props":13451,"children":13452},{"class":605,"line":991},[13453],{"type":21,"tag":399,"props":13454,"children":13455},{"style":2704},[13456],{"type":26,"value":9694},{"type":21,"tag":22,"props":13458,"children":13459},{},[13460],{"type":26,"value":9736},{"type":21,"tag":595,"props":13462,"children":13464},{"className":9530,"code":13463,"language":9532,"meta":8,"style":8},"#!/bin/csh\n\nenv GIT_WORK_TREE=/home/web/www/ git checkout -f\nsource /home/web/venv/bin/activate.csh\npushd /home/web/www/\n\npip install --upgrade -r requirements.txt\npython manage.py migrate\n\nkill ps aux | grep runserver | grep -v grep | awk '{print $2}'\npython manage.py runserver\n\npopd\ndeactivate\n",[13465],{"type":21,"tag":43,"props":13466,"children":13467},{"__ignoreMap":8},[13468,13475,13482,13505,13516,13523,13530,13553,13568,13575,13630,13645,13652,13659],{"type":21,"tag":399,"props":13469,"children":13470},{"class":605,"line":606},[13471],{"type":21,"tag":399,"props":13472,"children":13473},{"style":2818},[13474],{"type":26,"value":9751},{"type":21,"tag":399,"props":13476,"children":13477},{"class":605,"line":239},[13478],{"type":21,"tag":399,"props":13479,"children":13480},{"emptyLinePlaceholder":2690},[13481],{"type":26,"value":2693},{"type":21,"tag":399,"props":13483,"children":13484},{"class":605,"line":232},[13485,13489,13493,13497,13501],{"type":21,"tag":399,"props":13486,"children":13487},{"style":2704},[13488],{"type":26,"value":9766},{"type":21,"tag":399,"props":13490,"children":13491},{"style":644},[13492],{"type":26,"value":9771},{"type":21,"tag":399,"props":13494,"children":13495},{"style":644},[13496],{"type":26,"value":9573},{"type":21,"tag":399,"props":13498,"children":13499},{"style":644},[13500],{"type":26,"value":9578},{"type":21,"tag":399,"props":13502,"children":13503},{"style":630},[13504],{"type":26,"value":9583},{"type":21,"tag":399,"props":13506,"children":13507},{"class":605,"line":654},[13508,13512],{"type":21,"tag":399,"props":13509,"children":13510},{"style":630},[13511],{"type":26,"value":9598},{"type":21,"tag":399,"props":13513,"children":13514},{"style":644},[13515],{"type":26,"value":9795},{"type":21,"tag":399,"props":13517,"children":13518},{"class":605,"line":698},[13519],{"type":21,"tag":399,"props":13520,"children":13521},{"style":610},[13522],{"type":26,"value":9611},{"type":21,"tag":399,"props":13524,"children":13525},{"class":605,"line":737},[13526],{"type":21,"tag":399,"props":13527,"children":13528},{"emptyLinePlaceholder":2690},[13529],{"type":26,"value":2693},{"type":21,"tag":399,"props":13531,"children":13532},{"class":605,"line":755},[13533,13537,13541,13545,13549],{"type":21,"tag":399,"props":13534,"children":13535},{"style":2704},[13536],{"type":26,"value":9634},{"type":21,"tag":399,"props":13538,"children":13539},{"style":644},[13540],{"type":26,"value":9639},{"type":21,"tag":399,"props":13542,"children":13543},{"style":630},[13544],{"type":26,"value":9644},{"type":21,"tag":399,"props":13546,"children":13547},{"style":630},[13548],{"type":26,"value":9649},{"type":21,"tag":399,"props":13550,"children":13551},{"style":644},[13552],{"type":26,"value":9654},{"type":21,"tag":399,"props":13554,"children":13555},{"class":605,"line":773},[13556,13560,13564],{"type":21,"tag":399,"props":13557,"children":13558},{"style":2704},[13559],{"type":26,"value":16},{"type":21,"tag":399,"props":13561,"children":13562},{"style":644},[13563],{"type":26,"value":9666},{"type":21,"tag":399,"props":13565,"children":13566},{"style":644},[13567],{"type":26,"value":9671},{"type":21,"tag":399,"props":13569,"children":13570},{"class":605,"line":795},[13571],{"type":21,"tag":399,"props":13572,"children":13573},{"emptyLinePlaceholder":2690},[13574],{"type":26,"value":2693},{"type":21,"tag":399,"props":13576,"children":13577},{"class":605,"line":804},[13578,13582,13586,13590,13594,13598,13602,13606,13610,13614,13618,13622,13626],{"type":21,"tag":399,"props":13579,"children":13580},{"style":630},[13581],{"type":26,"value":13363},{"type":21,"tag":399,"props":13583,"children":13584},{"style":644},[13585],{"type":26,"value":13368},{"type":21,"tag":399,"props":13587,"children":13588},{"style":644},[13589],{"type":26,"value":13373},{"type":21,"tag":399,"props":13591,"children":13592},{"style":616},[13593],{"type":26,"value":13378},{"type":21,"tag":399,"props":13595,"children":13596},{"style":2704},[13597],{"type":26,"value":13383},{"type":21,"tag":399,"props":13599,"children":13600},{"style":644},[13601],{"type":26,"value":13388},{"type":21,"tag":399,"props":13603,"children":13604},{"style":616},[13605],{"type":26,"value":13378},{"type":21,"tag":399,"props":13607,"children":13608},{"style":2704},[13609],{"type":26,"value":13383},{"type":21,"tag":399,"props":13611,"children":13612},{"style":630},[13613],{"type":26,"value":13401},{"type":21,"tag":399,"props":13615,"children":13616},{"style":644},[13617],{"type":26,"value":13383},{"type":21,"tag":399,"props":13619,"children":13620},{"style":616},[13621],{"type":26,"value":13378},{"type":21,"tag":399,"props":13623,"children":13624},{"style":2704},[13625],{"type":26,"value":13414},{"type":21,"tag":399,"props":13627,"children":13628},{"style":644},[13629],{"type":26,"value":13419},{"type":21,"tag":399,"props":13631,"children":13632},{"class":605,"line":843},[13633,13637,13641],{"type":21,"tag":399,"props":13634,"children":13635},{"style":2704},[13636],{"type":26,"value":16},{"type":21,"tag":399,"props":13638,"children":13639},{"style":644},[13640],{"type":26,"value":9666},{"type":21,"tag":399,"props":13642,"children":13643},{"style":644},[13644],{"type":26,"value":13435},{"type":21,"tag":399,"props":13646,"children":13647},{"class":605,"line":882},[13648],{"type":21,"tag":399,"props":13649,"children":13650},{"emptyLinePlaceholder":2690},[13651],{"type":26,"value":2693},{"type":21,"tag":399,"props":13653,"children":13654},{"class":605,"line":900},[13655],{"type":21,"tag":399,"props":13656,"children":13657},{"style":610},[13658],{"type":26,"value":9686},{"type":21,"tag":399,"props":13660,"children":13661},{"class":605,"line":923},[13662],{"type":21,"tag":399,"props":13663,"children":13664},{"style":2704},[13665],{"type":26,"value":9694},{"type":21,"tag":22,"props":13667,"children":13668},{},[13669,13671,13677,13679,13685],{"type":26,"value":13670},"Save this file as ",{"type":21,"tag":43,"props":13672,"children":13674},{"className":13673},[],[13675],{"type":26,"value":13676},"post-receive",{"type":26,"value":13678},", and then make it executable: ",{"type":21,"tag":43,"props":13680,"children":13682},{"className":13681},[],[13683],{"type":26,"value":13684},"chmod +x post-receive",{"type":26,"value":2458},{"type":21,"tag":22,"props":13687,"children":13688},{},[13689,13691,13697,13699,13705],{"type":26,"value":13690},"Now, ",{"type":21,"tag":43,"props":13692,"children":13694},{"className":13693},[],[13695],{"type":26,"value":13696},"git init",{"type":26,"value":13698}," in your local directory, and add a new remote with ",{"type":21,"tag":43,"props":13700,"children":13702},{"className":13701},[],[13703],{"type":26,"value":13704},"git remote add your.server.addr.octets/home/web/www.git",{"type":26,"value":13706},". When pushing, do so as your web user, or you may run into permissions issues.",{"type":21,"tag":22,"props":13708,"children":13709},{},[13710,13712,13718],{"type":26,"value":13711},"As noted before, ",{"type":21,"tag":206,"props":13713,"children":13715},{"href":13110,"rel":13714},[210],[13716],{"type":26,"value":13717},"more details on this can be found in this article",{"type":26,"value":2458},{"type":21,"tag":190,"props":13720,"children":13722},{"id":13721},"are-we-there-yet",[13723],{"type":26,"value":13724},"Are we there yet?",{"type":21,"tag":22,"props":13726,"children":13727},{},[13728,13730,13735],{"type":26,"value":13729},"Hah, no, but we are done for today. We're now at a point where you could push your code into your server, and use django's runserver command to run the development server. With the ",{"type":21,"tag":43,"props":13731,"children":13733},{"className":13732},[],[13734],{"type":26,"value":12789},{"type":26,"value":13736}," app installed, this well-known invocation will not start up the old wsgi server, but a shiny new ASGI server instead, with workers and interface living inside the same process as separate threads.",{"type":21,"tag":22,"props":13738,"children":13739},{},[13740,13747],{"type":21,"tag":206,"props":13741,"children":13744},{"href":13742,"rel":13743},"https://artandlogic.com/2016/06/django-channels-ground-part-2/",[210],[13745],{"type":26,"value":13746},"Next time",{"type":26,"value":13748},", we'll go the rest of the way - getting your server something resembling production ready, with postgresql setup, supervisord managing our server workers and interface, and we'll start figuring out what we need to actually use this setup for something cool.",{"type":21,"tag":2495,"props":13750,"children":13751},{},[13752],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":13754},[13755,13758,13765],{"id":12151,"depth":239,"text":12154,"children":13756},[13757],{"id":12257,"depth":654,"text":12260},{"id":12288,"depth":239,"text":12291,"children":13759},[13760,13761,13762,13763,13764],{"id":12389,"depth":232,"text":12392},{"id":12596,"depth":232,"text":12599},{"id":12702,"depth":232,"text":12705},{"id":13017,"depth":232,"text":13020},{"id":13099,"depth":232,"text":13102},{"id":13721,"depth":239,"text":13724},"content:ckeefer:2016-3:djangoChannels1.md","ckeefer/2016-3/djangoChannels1.md","ckeefer/2016-3/djangoChannels1",{"user":9465,"name":9466},{"_path":13771,"_dir":13772,"_draft":7,"_partial":7,"_locale":8,"title":13773,"description":13774,"publishDate":13775,"tags":13776,"excerpt":13774,"body":13779,"_type":240,"_id":17138,"_source":242,"_file":17139,"_stem":17140,"_extension":245,"author":17141},"/ckeefer/2016-2/paymentprocessing","2016-2","Payment Processing with Braintree","You've built the web application of the century, and the users have rightly flooded to it. Cat pictures for everyone!","2016-05-11",[2537,13777,13778,16],"js","jquery",{"type":18,"children":13780,"toc":17131},[13781,13785,13790,13802,13814,13828,13868,13873,13879,13898,13903,13909,13948,13953,14098,14112,14352,14371,14376,14602,14608,14640,14662,15007,15012,15025,15202,15207,15212,15220,15226,15248,16008,16014,16027,17073,17079,17084,17096,17108,17113,17127],{"type":21,"tag":22,"props":13782,"children":13783},{},[13784],{"type":26,"value":13774},{"type":21,"tag":22,"props":13786,"children":13787},{},[13788],{"type":26,"value":13789},"But alas, while your users indulge in cat-induced bliss, the cold hard reality of server costs cannot help but harsh your mellow. What is to be done?",{"type":21,"tag":22,"props":13791,"children":13792},{},[13793,13795,13800],{"type":26,"value":13794},"Maybe, you could get the users to... ",{"type":21,"tag":166,"props":13796,"children":13797},{},[13798],{"type":26,"value":13799},"pay",{"type":26,"value":13801}," for access to your incredible web application in all its multivarious splendour?",{"type":21,"tag":22,"props":13803,"children":13804},{},[13805,13812],{"type":21,"tag":206,"props":13806,"children":13809},{"href":13807,"rel":13808},"https://www.braintreepayments.com/",[210],[13810],{"type":26,"value":13811},"Braintree",{"type":26,"value":13813}," is a payment processor (now a subsidiary of PayPal), which boasts of a \"simple, robust way to accept payments\", and with features like a drop-in payment ui and libraries for various programming languages enabling fairly easy integration, is a solid choice for accepting payments via credit card or PayPal.",{"type":21,"tag":22,"props":13815,"children":13816},{},[13817,13819,13826],{"type":26,"value":13818},"While Braintree's ",{"type":21,"tag":206,"props":13820,"children":13823},{"href":13821,"rel":13822},"https://developers.braintreepayments.com/",[210],[13824],{"type":26,"value":13825},"developer documentation",{"type":26,"value":13827}," is blessedly detailed, it's possessed of a potentially confusing bevy of options, and its various implementation examples are spread out amongst a number of pages and platforms. So today, rather than reiterate any particular section of the docs, we're going to take a look at an end-to-end example of a specific, straightforward scenario - accepting and processing a one-time, immediately settled payment in a web application.",{"type":21,"tag":22,"props":13829,"children":13830},{},[13831,13833,13840,13842,13849,13851,13858,13859,13866],{"type":26,"value":13832},"Our languages of choice for today will be ",{"type":21,"tag":206,"props":13834,"children":13837},{"href":13835,"rel":13836},"https://developers.braintreepayments.com/start/hello-server/python",[210],[13838],{"type":26,"value":13839},"Python",{"type":26,"value":13841}," for the backend and, of course, ",{"type":21,"tag":206,"props":13843,"children":13846},{"href":13844,"rel":13845},"https://developers.braintreepayments.com/start/hello-client/javascript",[210],[13847],{"type":26,"value":13848},"JavaScript",{"type":26,"value":13850}," for the frontend, with the additional assumption of jQuery for the ease of $.ajax. The general logic for the backend will be portable to the other languages for which Braintree has released an SDK, e.g. ",{"type":21,"tag":206,"props":13852,"children":13855},{"href":13853,"rel":13854},"https://developers.braintreepayments.com/start/hello-server/ruby",[210],[13856],{"type":26,"value":13857},"Ruby",{"type":26,"value":685},{"type":21,"tag":206,"props":13860,"children":13863},{"href":13861,"rel":13862},"https://developers.braintreepayments.com/start/hello-server/php",[210],[13864],{"type":26,"value":13865},"PHP",{"type":26,"value":13867},", etc., and the frontend logic could certainly be implemented without jQuery.",{"type":21,"tag":22,"props":13869,"children":13870},{},[13871],{"type":26,"value":13872},"We're also going to assume the existence of a database for storing and retrieving item and transaction details. We're also going to assume the use of Django as our web framework in the following examples, but the general concepts are portable to other frameworks.",{"type":21,"tag":12009,"props":13874,"children":13876},{"id":13875},"not-production-ready",[13877],{"type":26,"value":13878},"Not Production Ready",{"type":21,"tag":22,"props":13880,"children":13881},{},[13882,13884,13889,13891,13896],{"type":26,"value":13883},"One more important note before we get started - the code below should ",{"type":21,"tag":195,"props":13885,"children":13886},{},[13887],{"type":26,"value":13888},"not",{"type":26,"value":13890}," be considered production ready. It is example-quality, meant for instructional purposes only! While it should go without saying that copy-pasting code is always a bad idea, copy-pasting example code for business critical purposes like payment processing is a big enough no-no to warrant an extra paragraph warning everyone against it. ",{"type":21,"tag":166,"props":13892,"children":13893},{},[13894],{"type":26,"value":13895},"This is that paragraph.",{"type":26,"value":13897}," Copy-pasting == bad. Using this tutorial to learn == good.",{"type":21,"tag":22,"props":13899,"children":13900},{},[13901],{"type":26,"value":13902},"With that established, let's get started.",{"type":21,"tag":190,"props":13904,"children":13906},{"id":13905},"server-setup-and-token-generation",[13907],{"type":26,"value":13908},"Server Setup and Token Generation",{"type":21,"tag":22,"props":13910,"children":13911},{},[13912,13914,13921,13923,13930,13931,13937,13939,13946],{"type":26,"value":13913},"After ",{"type":21,"tag":206,"props":13915,"children":13918},{"href":13916,"rel":13917},"https://www.braintreepayments.com/get-started",[210],[13919],{"type":26,"value":13920},"signing up for a sandbox account",{"type":26,"value":13922}," and doing some basic setup in the ",{"type":21,"tag":206,"props":13924,"children":13927},{"href":13925,"rel":13926},"https://articles.braintreepayments.com/control-panel/basics/overview",[210],[13928],{"type":26,"value":13929},"braintree control panel",{"type":26,"value":685},{"type":21,"tag":206,"props":13932,"children":13934},{"href":13835,"rel":13933},[210],[13935],{"type":26,"value":13936},"our first step",{"type":26,"value":13938}," is to install the ",{"type":21,"tag":206,"props":13940,"children":13943},{"href":13941,"rel":13942},"https://pypi.python.org/pypi/braintree/3.25.0",[210],[13944],{"type":26,"value":13945},"Braintree Python Library",{"type":26,"value":13947},", manually or via pip.",{"type":21,"tag":22,"props":13949,"children":13950},{},[13951],{"type":26,"value":13952},"That done, we'll want to store our configuration details somewhere. In Django we might perform our configuration in settings.py:",{"type":21,"tag":595,"props":13954,"children":13956},{"className":597,"code":13955,"language":16,"meta":8,"style":8},"import os\nimport braintree\n\nbraintree.Configuration.configure(\n    os.environ.get('BT_ENVIRONMENT', braintree.Environment.Sandbox),\n    os.environ.get('BT_MERCHANT_ID', 'your_sandbox_merchant_id'),\n    os.environ.get('BT_PUBLIC_KEY', 'your_sandbox_public_key'),\n    os.environ.get('BT_PRIVATE_KEY', 'your_sandbox_private_key')\n)\n",[13957],{"type":21,"tag":43,"props":13958,"children":13959},{"__ignoreMap":8},[13960,13971,13983,13990,13998,14016,14041,14066,14091],{"type":21,"tag":399,"props":13961,"children":13962},{"class":605,"line":606},[13963,13967],{"type":21,"tag":399,"props":13964,"children":13965},{"style":616},[13966],{"type":26,"value":2679},{"type":21,"tag":399,"props":13968,"children":13969},{"style":610},[13970],{"type":26,"value":11339},{"type":21,"tag":399,"props":13972,"children":13973},{"class":605,"line":239},[13974,13978],{"type":21,"tag":399,"props":13975,"children":13976},{"style":616},[13977],{"type":26,"value":2679},{"type":21,"tag":399,"props":13979,"children":13980},{"style":610},[13981],{"type":26,"value":13982}," braintree\n",{"type":21,"tag":399,"props":13984,"children":13985},{"class":605,"line":232},[13986],{"type":21,"tag":399,"props":13987,"children":13988},{"emptyLinePlaceholder":2690},[13989],{"type":26,"value":2693},{"type":21,"tag":399,"props":13991,"children":13992},{"class":605,"line":654},[13993],{"type":21,"tag":399,"props":13994,"children":13995},{"style":610},[13996],{"type":26,"value":13997},"braintree.Configuration.configure(\n",{"type":21,"tag":399,"props":13999,"children":14000},{"class":605,"line":698},[14001,14006,14011],{"type":21,"tag":399,"props":14002,"children":14003},{"style":610},[14004],{"type":26,"value":14005},"    os.environ.get(",{"type":21,"tag":399,"props":14007,"children":14008},{"style":644},[14009],{"type":26,"value":14010},"'BT_ENVIRONMENT'",{"type":21,"tag":399,"props":14012,"children":14013},{"style":610},[14014],{"type":26,"value":14015},", braintree.Environment.Sandbox),\n",{"type":21,"tag":399,"props":14017,"children":14018},{"class":605,"line":737},[14019,14023,14028,14032,14037],{"type":21,"tag":399,"props":14020,"children":14021},{"style":610},[14022],{"type":26,"value":14005},{"type":21,"tag":399,"props":14024,"children":14025},{"style":644},[14026],{"type":26,"value":14027},"'BT_MERCHANT_ID'",{"type":21,"tag":399,"props":14029,"children":14030},{"style":610},[14031],{"type":26,"value":685},{"type":21,"tag":399,"props":14033,"children":14034},{"style":644},[14035],{"type":26,"value":14036},"'your_sandbox_merchant_id'",{"type":21,"tag":399,"props":14038,"children":14039},{"style":610},[14040],{"type":26,"value":695},{"type":21,"tag":399,"props":14042,"children":14043},{"class":605,"line":755},[14044,14048,14053,14057,14062],{"type":21,"tag":399,"props":14045,"children":14046},{"style":610},[14047],{"type":26,"value":14005},{"type":21,"tag":399,"props":14049,"children":14050},{"style":644},[14051],{"type":26,"value":14052},"'BT_PUBLIC_KEY'",{"type":21,"tag":399,"props":14054,"children":14055},{"style":610},[14056],{"type":26,"value":685},{"type":21,"tag":399,"props":14058,"children":14059},{"style":644},[14060],{"type":26,"value":14061},"'your_sandbox_public_key'",{"type":21,"tag":399,"props":14063,"children":14064},{"style":610},[14065],{"type":26,"value":695},{"type":21,"tag":399,"props":14067,"children":14068},{"class":605,"line":773},[14069,14073,14078,14082,14087],{"type":21,"tag":399,"props":14070,"children":14071},{"style":610},[14072],{"type":26,"value":14005},{"type":21,"tag":399,"props":14074,"children":14075},{"style":644},[14076],{"type":26,"value":14077},"'BT_PRIVATE_KEY'",{"type":21,"tag":399,"props":14079,"children":14080},{"style":610},[14081],{"type":26,"value":685},{"type":21,"tag":399,"props":14083,"children":14084},{"style":644},[14085],{"type":26,"value":14086},"'your_sandbox_private_key'",{"type":21,"tag":399,"props":14088,"children":14089},{"style":610},[14090],{"type":26,"value":1652},{"type":21,"tag":399,"props":14092,"children":14093},{"class":605,"line":795},[14094],{"type":21,"tag":399,"props":14095,"children":14096},{"style":610},[14097],{"type":26,"value":1652},{"type":21,"tag":22,"props":14099,"children":14100},{},[14101,14103,14110],{"type":26,"value":14102},"Next, we need to setup an endpoint for the client to request a ",{"type":21,"tag":206,"props":14104,"children":14107},{"href":14105,"rel":14106},"https://developers.braintreepayments.com/start/hello-server/python#generate-a-client-token",[210],[14108],{"type":26,"value":14109},"client token",{"type":26,"value":14111}," from:",{"type":21,"tag":595,"props":14113,"children":14115},{"className":597,"code":14114,"language":16,"meta":8,"style":8},"import braintree\nfrom django.http import JsonResponse\nfrom django.views.decorators.http import require_http_methods\n\n@require_http_methods(['GET'])\ndef get_braintree_client_token(request):\n    \"\"\"\n    Generate and return client token.\n    \"\"\"\n    try:\n        client_token = braintree.ClientToken.generate()\n    except ValueError as e:\n        return JsonResponse({\"error\": e.message}, status=500)\n    return JsonResponse({\"token\": client_token})\n",[14116],{"type":21,"tag":43,"props":14117,"children":14118},{"__ignoreMap":8},[14119,14130,14150,14171,14178,14199,14216,14224,14232,14239,14251,14268,14290,14331],{"type":21,"tag":399,"props":14120,"children":14121},{"class":605,"line":606},[14122,14126],{"type":21,"tag":399,"props":14123,"children":14124},{"style":616},[14125],{"type":26,"value":2679},{"type":21,"tag":399,"props":14127,"children":14128},{"style":610},[14129],{"type":26,"value":13982},{"type":21,"tag":399,"props":14131,"children":14132},{"class":605,"line":239},[14133,14137,14141,14145],{"type":21,"tag":399,"props":14134,"children":14135},{"style":616},[14136],{"type":26,"value":2669},{"type":21,"tag":399,"props":14138,"children":14139},{"style":610},[14140],{"type":26,"value":11144},{"type":21,"tag":399,"props":14142,"children":14143},{"style":616},[14144],{"type":26,"value":2679},{"type":21,"tag":399,"props":14146,"children":14147},{"style":610},[14148],{"type":26,"value":14149}," JsonResponse\n",{"type":21,"tag":399,"props":14151,"children":14152},{"class":605,"line":232},[14153,14157,14162,14166],{"type":21,"tag":399,"props":14154,"children":14155},{"style":616},[14156],{"type":26,"value":2669},{"type":21,"tag":399,"props":14158,"children":14159},{"style":610},[14160],{"type":26,"value":14161}," django.views.decorators.http ",{"type":21,"tag":399,"props":14163,"children":14164},{"style":616},[14165],{"type":26,"value":2679},{"type":21,"tag":399,"props":14167,"children":14168},{"style":610},[14169],{"type":26,"value":14170}," require_http_methods\n",{"type":21,"tag":399,"props":14172,"children":14173},{"class":605,"line":654},[14174],{"type":21,"tag":399,"props":14175,"children":14176},{"emptyLinePlaceholder":2690},[14177],{"type":26,"value":2693},{"type":21,"tag":399,"props":14179,"children":14180},{"class":605,"line":698},[14181,14186,14191,14195],{"type":21,"tag":399,"props":14182,"children":14183},{"style":2704},[14184],{"type":26,"value":14185},"@require_http_methods",{"type":21,"tag":399,"props":14187,"children":14188},{"style":610},[14189],{"type":26,"value":14190},"([",{"type":21,"tag":399,"props":14192,"children":14193},{"style":644},[14194],{"type":26,"value":4427},{"type":21,"tag":399,"props":14196,"children":14197},{"style":610},[14198],{"type":26,"value":4432},{"type":21,"tag":399,"props":14200,"children":14201},{"class":605,"line":737},[14202,14206,14211],{"type":21,"tag":399,"props":14203,"children":14204},{"style":616},[14205],{"type":26,"value":3281},{"type":21,"tag":399,"props":14207,"children":14208},{"style":2704},[14209],{"type":26,"value":14210}," get_braintree_client_token",{"type":21,"tag":399,"props":14212,"children":14213},{"style":610},[14214],{"type":26,"value":14215},"(request):\n",{"type":21,"tag":399,"props":14217,"children":14218},{"class":605,"line":755},[14219],{"type":21,"tag":399,"props":14220,"children":14221},{"style":644},[14222],{"type":26,"value":14223},"    \"\"\"\n",{"type":21,"tag":399,"props":14225,"children":14226},{"class":605,"line":773},[14227],{"type":21,"tag":399,"props":14228,"children":14229},{"style":644},[14230],{"type":26,"value":14231},"    Generate and return client token.\n",{"type":21,"tag":399,"props":14233,"children":14234},{"class":605,"line":795},[14235],{"type":21,"tag":399,"props":14236,"children":14237},{"style":644},[14238],{"type":26,"value":14223},{"type":21,"tag":399,"props":14240,"children":14241},{"class":605,"line":804},[14242,14247],{"type":21,"tag":399,"props":14243,"children":14244},{"style":616},[14245],{"type":26,"value":14246},"    try",{"type":21,"tag":399,"props":14248,"children":14249},{"style":610},[14250],{"type":26,"value":4683},{"type":21,"tag":399,"props":14252,"children":14253},{"class":605,"line":843},[14254,14259,14263],{"type":21,"tag":399,"props":14255,"children":14256},{"style":610},[14257],{"type":26,"value":14258},"        client_token ",{"type":21,"tag":399,"props":14260,"children":14261},{"style":616},[14262],{"type":26,"value":619},{"type":21,"tag":399,"props":14264,"children":14265},{"style":610},[14266],{"type":26,"value":14267}," braintree.ClientToken.generate()\n",{"type":21,"tag":399,"props":14269,"children":14270},{"class":605,"line":882},[14271,14276,14281,14285],{"type":21,"tag":399,"props":14272,"children":14273},{"style":616},[14274],{"type":26,"value":14275},"    except",{"type":21,"tag":399,"props":14277,"children":14278},{"style":630},[14279],{"type":26,"value":14280}," ValueError",{"type":21,"tag":399,"props":14282,"children":14283},{"style":616},[14284],{"type":26,"value":7893},{"type":21,"tag":399,"props":14286,"children":14287},{"style":610},[14288],{"type":26,"value":14289}," e:\n",{"type":21,"tag":399,"props":14291,"children":14292},{"class":605,"line":900},[14293,14298,14303,14308,14313,14318,14322,14327],{"type":21,"tag":399,"props":14294,"children":14295},{"style":616},[14296],{"type":26,"value":14297},"        return",{"type":21,"tag":399,"props":14299,"children":14300},{"style":610},[14301],{"type":26,"value":14302}," JsonResponse({",{"type":21,"tag":399,"props":14304,"children":14305},{"style":644},[14306],{"type":26,"value":14307},"\"error\"",{"type":21,"tag":399,"props":14309,"children":14310},{"style":610},[14311],{"type":26,"value":14312},": e.message}, ",{"type":21,"tag":399,"props":14314,"children":14315},{"style":658},[14316],{"type":26,"value":14317},"status",{"type":21,"tag":399,"props":14319,"children":14320},{"style":616},[14321],{"type":26,"value":619},{"type":21,"tag":399,"props":14323,"children":14324},{"style":630},[14325],{"type":26,"value":14326},"500",{"type":21,"tag":399,"props":14328,"children":14329},{"style":610},[14330],{"type":26,"value":1652},{"type":21,"tag":399,"props":14332,"children":14333},{"class":605,"line":923},[14334,14338,14342,14347],{"type":21,"tag":399,"props":14335,"children":14336},{"style":616},[14337],{"type":26,"value":2829},{"type":21,"tag":399,"props":14339,"children":14340},{"style":610},[14341],{"type":26,"value":14302},{"type":21,"tag":399,"props":14343,"children":14344},{"style":644},[14345],{"type":26,"value":14346},"\"token\"",{"type":21,"tag":399,"props":14348,"children":14349},{"style":610},[14350],{"type":26,"value":14351},": client_token})\n",{"type":21,"tag":22,"props":14353,"children":14354},{},[14355,14357,14362,14364,14370],{"type":26,"value":14356},"For Django, we can add this to a reasonable route in our ",{"type":21,"tag":43,"props":14358,"children":14360},{"className":14359},[],[14361],{"type":26,"value":10864},{"type":26,"value":14363}," file, and then make ajax requests against this route to generate and return the client token that the javascript braintree library requires. For the purpose of this tutorial, we'll assume we've bound it to ",{"type":21,"tag":43,"props":14365,"children":14367},{"className":14366},[],[14368],{"type":26,"value":14369},"/payment/token/",{"type":26,"value":2458},{"type":21,"tag":22,"props":14372,"children":14373},{},[14374],{"type":26,"value":14375},"As an aside, we could also generate this token as part of the view and pass it into the context if we knew we were going to need it immediately, or if there were some other reason why an ajax roundtrip is undesirable. This could be as simple as:",{"type":21,"tag":595,"props":14377,"children":14379},{"className":597,"code":14378,"language":16,"meta":8,"style":8},"import braintree\nfrom django.shortcuts import render\nfrom django.http import HttpResponse\n\ndef start_payment_view(request, template_name=\"start_payment.html\"):\n    \"\"\"\n    Generate client token and pass it in the view context.\n    \"\"\"\n    try:\n        client_token = braintree.ClientToken.generate()\n    except ValueError as e:\n        return HttpResponse(\"Failed to generate Braintree client token\", status=500)\n\n    return render(request, template_name, {\"bt_client_token\": client_token})\n",[14380],{"type":21,"tag":43,"props":14381,"children":14382},{"__ignoreMap":8},[14383,14394,14415,14434,14441,14471,14478,14486,14493,14504,14519,14538,14574,14581],{"type":21,"tag":399,"props":14384,"children":14385},{"class":605,"line":606},[14386,14390],{"type":21,"tag":399,"props":14387,"children":14388},{"style":616},[14389],{"type":26,"value":2679},{"type":21,"tag":399,"props":14391,"children":14392},{"style":610},[14393],{"type":26,"value":13982},{"type":21,"tag":399,"props":14395,"children":14396},{"class":605,"line":239},[14397,14401,14406,14410],{"type":21,"tag":399,"props":14398,"children":14399},{"style":616},[14400],{"type":26,"value":2669},{"type":21,"tag":399,"props":14402,"children":14403},{"style":610},[14404],{"type":26,"value":14405}," django.shortcuts ",{"type":21,"tag":399,"props":14407,"children":14408},{"style":616},[14409],{"type":26,"value":2679},{"type":21,"tag":399,"props":14411,"children":14412},{"style":610},[14413],{"type":26,"value":14414}," render\n",{"type":21,"tag":399,"props":14416,"children":14417},{"class":605,"line":232},[14418,14422,14426,14430],{"type":21,"tag":399,"props":14419,"children":14420},{"style":616},[14421],{"type":26,"value":2669},{"type":21,"tag":399,"props":14423,"children":14424},{"style":610},[14425],{"type":26,"value":11144},{"type":21,"tag":399,"props":14427,"children":14428},{"style":616},[14429],{"type":26,"value":2679},{"type":21,"tag":399,"props":14431,"children":14432},{"style":610},[14433],{"type":26,"value":11153},{"type":21,"tag":399,"props":14435,"children":14436},{"class":605,"line":654},[14437],{"type":21,"tag":399,"props":14438,"children":14439},{"emptyLinePlaceholder":2690},[14440],{"type":26,"value":2693},{"type":21,"tag":399,"props":14442,"children":14443},{"class":605,"line":698},[14444,14448,14453,14458,14462,14467],{"type":21,"tag":399,"props":14445,"children":14446},{"style":616},[14447],{"type":26,"value":3281},{"type":21,"tag":399,"props":14449,"children":14450},{"style":2704},[14451],{"type":26,"value":14452}," start_payment_view",{"type":21,"tag":399,"props":14454,"children":14455},{"style":610},[14456],{"type":26,"value":14457},"(request, template_name",{"type":21,"tag":399,"props":14459,"children":14460},{"style":616},[14461],{"type":26,"value":619},{"type":21,"tag":399,"props":14463,"children":14464},{"style":644},[14465],{"type":26,"value":14466},"\"start_payment.html\"",{"type":21,"tag":399,"props":14468,"children":14469},{"style":610},[14470],{"type":26,"value":2722},{"type":21,"tag":399,"props":14472,"children":14473},{"class":605,"line":737},[14474],{"type":21,"tag":399,"props":14475,"children":14476},{"style":644},[14477],{"type":26,"value":14223},{"type":21,"tag":399,"props":14479,"children":14480},{"class":605,"line":755},[14481],{"type":21,"tag":399,"props":14482,"children":14483},{"style":644},[14484],{"type":26,"value":14485},"    Generate client token and pass it in the view context.\n",{"type":21,"tag":399,"props":14487,"children":14488},{"class":605,"line":773},[14489],{"type":21,"tag":399,"props":14490,"children":14491},{"style":644},[14492],{"type":26,"value":14223},{"type":21,"tag":399,"props":14494,"children":14495},{"class":605,"line":795},[14496,14500],{"type":21,"tag":399,"props":14497,"children":14498},{"style":616},[14499],{"type":26,"value":14246},{"type":21,"tag":399,"props":14501,"children":14502},{"style":610},[14503],{"type":26,"value":4683},{"type":21,"tag":399,"props":14505,"children":14506},{"class":605,"line":804},[14507,14511,14515],{"type":21,"tag":399,"props":14508,"children":14509},{"style":610},[14510],{"type":26,"value":14258},{"type":21,"tag":399,"props":14512,"children":14513},{"style":616},[14514],{"type":26,"value":619},{"type":21,"tag":399,"props":14516,"children":14517},{"style":610},[14518],{"type":26,"value":14267},{"type":21,"tag":399,"props":14520,"children":14521},{"class":605,"line":843},[14522,14526,14530,14534],{"type":21,"tag":399,"props":14523,"children":14524},{"style":616},[14525],{"type":26,"value":14275},{"type":21,"tag":399,"props":14527,"children":14528},{"style":630},[14529],{"type":26,"value":14280},{"type":21,"tag":399,"props":14531,"children":14532},{"style":616},[14533],{"type":26,"value":7893},{"type":21,"tag":399,"props":14535,"children":14536},{"style":610},[14537],{"type":26,"value":14289},{"type":21,"tag":399,"props":14539,"children":14540},{"class":605,"line":882},[14541,14545,14549,14554,14558,14562,14566,14570],{"type":21,"tag":399,"props":14542,"children":14543},{"style":616},[14544],{"type":26,"value":14297},{"type":21,"tag":399,"props":14546,"children":14547},{"style":610},[14548],{"type":26,"value":11223},{"type":21,"tag":399,"props":14550,"children":14551},{"style":644},[14552],{"type":26,"value":14553},"\"Failed to generate Braintree client token\"",{"type":21,"tag":399,"props":14555,"children":14556},{"style":610},[14557],{"type":26,"value":685},{"type":21,"tag":399,"props":14559,"children":14560},{"style":658},[14561],{"type":26,"value":14317},{"type":21,"tag":399,"props":14563,"children":14564},{"style":616},[14565],{"type":26,"value":619},{"type":21,"tag":399,"props":14567,"children":14568},{"style":630},[14569],{"type":26,"value":14326},{"type":21,"tag":399,"props":14571,"children":14572},{"style":610},[14573],{"type":26,"value":1652},{"type":21,"tag":399,"props":14575,"children":14576},{"class":605,"line":900},[14577],{"type":21,"tag":399,"props":14578,"children":14579},{"emptyLinePlaceholder":2690},[14580],{"type":26,"value":2693},{"type":21,"tag":399,"props":14582,"children":14583},{"class":605,"line":923},[14584,14588,14593,14598],{"type":21,"tag":399,"props":14585,"children":14586},{"style":616},[14587],{"type":26,"value":2829},{"type":21,"tag":399,"props":14589,"children":14590},{"style":610},[14591],{"type":26,"value":14592}," render(request, template_name, {",{"type":21,"tag":399,"props":14594,"children":14595},{"style":644},[14596],{"type":26,"value":14597},"\"bt_client_token\"",{"type":21,"tag":399,"props":14599,"children":14600},{"style":610},[14601],{"type":26,"value":14351},{"type":21,"tag":190,"props":14603,"children":14605},{"id":14604},"client-setup",[14606],{"type":26,"value":14607},"Client Setup",{"type":21,"tag":22,"props":14609,"children":14610},{},[14611,14613,14620,14622,14629,14631,14638],{"type":26,"value":14612},"Now for ",{"type":21,"tag":206,"props":14614,"children":14617},{"href":14615,"rel":14616},"https://developers.braintreepayments.com/reference/client-reference/javascript/v2/configuration",[210],[14618],{"type":26,"value":14619},"client side configuration",{"type":26,"value":14621},". On the client side, we're going to assume we want to use the ",{"type":21,"tag":206,"props":14623,"children":14626},{"href":14624,"rel":14625},"https://developers.braintreepayments.com/guides/drop-in/javascript/v2",[210],[14627],{"type":26,"value":14628},"'Drop-in'",{"type":26,"value":14630}," integration. This generates an iframe hosted by Braintree, allowing our application to qualify for the lowest-effort level of ",{"type":21,"tag":206,"props":14632,"children":14635},{"href":14633,"rel":14634},"https://en.wikipedia.org/wiki/Payment_Card_Industry_Data_Security_Standard",[210],[14636],{"type":26,"value":14637},"PCI Compliance",{"type":26,"value":14639},", while still allowing for a fair amount of customizability, some of which we'll explore shortly.",{"type":21,"tag":22,"props":14641,"children":14642},{},[14643,14645,14652,14654,14660],{"type":26,"value":14644},"Braintree offers a ",{"type":21,"tag":206,"props":14646,"children":14649},{"href":14647,"rel":14648},"https://developers.braintreepayments.com/guides/client-sdk/setup/javascript/v2#install-it-with-npm",[210],[14650],{"type":26,"value":14651},"variety of options",{"type":26,"value":14653}," for integrating the library with your application. For the below, we assume we have a ",{"type":21,"tag":43,"props":14655,"children":14657},{"className":14656},[],[14658],{"type":26,"value":14659},"braintree",{"type":26,"value":14661}," object in global scope:",{"type":21,"tag":595,"props":14663,"children":14667},{"className":14664,"code":14665,"language":14666,"meta":8,"style":8},"language-javascript shiki shiki-themes github-light github-dark","        /**\n         * Request client token and initialize braintree payment library.\n         */\n    setupBraintree:function(){\n        $.ajax({\n            type:'GET',\n            dataType:'json',\n            url:'/payment/token/'\n        }).done(function(res){\n            braintree.setup(res.token, \"dropin\", {\n                container:$('#braintreeContainer')[0],\n                onReady:paymentMethodReady,\n                onPaymentMethodReceived:paymentMethodReceived,\n                onError:paymentMethodError\n            });\n        }.bind(this)).fail(function(jqXHR){\n            console.log(jqXHR, jqXHR.responseJSON || jqXHR.responseText);\n        });\n    }\n","javascript",[14668],{"type":21,"tag":43,"props":14669,"children":14670},{"__ignoreMap":8},[14671,14679,14687,14695,14717,14735,14751,14768,14781,14816,14844,14879,14887,14895,14903,14911,14964,14992,15000],{"type":21,"tag":399,"props":14672,"children":14673},{"class":605,"line":606},[14674],{"type":21,"tag":399,"props":14675,"children":14676},{"style":2818},[14677],{"type":26,"value":14678},"        /**\n",{"type":21,"tag":399,"props":14680,"children":14681},{"class":605,"line":239},[14682],{"type":21,"tag":399,"props":14683,"children":14684},{"style":2818},[14685],{"type":26,"value":14686},"         * Request client token and initialize braintree payment library.\n",{"type":21,"tag":399,"props":14688,"children":14689},{"class":605,"line":232},[14690],{"type":21,"tag":399,"props":14691,"children":14692},{"style":2818},[14693],{"type":26,"value":14694},"         */\n",{"type":21,"tag":399,"props":14696,"children":14697},{"class":605,"line":654},[14698,14703,14707,14712],{"type":21,"tag":399,"props":14699,"children":14700},{"style":2704},[14701],{"type":26,"value":14702},"    setupBraintree",{"type":21,"tag":399,"props":14704,"children":14705},{"style":610},[14706],{"type":26,"value":434},{"type":21,"tag":399,"props":14708,"children":14709},{"style":616},[14710],{"type":26,"value":14711},"function",{"type":21,"tag":399,"props":14713,"children":14714},{"style":610},[14715],{"type":26,"value":14716},"(){\n",{"type":21,"tag":399,"props":14718,"children":14719},{"class":605,"line":698},[14720,14725,14730],{"type":21,"tag":399,"props":14721,"children":14722},{"style":610},[14723],{"type":26,"value":14724},"        $.",{"type":21,"tag":399,"props":14726,"children":14727},{"style":2704},[14728],{"type":26,"value":14729},"ajax",{"type":21,"tag":399,"props":14731,"children":14732},{"style":610},[14733],{"type":26,"value":14734},"({\n",{"type":21,"tag":399,"props":14736,"children":14737},{"class":605,"line":737},[14738,14743,14747],{"type":21,"tag":399,"props":14739,"children":14740},{"style":610},[14741],{"type":26,"value":14742},"            type:",{"type":21,"tag":399,"props":14744,"children":14745},{"style":644},[14746],{"type":26,"value":4427},{"type":21,"tag":399,"props":14748,"children":14749},{"style":610},[14750],{"type":26,"value":638},{"type":21,"tag":399,"props":14752,"children":14753},{"class":605,"line":755},[14754,14759,14764],{"type":21,"tag":399,"props":14755,"children":14756},{"style":610},[14757],{"type":26,"value":14758},"            dataType:",{"type":21,"tag":399,"props":14760,"children":14761},{"style":644},[14762],{"type":26,"value":14763},"'json'",{"type":21,"tag":399,"props":14765,"children":14766},{"style":610},[14767],{"type":26,"value":638},{"type":21,"tag":399,"props":14769,"children":14770},{"class":605,"line":773},[14771,14776],{"type":21,"tag":399,"props":14772,"children":14773},{"style":610},[14774],{"type":26,"value":14775},"            url:",{"type":21,"tag":399,"props":14777,"children":14778},{"style":644},[14779],{"type":26,"value":14780},"'/payment/token/'\n",{"type":21,"tag":399,"props":14782,"children":14783},{"class":605,"line":795},[14784,14789,14794,14798,14802,14806,14811],{"type":21,"tag":399,"props":14785,"children":14786},{"style":610},[14787],{"type":26,"value":14788},"        }).",{"type":21,"tag":399,"props":14790,"children":14791},{"style":2704},[14792],{"type":26,"value":14793},"done",{"type":21,"tag":399,"props":14795,"children":14796},{"style":610},[14797],{"type":26,"value":2712},{"type":21,"tag":399,"props":14799,"children":14800},{"style":616},[14801],{"type":26,"value":14711},{"type":21,"tag":399,"props":14803,"children":14804},{"style":610},[14805],{"type":26,"value":2712},{"type":21,"tag":399,"props":14807,"children":14808},{"style":658},[14809],{"type":26,"value":14810},"res",{"type":21,"tag":399,"props":14812,"children":14813},{"style":610},[14814],{"type":26,"value":14815},"){\n",{"type":21,"tag":399,"props":14817,"children":14818},{"class":605,"line":804},[14819,14824,14829,14834,14839],{"type":21,"tag":399,"props":14820,"children":14821},{"style":610},[14822],{"type":26,"value":14823},"            braintree.",{"type":21,"tag":399,"props":14825,"children":14826},{"style":2704},[14827],{"type":26,"value":14828},"setup",{"type":21,"tag":399,"props":14830,"children":14831},{"style":610},[14832],{"type":26,"value":14833},"(res.token, ",{"type":21,"tag":399,"props":14835,"children":14836},{"style":644},[14837],{"type":26,"value":14838},"\"dropin\"",{"type":21,"tag":399,"props":14840,"children":14841},{"style":610},[14842],{"type":26,"value":14843},", {\n",{"type":21,"tag":399,"props":14845,"children":14846},{"class":605,"line":843},[14847,14852,14856,14860,14865,14870,14874],{"type":21,"tag":399,"props":14848,"children":14849},{"style":610},[14850],{"type":26,"value":14851},"                container:",{"type":21,"tag":399,"props":14853,"children":14854},{"style":2704},[14855],{"type":26,"value":11837},{"type":21,"tag":399,"props":14857,"children":14858},{"style":610},[14859],{"type":26,"value":2712},{"type":21,"tag":399,"props":14861,"children":14862},{"style":644},[14863],{"type":26,"value":14864},"'#braintreeContainer'",{"type":21,"tag":399,"props":14866,"children":14867},{"style":610},[14868],{"type":26,"value":14869},")[",{"type":21,"tag":399,"props":14871,"children":14872},{"style":630},[14873],{"type":26,"value":8903},{"type":21,"tag":399,"props":14875,"children":14876},{"style":610},[14877],{"type":26,"value":14878},"],\n",{"type":21,"tag":399,"props":14880,"children":14881},{"class":605,"line":882},[14882],{"type":21,"tag":399,"props":14883,"children":14884},{"style":610},[14885],{"type":26,"value":14886},"                onReady:paymentMethodReady,\n",{"type":21,"tag":399,"props":14888,"children":14889},{"class":605,"line":900},[14890],{"type":21,"tag":399,"props":14891,"children":14892},{"style":610},[14893],{"type":26,"value":14894},"                onPaymentMethodReceived:paymentMethodReceived,\n",{"type":21,"tag":399,"props":14896,"children":14897},{"class":605,"line":923},[14898],{"type":21,"tag":399,"props":14899,"children":14900},{"style":610},[14901],{"type":26,"value":14902},"                onError:paymentMethodError\n",{"type":21,"tag":399,"props":14904,"children":14905},{"class":605,"line":969},[14906],{"type":21,"tag":399,"props":14907,"children":14908},{"style":610},[14909],{"type":26,"value":14910},"            });\n",{"type":21,"tag":399,"props":14912,"children":14913},{"class":605,"line":991},[14914,14919,14924,14928,14933,14938,14943,14947,14951,14955,14960],{"type":21,"tag":399,"props":14915,"children":14916},{"style":610},[14917],{"type":26,"value":14918},"        }.",{"type":21,"tag":399,"props":14920,"children":14921},{"style":2704},[14922],{"type":26,"value":14923},"bind",{"type":21,"tag":399,"props":14925,"children":14926},{"style":610},[14927],{"type":26,"value":2712},{"type":21,"tag":399,"props":14929,"children":14930},{"style":630},[14931],{"type":26,"value":14932},"this",{"type":21,"tag":399,"props":14934,"children":14935},{"style":610},[14936],{"type":26,"value":14937},")).",{"type":21,"tag":399,"props":14939,"children":14940},{"style":2704},[14941],{"type":26,"value":14942},"fail",{"type":21,"tag":399,"props":14944,"children":14945},{"style":610},[14946],{"type":26,"value":2712},{"type":21,"tag":399,"props":14948,"children":14949},{"style":616},[14950],{"type":26,"value":14711},{"type":21,"tag":399,"props":14952,"children":14953},{"style":610},[14954],{"type":26,"value":2712},{"type":21,"tag":399,"props":14956,"children":14957},{"style":658},[14958],{"type":26,"value":14959},"jqXHR",{"type":21,"tag":399,"props":14961,"children":14962},{"style":610},[14963],{"type":26,"value":14815},{"type":21,"tag":399,"props":14965,"children":14966},{"class":605,"line":1013},[14967,14972,14977,14982,14987],{"type":21,"tag":399,"props":14968,"children":14969},{"style":610},[14970],{"type":26,"value":14971},"            console.",{"type":21,"tag":399,"props":14973,"children":14974},{"style":2704},[14975],{"type":26,"value":14976},"log",{"type":21,"tag":399,"props":14978,"children":14979},{"style":610},[14980],{"type":26,"value":14981},"(jqXHR, jqXHR.responseJSON ",{"type":21,"tag":399,"props":14983,"children":14984},{"style":616},[14985],{"type":26,"value":14986},"||",{"type":21,"tag":399,"props":14988,"children":14989},{"style":610},[14990],{"type":26,"value":14991}," jqXHR.responseText);\n",{"type":21,"tag":399,"props":14993,"children":14994},{"class":605,"line":1035},[14995],{"type":21,"tag":399,"props":14996,"children":14997},{"style":610},[14998],{"type":26,"value":14999},"        });\n",{"type":21,"tag":399,"props":15001,"children":15002},{"class":605,"line":1092},[15003],{"type":21,"tag":399,"props":15004,"children":15005},{"style":610},[15006],{"type":26,"value":4328},{"type":21,"tag":22,"props":15008,"children":15009},{},[15010],{"type":26,"value":15011},"Here, we request the client token and, as soon as we've received it from the server, initialize the braintree library, telling it where to place the 'dropin' iframe, and specifying a number of other options, which we'll return to shortly.",{"type":21,"tag":22,"props":15013,"children":15014},{},[15015,15017,15023],{"type":26,"value":15016},"As we need a node for the braintree iframe to live in, which you'll notice in the above code we've given the id of ",{"type":21,"tag":43,"props":15018,"children":15020},{"className":15019},[],[15021],{"type":26,"value":15022},"braintreeContainer",{"type":26,"value":15024},", a sparse example of the markup might look like:",{"type":21,"tag":595,"props":15026,"children":15030},{"className":15027,"code":15028,"language":15029,"meta":8,"style":8},"language-html shiki shiki-themes github-light github-dark","    \u003Cbody>\n\n        \u003Cform>\n            \u003Cdiv id=\"braintreeContainer\">\u003C/div>\n\n            \u003Cbutton type=\"submit\">Pay Now\u003C/button>\n        \u003C/form>\n\n    \u003C/body>\n","html",[15031],{"type":21,"tag":43,"props":15032,"children":15033},{"__ignoreMap":8},[15034,15053,15060,15077,15117,15124,15163,15179,15186],{"type":21,"tag":399,"props":15035,"children":15036},{"class":605,"line":606},[15037,15042,15048],{"type":21,"tag":399,"props":15038,"children":15039},{"style":610},[15040],{"type":26,"value":15041},"    \u003C",{"type":21,"tag":399,"props":15043,"children":15045},{"style":15044},"--shiki-default:#22863A;--shiki-dark:#85E89D",[15046],{"type":26,"value":15047},"body",{"type":21,"tag":399,"props":15049,"children":15050},{"style":610},[15051],{"type":26,"value":15052},">\n",{"type":21,"tag":399,"props":15054,"children":15055},{"class":605,"line":239},[15056],{"type":21,"tag":399,"props":15057,"children":15058},{"emptyLinePlaceholder":2690},[15059],{"type":26,"value":2693},{"type":21,"tag":399,"props":15061,"children":15062},{"class":605,"line":232},[15063,15068,15073],{"type":21,"tag":399,"props":15064,"children":15065},{"style":610},[15066],{"type":26,"value":15067},"        \u003C",{"type":21,"tag":399,"props":15069,"children":15070},{"style":15044},[15071],{"type":26,"value":15072},"form",{"type":21,"tag":399,"props":15074,"children":15075},{"style":610},[15076],{"type":26,"value":15052},{"type":21,"tag":399,"props":15078,"children":15079},{"class":605,"line":654},[15080,15085,15090,15095,15099,15104,15109,15113],{"type":21,"tag":399,"props":15081,"children":15082},{"style":610},[15083],{"type":26,"value":15084},"            \u003C",{"type":21,"tag":399,"props":15086,"children":15087},{"style":15044},[15088],{"type":26,"value":15089},"div",{"type":21,"tag":399,"props":15091,"children":15092},{"style":2704},[15093],{"type":26,"value":15094}," id",{"type":21,"tag":399,"props":15096,"children":15097},{"style":610},[15098],{"type":26,"value":619},{"type":21,"tag":399,"props":15100,"children":15101},{"style":644},[15102],{"type":26,"value":15103},"\"braintreeContainer\"",{"type":21,"tag":399,"props":15105,"children":15106},{"style":610},[15107],{"type":26,"value":15108},">\u003C/",{"type":21,"tag":399,"props":15110,"children":15111},{"style":15044},[15112],{"type":26,"value":15089},{"type":21,"tag":399,"props":15114,"children":15115},{"style":610},[15116],{"type":26,"value":15052},{"type":21,"tag":399,"props":15118,"children":15119},{"class":605,"line":698},[15120],{"type":21,"tag":399,"props":15121,"children":15122},{"emptyLinePlaceholder":2690},[15123],{"type":26,"value":2693},{"type":21,"tag":399,"props":15125,"children":15126},{"class":605,"line":737},[15127,15131,15136,15141,15145,15150,15155,15159],{"type":21,"tag":399,"props":15128,"children":15129},{"style":610},[15130],{"type":26,"value":15084},{"type":21,"tag":399,"props":15132,"children":15133},{"style":15044},[15134],{"type":26,"value":15135},"button",{"type":21,"tag":399,"props":15137,"children":15138},{"style":2704},[15139],{"type":26,"value":15140}," type",{"type":21,"tag":399,"props":15142,"children":15143},{"style":610},[15144],{"type":26,"value":619},{"type":21,"tag":399,"props":15146,"children":15147},{"style":644},[15148],{"type":26,"value":15149},"\"submit\"",{"type":21,"tag":399,"props":15151,"children":15152},{"style":610},[15153],{"type":26,"value":15154},">Pay Now\u003C/",{"type":21,"tag":399,"props":15156,"children":15157},{"style":15044},[15158],{"type":26,"value":15135},{"type":21,"tag":399,"props":15160,"children":15161},{"style":610},[15162],{"type":26,"value":15052},{"type":21,"tag":399,"props":15164,"children":15165},{"class":605,"line":755},[15166,15171,15175],{"type":21,"tag":399,"props":15167,"children":15168},{"style":610},[15169],{"type":26,"value":15170},"        \u003C/",{"type":21,"tag":399,"props":15172,"children":15173},{"style":15044},[15174],{"type":26,"value":15072},{"type":21,"tag":399,"props":15176,"children":15177},{"style":610},[15178],{"type":26,"value":15052},{"type":21,"tag":399,"props":15180,"children":15181},{"class":605,"line":773},[15182],{"type":21,"tag":399,"props":15183,"children":15184},{"emptyLinePlaceholder":2690},[15185],{"type":26,"value":2693},{"type":21,"tag":399,"props":15187,"children":15188},{"class":605,"line":795},[15189,15194,15198],{"type":21,"tag":399,"props":15190,"children":15191},{"style":610},[15192],{"type":26,"value":15193},"    \u003C/",{"type":21,"tag":399,"props":15195,"children":15196},{"style":15044},[15197],{"type":26,"value":15047},{"type":21,"tag":399,"props":15199,"children":15200},{"style":610},[15201],{"type":26,"value":15052},{"type":21,"tag":22,"props":15203,"children":15204},{},[15205],{"type":26,"value":15206},"Notice the 'Pay Now' button - we'll need to use it to trigger submission of the containing form (which in turn will trigger submission/processing of the input within the braintree iframe). Notice also that we don't specify any form action - we'll be submitting our payment details by ajax, but we could instead specify an action, and send needed details via hidden fields in the form.",{"type":21,"tag":22,"props":15208,"children":15209},{},[15210],{"type":26,"value":15211},"At this point, if those additional options in the braintree.setup pointed to real functions, we would see something like:",{"type":21,"tag":22,"props":15213,"children":15214},{},[15215],{"type":21,"tag":1681,"props":15216,"children":15219},{"alt":15217,"src":15218},"braintree client example","assets/images/bt_ex_1.jpg",[],{"type":21,"tag":190,"props":15221,"children":15223},{"id":15222},"client-options",[15224],{"type":26,"value":15225},"Client Options",{"type":21,"tag":22,"props":15227,"children":15228},{},[15229,15231,15238,15240,15246],{"type":26,"value":15230},"Let's take a closer look at those ",{"type":21,"tag":206,"props":15232,"children":15235},{"href":15233,"rel":15234},"https://developers.braintreepayments.com/reference/client-reference/javascript/v2/configuration#setup-method-options",[210],[15236],{"type":26,"value":15237},"setup options",{"type":26,"value":15239},", then. Besides the obvious ",{"type":21,"tag":43,"props":15241,"children":15243},{"className":15242},[],[15244],{"type":26,"value":15245},"container",{"type":26,"value":15247}," option, we have three more: onReady, onPaymentMethodReceived, and onError, which we supply with callback functions. The Braintree documentation adequately explains each of them (and a number of others we aren't taking a look at today, such as an option to enable CORS requests), so let's instead take at what other things might do in the specified functions.",{"type":21,"tag":595,"props":15249,"children":15251},{"className":14664,"code":15250,"language":14666,"meta":8,"style":8},"    /**\n     * As this function informs us that the braintree setup is complete, if we were hiding the braintree container node\n     * or displaying some other dialog while it loaded, we could now safely reveal it to the user.\n     * @param {object} integration An object containing the teardown, paypal and deviceData objects, as noted\n     * in the braintree docs.\n     */\n    paymentMethodReady:function(integration){\n        // e.g.\n        $('#braintreeContainer').show();\n    }\n    /**\n     * Called by the braintree js SDK once the payment method has been received - that is, once the user has selected\n     * their paypal account or entered their credit card details.\n     * @param {object} recv Contains the following:\n     * nonce: The payment method nonce.\n     * type: A string representing the type of payment method generated; either 'CreditCard' or 'PayPalAccount'.\n     * details: Additional details. See https://developers.braintreepayments.com/reference/client-reference/javascript/v2/configuration#onpaymentmethodreceived-details-object\n     */\n    paymentMethodReceived:function(recv){\n        // We could be submitting information to our server via the form, and add information we need submitted as hidden\n        // fields on the form, but we can also submit the information we want to the server within this callback via ajax.\n        var details = {otherDetails:{/* object containing any other info we want to send up */, payment:recv};\n\n        $.ajax({\n            url:'/payment/process/',\n            type:'POST',\n            dataType:'json',\n            contentType:'application/json',\n            data:JSON.stringify(details)\n        }).done(function(res){\n            // Do something with the response from your server.\n        }.bind(this)).fail(function(jqXHR){\n            var res = jqXHR.responseJSON || jqXHR.responseText;\n            console.log(res);\n        }\n    }\n\n    /**\n     * On payment method error, this callback will receive a detail object, as noted in the setup options\n     * documentation, which gives us some idea as to what error occurred, allowing us to choose how to \n     * respond to it or inform the client about it.\n     * @param {object} detail\n     */\n    paymentMethodError:function(detail){\n        console.log(detail);\n\n        if (detail.type == \"VALIDATION\"){\n            // Potentially display a notification to your user that validation failed.\n            return;\n        }\n\n        // Potentially display or log that another type of error occurred.\n    }\n",[15252],{"type":21,"tag":43,"props":15253,"children":15254},{"__ignoreMap":8},[15255,15263,15271,15279,15307,15315,15323,15352,15360,15391,15398,15405,15413,15421,15446,15454,15462,15470,15477,15506,15514,15522,15554,15561,15576,15592,15608,15623,15640,15667,15698,15706,15753,15784,15800,15807,15814,15821,15828,15836,15844,15852,15872,15879,15904,15921,15928,15954,15962,15975,15983,15991,16000],{"type":21,"tag":399,"props":15256,"children":15257},{"class":605,"line":606},[15258],{"type":21,"tag":399,"props":15259,"children":15260},{"style":2818},[15261],{"type":26,"value":15262},"    /**\n",{"type":21,"tag":399,"props":15264,"children":15265},{"class":605,"line":239},[15266],{"type":21,"tag":399,"props":15267,"children":15268},{"style":2818},[15269],{"type":26,"value":15270},"     * As this function informs us that the braintree setup is complete, if we were hiding the braintree container node\n",{"type":21,"tag":399,"props":15272,"children":15273},{"class":605,"line":232},[15274],{"type":21,"tag":399,"props":15275,"children":15276},{"style":2818},[15277],{"type":26,"value":15278},"     * or displaying some other dialog while it loaded, we could now safely reveal it to the user.\n",{"type":21,"tag":399,"props":15280,"children":15281},{"class":605,"line":654},[15282,15287,15292,15297,15302],{"type":21,"tag":399,"props":15283,"children":15284},{"style":2818},[15285],{"type":26,"value":15286},"     * ",{"type":21,"tag":399,"props":15288,"children":15289},{"style":616},[15290],{"type":26,"value":15291},"@param",{"type":21,"tag":399,"props":15293,"children":15294},{"style":2704},[15295],{"type":26,"value":15296}," {object}",{"type":21,"tag":399,"props":15298,"children":15299},{"style":610},[15300],{"type":26,"value":15301}," integration",{"type":21,"tag":399,"props":15303,"children":15304},{"style":2818},[15305],{"type":26,"value":15306}," An object containing the teardown, paypal and deviceData objects, as noted\n",{"type":21,"tag":399,"props":15308,"children":15309},{"class":605,"line":698},[15310],{"type":21,"tag":399,"props":15311,"children":15312},{"style":2818},[15313],{"type":26,"value":15314},"     * in the braintree docs.\n",{"type":21,"tag":399,"props":15316,"children":15317},{"class":605,"line":737},[15318],{"type":21,"tag":399,"props":15319,"children":15320},{"style":2818},[15321],{"type":26,"value":15322},"     */\n",{"type":21,"tag":399,"props":15324,"children":15325},{"class":605,"line":755},[15326,15331,15335,15339,15343,15348],{"type":21,"tag":399,"props":15327,"children":15328},{"style":2704},[15329],{"type":26,"value":15330},"    paymentMethodReady",{"type":21,"tag":399,"props":15332,"children":15333},{"style":610},[15334],{"type":26,"value":434},{"type":21,"tag":399,"props":15336,"children":15337},{"style":616},[15338],{"type":26,"value":14711},{"type":21,"tag":399,"props":15340,"children":15341},{"style":610},[15342],{"type":26,"value":2712},{"type":21,"tag":399,"props":15344,"children":15345},{"style":658},[15346],{"type":26,"value":15347},"integration",{"type":21,"tag":399,"props":15349,"children":15350},{"style":610},[15351],{"type":26,"value":14815},{"type":21,"tag":399,"props":15353,"children":15354},{"class":605,"line":773},[15355],{"type":21,"tag":399,"props":15356,"children":15357},{"style":2818},[15358],{"type":26,"value":15359},"        // e.g.\n",{"type":21,"tag":399,"props":15361,"children":15362},{"class":605,"line":795},[15363,15368,15372,15376,15381,15386],{"type":21,"tag":399,"props":15364,"children":15365},{"style":2704},[15366],{"type":26,"value":15367},"        $",{"type":21,"tag":399,"props":15369,"children":15370},{"style":610},[15371],{"type":26,"value":2712},{"type":21,"tag":399,"props":15373,"children":15374},{"style":644},[15375],{"type":26,"value":14864},{"type":21,"tag":399,"props":15377,"children":15378},{"style":610},[15379],{"type":26,"value":15380},").",{"type":21,"tag":399,"props":15382,"children":15383},{"style":2704},[15384],{"type":26,"value":15385},"show",{"type":21,"tag":399,"props":15387,"children":15388},{"style":610},[15389],{"type":26,"value":15390},"();\n",{"type":21,"tag":399,"props":15392,"children":15393},{"class":605,"line":804},[15394],{"type":21,"tag":399,"props":15395,"children":15396},{"style":610},[15397],{"type":26,"value":4328},{"type":21,"tag":399,"props":15399,"children":15400},{"class":605,"line":843},[15401],{"type":21,"tag":399,"props":15402,"children":15403},{"style":2818},[15404],{"type":26,"value":15262},{"type":21,"tag":399,"props":15406,"children":15407},{"class":605,"line":882},[15408],{"type":21,"tag":399,"props":15409,"children":15410},{"style":2818},[15411],{"type":26,"value":15412},"     * Called by the braintree js SDK once the payment method has been received - that is, once the user has selected\n",{"type":21,"tag":399,"props":15414,"children":15415},{"class":605,"line":900},[15416],{"type":21,"tag":399,"props":15417,"children":15418},{"style":2818},[15419],{"type":26,"value":15420},"     * their paypal account or entered their credit card details.\n",{"type":21,"tag":399,"props":15422,"children":15423},{"class":605,"line":923},[15424,15428,15432,15436,15441],{"type":21,"tag":399,"props":15425,"children":15426},{"style":2818},[15427],{"type":26,"value":15286},{"type":21,"tag":399,"props":15429,"children":15430},{"style":616},[15431],{"type":26,"value":15291},{"type":21,"tag":399,"props":15433,"children":15434},{"style":2704},[15435],{"type":26,"value":15296},{"type":21,"tag":399,"props":15437,"children":15438},{"style":610},[15439],{"type":26,"value":15440}," recv",{"type":21,"tag":399,"props":15442,"children":15443},{"style":2818},[15444],{"type":26,"value":15445}," Contains the following:\n",{"type":21,"tag":399,"props":15447,"children":15448},{"class":605,"line":969},[15449],{"type":21,"tag":399,"props":15450,"children":15451},{"style":2818},[15452],{"type":26,"value":15453},"     * nonce: The payment method nonce.\n",{"type":21,"tag":399,"props":15455,"children":15456},{"class":605,"line":991},[15457],{"type":21,"tag":399,"props":15458,"children":15459},{"style":2818},[15460],{"type":26,"value":15461},"     * type: A string representing the type of payment method generated; either 'CreditCard' or 'PayPalAccount'.\n",{"type":21,"tag":399,"props":15463,"children":15464},{"class":605,"line":1013},[15465],{"type":21,"tag":399,"props":15466,"children":15467},{"style":2818},[15468],{"type":26,"value":15469},"     * details: Additional details. See https://developers.braintreepayments.com/reference/client-reference/javascript/v2/configuration#onpaymentmethodreceived-details-object\n",{"type":21,"tag":399,"props":15471,"children":15472},{"class":605,"line":1035},[15473],{"type":21,"tag":399,"props":15474,"children":15475},{"style":2818},[15476],{"type":26,"value":15322},{"type":21,"tag":399,"props":15478,"children":15479},{"class":605,"line":1092},[15480,15485,15489,15493,15497,15502],{"type":21,"tag":399,"props":15481,"children":15482},{"style":2704},[15483],{"type":26,"value":15484},"    paymentMethodReceived",{"type":21,"tag":399,"props":15486,"children":15487},{"style":610},[15488],{"type":26,"value":434},{"type":21,"tag":399,"props":15490,"children":15491},{"style":616},[15492],{"type":26,"value":14711},{"type":21,"tag":399,"props":15494,"children":15495},{"style":610},[15496],{"type":26,"value":2712},{"type":21,"tag":399,"props":15498,"children":15499},{"style":658},[15500],{"type":26,"value":15501},"recv",{"type":21,"tag":399,"props":15503,"children":15504},{"style":610},[15505],{"type":26,"value":14815},{"type":21,"tag":399,"props":15507,"children":15508},{"class":605,"line":1114},[15509],{"type":21,"tag":399,"props":15510,"children":15511},{"style":2818},[15512],{"type":26,"value":15513},"        // We could be submitting information to our server via the form, and add information we need submitted as hidden\n",{"type":21,"tag":399,"props":15515,"children":15516},{"class":605,"line":1136},[15517],{"type":21,"tag":399,"props":15518,"children":15519},{"style":2818},[15520],{"type":26,"value":15521},"        // fields on the form, but we can also submit the information we want to the server within this callback via ajax.\n",{"type":21,"tag":399,"props":15523,"children":15524},{"class":605,"line":1158},[15525,15530,15535,15539,15544,15549],{"type":21,"tag":399,"props":15526,"children":15527},{"style":616},[15528],{"type":26,"value":15529},"        var",{"type":21,"tag":399,"props":15531,"children":15532},{"style":610},[15533],{"type":26,"value":15534}," details ",{"type":21,"tag":399,"props":15536,"children":15537},{"style":616},[15538],{"type":26,"value":619},{"type":21,"tag":399,"props":15540,"children":15541},{"style":610},[15542],{"type":26,"value":15543}," {otherDetails:{",{"type":21,"tag":399,"props":15545,"children":15546},{"style":2818},[15547],{"type":26,"value":15548},"/* object containing any other info we want to send up */",{"type":21,"tag":399,"props":15550,"children":15551},{"style":610},[15552],{"type":26,"value":15553},", payment:recv};\n",{"type":21,"tag":399,"props":15555,"children":15556},{"class":605,"line":1180},[15557],{"type":21,"tag":399,"props":15558,"children":15559},{"emptyLinePlaceholder":2690},[15560],{"type":26,"value":2693},{"type":21,"tag":399,"props":15562,"children":15563},{"class":605,"line":1202},[15564,15568,15572],{"type":21,"tag":399,"props":15565,"children":15566},{"style":610},[15567],{"type":26,"value":14724},{"type":21,"tag":399,"props":15569,"children":15570},{"style":2704},[15571],{"type":26,"value":14729},{"type":21,"tag":399,"props":15573,"children":15574},{"style":610},[15575],{"type":26,"value":14734},{"type":21,"tag":399,"props":15577,"children":15578},{"class":605,"line":1211},[15579,15583,15588],{"type":21,"tag":399,"props":15580,"children":15581},{"style":610},[15582],{"type":26,"value":14775},{"type":21,"tag":399,"props":15584,"children":15585},{"style":644},[15586],{"type":26,"value":15587},"'/payment/process/'",{"type":21,"tag":399,"props":15589,"children":15590},{"style":610},[15591],{"type":26,"value":638},{"type":21,"tag":399,"props":15593,"children":15594},{"class":605,"line":1228},[15595,15599,15604],{"type":21,"tag":399,"props":15596,"children":15597},{"style":610},[15598],{"type":26,"value":14742},{"type":21,"tag":399,"props":15600,"children":15601},{"style":644},[15602],{"type":26,"value":15603},"'POST'",{"type":21,"tag":399,"props":15605,"children":15606},{"style":610},[15607],{"type":26,"value":638},{"type":21,"tag":399,"props":15609,"children":15610},{"class":605,"line":1269},[15611,15615,15619],{"type":21,"tag":399,"props":15612,"children":15613},{"style":610},[15614],{"type":26,"value":14758},{"type":21,"tag":399,"props":15616,"children":15617},{"style":644},[15618],{"type":26,"value":14763},{"type":21,"tag":399,"props":15620,"children":15621},{"style":610},[15622],{"type":26,"value":638},{"type":21,"tag":399,"props":15624,"children":15625},{"class":605,"line":1307},[15626,15631,15636],{"type":21,"tag":399,"props":15627,"children":15628},{"style":610},[15629],{"type":26,"value":15630},"            contentType:",{"type":21,"tag":399,"props":15632,"children":15633},{"style":644},[15634],{"type":26,"value":15635},"'application/json'",{"type":21,"tag":399,"props":15637,"children":15638},{"style":610},[15639],{"type":26,"value":638},{"type":21,"tag":399,"props":15641,"children":15642},{"class":605,"line":1346},[15643,15648,15653,15657,15662],{"type":21,"tag":399,"props":15644,"children":15645},{"style":610},[15646],{"type":26,"value":15647},"            data:",{"type":21,"tag":399,"props":15649,"children":15650},{"style":630},[15651],{"type":26,"value":15652},"JSON",{"type":21,"tag":399,"props":15654,"children":15655},{"style":610},[15656],{"type":26,"value":2458},{"type":21,"tag":399,"props":15658,"children":15659},{"style":2704},[15660],{"type":26,"value":15661},"stringify",{"type":21,"tag":399,"props":15663,"children":15664},{"style":610},[15665],{"type":26,"value":15666},"(details)\n",{"type":21,"tag":399,"props":15668,"children":15669},{"class":605,"line":1355},[15670,15674,15678,15682,15686,15690,15694],{"type":21,"tag":399,"props":15671,"children":15672},{"style":610},[15673],{"type":26,"value":14788},{"type":21,"tag":399,"props":15675,"children":15676},{"style":2704},[15677],{"type":26,"value":14793},{"type":21,"tag":399,"props":15679,"children":15680},{"style":610},[15681],{"type":26,"value":2712},{"type":21,"tag":399,"props":15683,"children":15684},{"style":616},[15685],{"type":26,"value":14711},{"type":21,"tag":399,"props":15687,"children":15688},{"style":610},[15689],{"type":26,"value":2712},{"type":21,"tag":399,"props":15691,"children":15692},{"style":658},[15693],{"type":26,"value":14810},{"type":21,"tag":399,"props":15695,"children":15696},{"style":610},[15697],{"type":26,"value":14815},{"type":21,"tag":399,"props":15699,"children":15700},{"class":605,"line":1364},[15701],{"type":21,"tag":399,"props":15702,"children":15703},{"style":2818},[15704],{"type":26,"value":15705},"            // Do something with the response from your server.\n",{"type":21,"tag":399,"props":15707,"children":15708},{"class":605,"line":1403},[15709,15713,15717,15721,15725,15729,15733,15737,15741,15745,15749],{"type":21,"tag":399,"props":15710,"children":15711},{"style":610},[15712],{"type":26,"value":14918},{"type":21,"tag":399,"props":15714,"children":15715},{"style":2704},[15716],{"type":26,"value":14923},{"type":21,"tag":399,"props":15718,"children":15719},{"style":610},[15720],{"type":26,"value":2712},{"type":21,"tag":399,"props":15722,"children":15723},{"style":630},[15724],{"type":26,"value":14932},{"type":21,"tag":399,"props":15726,"children":15727},{"style":610},[15728],{"type":26,"value":14937},{"type":21,"tag":399,"props":15730,"children":15731},{"style":2704},[15732],{"type":26,"value":14942},{"type":21,"tag":399,"props":15734,"children":15735},{"style":610},[15736],{"type":26,"value":2712},{"type":21,"tag":399,"props":15738,"children":15739},{"style":616},[15740],{"type":26,"value":14711},{"type":21,"tag":399,"props":15742,"children":15743},{"style":610},[15744],{"type":26,"value":2712},{"type":21,"tag":399,"props":15746,"children":15747},{"style":658},[15748],{"type":26,"value":14959},{"type":21,"tag":399,"props":15750,"children":15751},{"style":610},[15752],{"type":26,"value":14815},{"type":21,"tag":399,"props":15754,"children":15755},{"class":605,"line":1442},[15756,15761,15766,15770,15775,15779],{"type":21,"tag":399,"props":15757,"children":15758},{"style":616},[15759],{"type":26,"value":15760},"            var",{"type":21,"tag":399,"props":15762,"children":15763},{"style":610},[15764],{"type":26,"value":15765}," res ",{"type":21,"tag":399,"props":15767,"children":15768},{"style":616},[15769],{"type":26,"value":619},{"type":21,"tag":399,"props":15771,"children":15772},{"style":610},[15773],{"type":26,"value":15774}," jqXHR.responseJSON ",{"type":21,"tag":399,"props":15776,"children":15777},{"style":616},[15778],{"type":26,"value":14986},{"type":21,"tag":399,"props":15780,"children":15781},{"style":610},[15782],{"type":26,"value":15783}," jqXHR.responseText;\n",{"type":21,"tag":399,"props":15785,"children":15786},{"class":605,"line":1463},[15787,15791,15795],{"type":21,"tag":399,"props":15788,"children":15789},{"style":610},[15790],{"type":26,"value":14971},{"type":21,"tag":399,"props":15792,"children":15793},{"style":2704},[15794],{"type":26,"value":14976},{"type":21,"tag":399,"props":15796,"children":15797},{"style":610},[15798],{"type":26,"value":15799},"(res);\n",{"type":21,"tag":399,"props":15801,"children":15802},{"class":605,"line":1501},[15803],{"type":21,"tag":399,"props":15804,"children":15805},{"style":610},[15806],{"type":26,"value":1352},{"type":21,"tag":399,"props":15808,"children":15809},{"class":605,"line":1546},[15810],{"type":21,"tag":399,"props":15811,"children":15812},{"style":610},[15813],{"type":26,"value":4328},{"type":21,"tag":399,"props":15815,"children":15816},{"class":605,"line":1585},[15817],{"type":21,"tag":399,"props":15818,"children":15819},{"emptyLinePlaceholder":2690},[15820],{"type":26,"value":2693},{"type":21,"tag":399,"props":15822,"children":15823},{"class":605,"line":1624},[15824],{"type":21,"tag":399,"props":15825,"children":15826},{"style":2818},[15827],{"type":26,"value":15262},{"type":21,"tag":399,"props":15829,"children":15830},{"class":605,"line":1646},[15831],{"type":21,"tag":399,"props":15832,"children":15833},{"style":2818},[15834],{"type":26,"value":15835},"     * On payment method error, this callback will receive a detail object, as noted in the setup options\n",{"type":21,"tag":399,"props":15837,"children":15838},{"class":605,"line":4599},[15839],{"type":21,"tag":399,"props":15840,"children":15841},{"style":2818},[15842],{"type":26,"value":15843},"     * documentation, which gives us some idea as to what error occurred, allowing us to choose how to \n",{"type":21,"tag":399,"props":15845,"children":15846},{"class":605,"line":4608},[15847],{"type":21,"tag":399,"props":15848,"children":15849},{"style":2818},[15850],{"type":26,"value":15851},"     * respond to it or inform the client about it.\n",{"type":21,"tag":399,"props":15853,"children":15854},{"class":605,"line":4617},[15855,15859,15863,15867],{"type":21,"tag":399,"props":15856,"children":15857},{"style":2818},[15858],{"type":26,"value":15286},{"type":21,"tag":399,"props":15860,"children":15861},{"style":616},[15862],{"type":26,"value":15291},{"type":21,"tag":399,"props":15864,"children":15865},{"style":2704},[15866],{"type":26,"value":15296},{"type":21,"tag":399,"props":15868,"children":15869},{"style":610},[15870],{"type":26,"value":15871}," detail\n",{"type":21,"tag":399,"props":15873,"children":15874},{"class":605,"line":4626},[15875],{"type":21,"tag":399,"props":15876,"children":15877},{"style":2818},[15878],{"type":26,"value":15322},{"type":21,"tag":399,"props":15880,"children":15881},{"class":605,"line":4635},[15882,15887,15891,15895,15900],{"type":21,"tag":399,"props":15883,"children":15884},{"style":610},[15885],{"type":26,"value":15886},"    paymentMethodError:",{"type":21,"tag":399,"props":15888,"children":15889},{"style":616},[15890],{"type":26,"value":14711},{"type":21,"tag":399,"props":15892,"children":15893},{"style":610},[15894],{"type":26,"value":2712},{"type":21,"tag":399,"props":15896,"children":15897},{"style":658},[15898],{"type":26,"value":15899},"detail",{"type":21,"tag":399,"props":15901,"children":15902},{"style":610},[15903],{"type":26,"value":14815},{"type":21,"tag":399,"props":15905,"children":15906},{"class":605,"line":4649},[15907,15912,15916],{"type":21,"tag":399,"props":15908,"children":15909},{"style":610},[15910],{"type":26,"value":15911},"        console.",{"type":21,"tag":399,"props":15913,"children":15914},{"style":2704},[15915],{"type":26,"value":14976},{"type":21,"tag":399,"props":15917,"children":15918},{"style":610},[15919],{"type":26,"value":15920},"(detail);\n",{"type":21,"tag":399,"props":15922,"children":15923},{"class":605,"line":4657},[15924],{"type":21,"tag":399,"props":15925,"children":15926},{"emptyLinePlaceholder":2690},[15927],{"type":26,"value":2693},{"type":21,"tag":399,"props":15929,"children":15930},{"class":605,"line":4686},[15931,15936,15941,15945,15950],{"type":21,"tag":399,"props":15932,"children":15933},{"style":616},[15934],{"type":26,"value":15935},"        if",{"type":21,"tag":399,"props":15937,"children":15938},{"style":610},[15939],{"type":26,"value":15940}," (detail.type ",{"type":21,"tag":399,"props":15942,"children":15943},{"style":616},[15944],{"type":26,"value":1070},{"type":21,"tag":399,"props":15946,"children":15947},{"style":644},[15948],{"type":26,"value":15949}," \"VALIDATION\"",{"type":21,"tag":399,"props":15951,"children":15952},{"style":610},[15953],{"type":26,"value":14815},{"type":21,"tag":399,"props":15955,"children":15956},{"class":605,"line":12002},[15957],{"type":21,"tag":399,"props":15958,"children":15959},{"style":2818},[15960],{"type":26,"value":15961},"            // Potentially display a notification to your user that validation failed.\n",{"type":21,"tag":399,"props":15963,"children":15965},{"class":605,"line":15964},49,[15966,15971],{"type":21,"tag":399,"props":15967,"children":15968},{"style":616},[15969],{"type":26,"value":15970},"            return",{"type":21,"tag":399,"props":15972,"children":15973},{"style":610},[15974],{"type":26,"value":6581},{"type":21,"tag":399,"props":15976,"children":15978},{"class":605,"line":15977},50,[15979],{"type":21,"tag":399,"props":15980,"children":15981},{"style":610},[15982],{"type":26,"value":1352},{"type":21,"tag":399,"props":15984,"children":15986},{"class":605,"line":15985},51,[15987],{"type":21,"tag":399,"props":15988,"children":15989},{"emptyLinePlaceholder":2690},[15990],{"type":26,"value":2693},{"type":21,"tag":399,"props":15992,"children":15994},{"class":605,"line":15993},52,[15995],{"type":21,"tag":399,"props":15996,"children":15997},{"style":2818},[15998],{"type":26,"value":15999},"        // Potentially display or log that another type of error occurred.\n",{"type":21,"tag":399,"props":16001,"children":16003},{"class":605,"line":16002},53,[16004],{"type":21,"tag":399,"props":16005,"children":16006},{"style":610},[16007],{"type":26,"value":4328},{"type":21,"tag":190,"props":16009,"children":16011},{"id":16010},"perform-transaction",[16012],{"type":26,"value":16013},"Perform Transaction",{"type":21,"tag":22,"props":16015,"children":16016},{},[16017,16019,16025],{"type":26,"value":16018},"You'll notice that we specified an endpoint of ",{"type":21,"tag":43,"props":16020,"children":16022},{"className":16021},[],[16023],{"type":26,"value":16024},"/payment/process/",{"type":26,"value":16026}," in the paymentMethodReceived callback on the client-side. So, on the server side, let's define this endpoint and finally process our payment.",{"type":21,"tag":595,"props":16028,"children":16030},{"className":597,"code":16029,"language":16,"meta":8,"style":8},"import braintree\nimport json\n\nfrom django.views.decorators.http import require_http_methods\nfrom django.http import JsonResponse\n\n#from yourapp.models import SaleItem, TransactionRecord\n\n@require_http_methods(['POST'])\ndef perform_braintree_simple_sale(request):\n    \"\"\"\n    Perform one-time, immediate settlement sale transaction using\n    the payment nonce sent to us by the client.\n    On failure, return the failure details to the client.\n    \"\"\"\n    try:\n        details = json.loads(request.body)\n    except ValueError:\n        # Handle the error case where you fail to parse the request body\n        return JsonResponse(status=400)\n\n    payment_details = details['payment']\n\n    # Note that we get the value of the transaction from a database row - generally speaking,\n    # it's a bad idea to trust the client to define the transaction amount - otherwise, the client\n    # may alter the request to say, have themselves charged $1 for something you want to charge $100\n    # for, and if you don't validate this, you're out $99. Allowing the client to specify what 'item' \n    # they'd like to purchase is safer, however, as long as the server remains in control of setting and\n    # confirming the price.\n    item = SaleItem.objects.get(type=\"your-sale-item\")\n    sale = {\n        \"amount\": str(item.amount),\n        \"payment_method_nonce\": payment_details['nonce'],\n        \"descriptor\": {\"name\": \"BIZ*NAME\"},\n        \"options\": {\n            \"paypal\": {\"description\": \"BizName (bizname.foo)\"},\n            # Note that setting this to true indicates we want both authorization and immediate settlement of this charge\n            \"submit_for_settlement\": True \n        }\n    }\n\n    # Perform transaction, making the remote request to braintree\n    payment_result = braintree.Transaction.sale(sale)\n\n    if payment_result.is_success is not True:\n        if not hasattr(payment_result, 'transation'):\n            \"\"\"\n            If the transaction was not successfully processed by braintree,\n            the list of possible errors is staggeringly long:\n            @see https://developers.braintreepayments.com/reference/general/validation-errors/all/python#transaction\n            You'll likely want to iterate through the deep_errors list and handle them appropriately.\n            \"\"\"\n            return JsonResponse(status=500)\n        else:\n            \"\"\"\n            If the transacation was successfully processed by braintree, but was rejected by\n            the payment processor, you'll want to handle this rejection appropriately.\n            @see https://developers.braintreepayments.com/reference/general/processor-responses/settlement-responses\n            \"\"\"\n            return JsonResponse(status=500)\n\n    transaction = payment_result.transaction\n    if transaction.amount \u003C item.amount:\n        # Confirm you actually were paid as much as your item costs! If you don't submit for settlement,\n        # or only submit for partial settlement, you should skip this check.\n        return JsonResponse(status=500)\n\n    # Transaction successful - you can now proceed to create a new user, produce a download link for an item,\n    # or whatever else it is you need payment processing for.\n    # You can also now create a local reference for the transaction, in case you ever need to look it up for refund\n    # purposes, for example.\n    TransactionRecord.objects.create(item=item, amount=transaction.amount,\n                                     transaction_id=transaction.id, type=transaction.payment_instrument_type)\n\n    return JsonResponse(status=200)\n",[16031],{"type":21,"tag":43,"props":16032,"children":16033},{"__ignoreMap":8},[16034,16045,16057,16064,16083,16102,16109,16117,16124,16143,16159,16166,16174,16182,16190,16197,16208,16225,16240,16248,16277,16284,16310,16317,16325,16333,16341,16349,16357,16365,16400,16416,16437,16459,16489,16501,16531,16539,16559,16566,16573,16580,16588,16605,16612,16642,16672,16680,16688,16696,16704,16712,16719,16746,16759,16767,16776,16785,16794,16802,16830,16838,16856,16879,16888,16897,16925,16933,16942,16951,16960,16969,17006,17037,17045],{"type":21,"tag":399,"props":16035,"children":16036},{"class":605,"line":606},[16037,16041],{"type":21,"tag":399,"props":16038,"children":16039},{"style":616},[16040],{"type":26,"value":2679},{"type":21,"tag":399,"props":16042,"children":16043},{"style":610},[16044],{"type":26,"value":13982},{"type":21,"tag":399,"props":16046,"children":16047},{"class":605,"line":239},[16048,16052],{"type":21,"tag":399,"props":16049,"children":16050},{"style":616},[16051],{"type":26,"value":2679},{"type":21,"tag":399,"props":16053,"children":16054},{"style":610},[16055],{"type":26,"value":16056}," json\n",{"type":21,"tag":399,"props":16058,"children":16059},{"class":605,"line":232},[16060],{"type":21,"tag":399,"props":16061,"children":16062},{"emptyLinePlaceholder":2690},[16063],{"type":26,"value":2693},{"type":21,"tag":399,"props":16065,"children":16066},{"class":605,"line":654},[16067,16071,16075,16079],{"type":21,"tag":399,"props":16068,"children":16069},{"style":616},[16070],{"type":26,"value":2669},{"type":21,"tag":399,"props":16072,"children":16073},{"style":610},[16074],{"type":26,"value":14161},{"type":21,"tag":399,"props":16076,"children":16077},{"style":616},[16078],{"type":26,"value":2679},{"type":21,"tag":399,"props":16080,"children":16081},{"style":610},[16082],{"type":26,"value":14170},{"type":21,"tag":399,"props":16084,"children":16085},{"class":605,"line":698},[16086,16090,16094,16098],{"type":21,"tag":399,"props":16087,"children":16088},{"style":616},[16089],{"type":26,"value":2669},{"type":21,"tag":399,"props":16091,"children":16092},{"style":610},[16093],{"type":26,"value":11144},{"type":21,"tag":399,"props":16095,"children":16096},{"style":616},[16097],{"type":26,"value":2679},{"type":21,"tag":399,"props":16099,"children":16100},{"style":610},[16101],{"type":26,"value":14149},{"type":21,"tag":399,"props":16103,"children":16104},{"class":605,"line":737},[16105],{"type":21,"tag":399,"props":16106,"children":16107},{"emptyLinePlaceholder":2690},[16108],{"type":26,"value":2693},{"type":21,"tag":399,"props":16110,"children":16111},{"class":605,"line":755},[16112],{"type":21,"tag":399,"props":16113,"children":16114},{"style":2818},[16115],{"type":26,"value":16116},"#from yourapp.models import SaleItem, TransactionRecord\n",{"type":21,"tag":399,"props":16118,"children":16119},{"class":605,"line":773},[16120],{"type":21,"tag":399,"props":16121,"children":16122},{"emptyLinePlaceholder":2690},[16123],{"type":26,"value":2693},{"type":21,"tag":399,"props":16125,"children":16126},{"class":605,"line":795},[16127,16131,16135,16139],{"type":21,"tag":399,"props":16128,"children":16129},{"style":2704},[16130],{"type":26,"value":14185},{"type":21,"tag":399,"props":16132,"children":16133},{"style":610},[16134],{"type":26,"value":14190},{"type":21,"tag":399,"props":16136,"children":16137},{"style":644},[16138],{"type":26,"value":15603},{"type":21,"tag":399,"props":16140,"children":16141},{"style":610},[16142],{"type":26,"value":4432},{"type":21,"tag":399,"props":16144,"children":16145},{"class":605,"line":804},[16146,16150,16155],{"type":21,"tag":399,"props":16147,"children":16148},{"style":616},[16149],{"type":26,"value":3281},{"type":21,"tag":399,"props":16151,"children":16152},{"style":2704},[16153],{"type":26,"value":16154}," perform_braintree_simple_sale",{"type":21,"tag":399,"props":16156,"children":16157},{"style":610},[16158],{"type":26,"value":14215},{"type":21,"tag":399,"props":16160,"children":16161},{"class":605,"line":843},[16162],{"type":21,"tag":399,"props":16163,"children":16164},{"style":644},[16165],{"type":26,"value":14223},{"type":21,"tag":399,"props":16167,"children":16168},{"class":605,"line":882},[16169],{"type":21,"tag":399,"props":16170,"children":16171},{"style":644},[16172],{"type":26,"value":16173},"    Perform one-time, immediate settlement sale transaction using\n",{"type":21,"tag":399,"props":16175,"children":16176},{"class":605,"line":900},[16177],{"type":21,"tag":399,"props":16178,"children":16179},{"style":644},[16180],{"type":26,"value":16181},"    the payment nonce sent to us by the client.\n",{"type":21,"tag":399,"props":16183,"children":16184},{"class":605,"line":923},[16185],{"type":21,"tag":399,"props":16186,"children":16187},{"style":644},[16188],{"type":26,"value":16189},"    On failure, return the failure details to the client.\n",{"type":21,"tag":399,"props":16191,"children":16192},{"class":605,"line":969},[16193],{"type":21,"tag":399,"props":16194,"children":16195},{"style":644},[16196],{"type":26,"value":14223},{"type":21,"tag":399,"props":16198,"children":16199},{"class":605,"line":991},[16200,16204],{"type":21,"tag":399,"props":16201,"children":16202},{"style":616},[16203],{"type":26,"value":14246},{"type":21,"tag":399,"props":16205,"children":16206},{"style":610},[16207],{"type":26,"value":4683},{"type":21,"tag":399,"props":16209,"children":16210},{"class":605,"line":1013},[16211,16216,16220],{"type":21,"tag":399,"props":16212,"children":16213},{"style":610},[16214],{"type":26,"value":16215},"        details ",{"type":21,"tag":399,"props":16217,"children":16218},{"style":616},[16219],{"type":26,"value":619},{"type":21,"tag":399,"props":16221,"children":16222},{"style":610},[16223],{"type":26,"value":16224}," json.loads(request.body)\n",{"type":21,"tag":399,"props":16226,"children":16227},{"class":605,"line":1035},[16228,16232,16236],{"type":21,"tag":399,"props":16229,"children":16230},{"style":616},[16231],{"type":26,"value":14275},{"type":21,"tag":399,"props":16233,"children":16234},{"style":630},[16235],{"type":26,"value":14280},{"type":21,"tag":399,"props":16237,"children":16238},{"style":610},[16239],{"type":26,"value":4683},{"type":21,"tag":399,"props":16241,"children":16242},{"class":605,"line":1092},[16243],{"type":21,"tag":399,"props":16244,"children":16245},{"style":2818},[16246],{"type":26,"value":16247},"        # Handle the error case where you fail to parse the request body\n",{"type":21,"tag":399,"props":16249,"children":16250},{"class":605,"line":1114},[16251,16255,16260,16264,16268,16273],{"type":21,"tag":399,"props":16252,"children":16253},{"style":616},[16254],{"type":26,"value":14297},{"type":21,"tag":399,"props":16256,"children":16257},{"style":610},[16258],{"type":26,"value":16259}," JsonResponse(",{"type":21,"tag":399,"props":16261,"children":16262},{"style":658},[16263],{"type":26,"value":14317},{"type":21,"tag":399,"props":16265,"children":16266},{"style":616},[16267],{"type":26,"value":619},{"type":21,"tag":399,"props":16269,"children":16270},{"style":630},[16271],{"type":26,"value":16272},"400",{"type":21,"tag":399,"props":16274,"children":16275},{"style":610},[16276],{"type":26,"value":1652},{"type":21,"tag":399,"props":16278,"children":16279},{"class":605,"line":1136},[16280],{"type":21,"tag":399,"props":16281,"children":16282},{"emptyLinePlaceholder":2690},[16283],{"type":26,"value":2693},{"type":21,"tag":399,"props":16285,"children":16286},{"class":605,"line":1158},[16287,16292,16296,16301,16306],{"type":21,"tag":399,"props":16288,"children":16289},{"style":610},[16290],{"type":26,"value":16291},"    payment_details ",{"type":21,"tag":399,"props":16293,"children":16294},{"style":616},[16295],{"type":26,"value":619},{"type":21,"tag":399,"props":16297,"children":16298},{"style":610},[16299],{"type":26,"value":16300}," details[",{"type":21,"tag":399,"props":16302,"children":16303},{"style":644},[16304],{"type":26,"value":16305},"'payment'",{"type":21,"tag":399,"props":16307,"children":16308},{"style":610},[16309],{"type":26,"value":10997},{"type":21,"tag":399,"props":16311,"children":16312},{"class":605,"line":1180},[16313],{"type":21,"tag":399,"props":16314,"children":16315},{"emptyLinePlaceholder":2690},[16316],{"type":26,"value":2693},{"type":21,"tag":399,"props":16318,"children":16319},{"class":605,"line":1202},[16320],{"type":21,"tag":399,"props":16321,"children":16322},{"style":2818},[16323],{"type":26,"value":16324},"    # Note that we get the value of the transaction from a database row - generally speaking,\n",{"type":21,"tag":399,"props":16326,"children":16327},{"class":605,"line":1211},[16328],{"type":21,"tag":399,"props":16329,"children":16330},{"style":2818},[16331],{"type":26,"value":16332},"    # it's a bad idea to trust the client to define the transaction amount - otherwise, the client\n",{"type":21,"tag":399,"props":16334,"children":16335},{"class":605,"line":1228},[16336],{"type":21,"tag":399,"props":16337,"children":16338},{"style":2818},[16339],{"type":26,"value":16340},"    # may alter the request to say, have themselves charged $1 for something you want to charge $100\n",{"type":21,"tag":399,"props":16342,"children":16343},{"class":605,"line":1269},[16344],{"type":21,"tag":399,"props":16345,"children":16346},{"style":2818},[16347],{"type":26,"value":16348},"    # for, and if you don't validate this, you're out $99. Allowing the client to specify what 'item' \n",{"type":21,"tag":399,"props":16350,"children":16351},{"class":605,"line":1307},[16352],{"type":21,"tag":399,"props":16353,"children":16354},{"style":2818},[16355],{"type":26,"value":16356},"    # they'd like to purchase is safer, however, as long as the server remains in control of setting and\n",{"type":21,"tag":399,"props":16358,"children":16359},{"class":605,"line":1346},[16360],{"type":21,"tag":399,"props":16361,"children":16362},{"style":2818},[16363],{"type":26,"value":16364},"    # confirming the price.\n",{"type":21,"tag":399,"props":16366,"children":16367},{"class":605,"line":1355},[16368,16373,16377,16382,16387,16391,16396],{"type":21,"tag":399,"props":16369,"children":16370},{"style":610},[16371],{"type":26,"value":16372},"    item ",{"type":21,"tag":399,"props":16374,"children":16375},{"style":616},[16376],{"type":26,"value":619},{"type":21,"tag":399,"props":16378,"children":16379},{"style":610},[16380],{"type":26,"value":16381}," SaleItem.objects.get(",{"type":21,"tag":399,"props":16383,"children":16384},{"style":658},[16385],{"type":26,"value":16386},"type",{"type":21,"tag":399,"props":16388,"children":16389},{"style":616},[16390],{"type":26,"value":619},{"type":21,"tag":399,"props":16392,"children":16393},{"style":644},[16394],{"type":26,"value":16395},"\"your-sale-item\"",{"type":21,"tag":399,"props":16397,"children":16398},{"style":610},[16399],{"type":26,"value":1652},{"type":21,"tag":399,"props":16401,"children":16402},{"class":605,"line":1364},[16403,16408,16412],{"type":21,"tag":399,"props":16404,"children":16405},{"style":610},[16406],{"type":26,"value":16407},"    sale ",{"type":21,"tag":399,"props":16409,"children":16410},{"style":616},[16411],{"type":26,"value":619},{"type":21,"tag":399,"props":16413,"children":16414},{"style":610},[16415],{"type":26,"value":8993},{"type":21,"tag":399,"props":16417,"children":16418},{"class":605,"line":1403},[16419,16424,16428,16432],{"type":21,"tag":399,"props":16420,"children":16421},{"style":644},[16422],{"type":26,"value":16423},"        \"amount\"",{"type":21,"tag":399,"props":16425,"children":16426},{"style":610},[16427],{"type":26,"value":911},{"type":21,"tag":399,"props":16429,"children":16430},{"style":630},[16431],{"type":26,"value":3448},{"type":21,"tag":399,"props":16433,"children":16434},{"style":610},[16435],{"type":26,"value":16436},"(item.amount),\n",{"type":21,"tag":399,"props":16438,"children":16439},{"class":605,"line":1442},[16440,16445,16450,16455],{"type":21,"tag":399,"props":16441,"children":16442},{"style":644},[16443],{"type":26,"value":16444},"        \"payment_method_nonce\"",{"type":21,"tag":399,"props":16446,"children":16447},{"style":610},[16448],{"type":26,"value":16449},": payment_details[",{"type":21,"tag":399,"props":16451,"children":16452},{"style":644},[16453],{"type":26,"value":16454},"'nonce'",{"type":21,"tag":399,"props":16456,"children":16457},{"style":610},[16458],{"type":26,"value":14878},{"type":21,"tag":399,"props":16460,"children":16461},{"class":605,"line":1463},[16462,16467,16472,16476,16480,16485],{"type":21,"tag":399,"props":16463,"children":16464},{"style":644},[16465],{"type":26,"value":16466},"        \"descriptor\"",{"type":21,"tag":399,"props":16468,"children":16469},{"style":610},[16470],{"type":26,"value":16471},": {",{"type":21,"tag":399,"props":16473,"children":16474},{"style":644},[16475],{"type":26,"value":3438},{"type":21,"tag":399,"props":16477,"children":16478},{"style":610},[16479],{"type":26,"value":911},{"type":21,"tag":399,"props":16481,"children":16482},{"style":644},[16483],{"type":26,"value":16484},"\"BIZ*NAME\"",{"type":21,"tag":399,"props":16486,"children":16487},{"style":610},[16488],{"type":26,"value":10805},{"type":21,"tag":399,"props":16490,"children":16491},{"class":605,"line":1501},[16492,16497],{"type":21,"tag":399,"props":16493,"children":16494},{"style":644},[16495],{"type":26,"value":16496},"        \"options\"",{"type":21,"tag":399,"props":16498,"children":16499},{"style":610},[16500],{"type":26,"value":10547},{"type":21,"tag":399,"props":16502,"children":16503},{"class":605,"line":1546},[16504,16509,16513,16518,16522,16527],{"type":21,"tag":399,"props":16505,"children":16506},{"style":644},[16507],{"type":26,"value":16508},"            \"paypal\"",{"type":21,"tag":399,"props":16510,"children":16511},{"style":610},[16512],{"type":26,"value":16471},{"type":21,"tag":399,"props":16514,"children":16515},{"style":644},[16516],{"type":26,"value":16517},"\"description\"",{"type":21,"tag":399,"props":16519,"children":16520},{"style":610},[16521],{"type":26,"value":911},{"type":21,"tag":399,"props":16523,"children":16524},{"style":644},[16525],{"type":26,"value":16526},"\"BizName (bizname.foo)\"",{"type":21,"tag":399,"props":16528,"children":16529},{"style":610},[16530],{"type":26,"value":10805},{"type":21,"tag":399,"props":16532,"children":16533},{"class":605,"line":1585},[16534],{"type":21,"tag":399,"props":16535,"children":16536},{"style":2818},[16537],{"type":26,"value":16538},"            # Note that setting this to true indicates we want both authorization and immediate settlement of this charge\n",{"type":21,"tag":399,"props":16540,"children":16541},{"class":605,"line":1624},[16542,16547,16551,16555],{"type":21,"tag":399,"props":16543,"children":16544},{"style":644},[16545],{"type":26,"value":16546},"            \"submit_for_settlement\"",{"type":21,"tag":399,"props":16548,"children":16549},{"style":610},[16550],{"type":26,"value":911},{"type":21,"tag":399,"props":16552,"children":16553},{"style":630},[16554],{"type":26,"value":875},{"type":21,"tag":399,"props":16556,"children":16557},{"style":610},[16558],{"type":26,"value":4646},{"type":21,"tag":399,"props":16560,"children":16561},{"class":605,"line":1646},[16562],{"type":21,"tag":399,"props":16563,"children":16564},{"style":610},[16565],{"type":26,"value":1352},{"type":21,"tag":399,"props":16567,"children":16568},{"class":605,"line":4599},[16569],{"type":21,"tag":399,"props":16570,"children":16571},{"style":610},[16572],{"type":26,"value":4328},{"type":21,"tag":399,"props":16574,"children":16575},{"class":605,"line":4608},[16576],{"type":21,"tag":399,"props":16577,"children":16578},{"emptyLinePlaceholder":2690},[16579],{"type":26,"value":2693},{"type":21,"tag":399,"props":16581,"children":16582},{"class":605,"line":4617},[16583],{"type":21,"tag":399,"props":16584,"children":16585},{"style":2818},[16586],{"type":26,"value":16587},"    # Perform transaction, making the remote request to braintree\n",{"type":21,"tag":399,"props":16589,"children":16590},{"class":605,"line":4626},[16591,16596,16600],{"type":21,"tag":399,"props":16592,"children":16593},{"style":610},[16594],{"type":26,"value":16595},"    payment_result ",{"type":21,"tag":399,"props":16597,"children":16598},{"style":616},[16599],{"type":26,"value":619},{"type":21,"tag":399,"props":16601,"children":16602},{"style":610},[16603],{"type":26,"value":16604}," braintree.Transaction.sale(sale)\n",{"type":21,"tag":399,"props":16606,"children":16607},{"class":605,"line":4635},[16608],{"type":21,"tag":399,"props":16609,"children":16610},{"emptyLinePlaceholder":2690},[16611],{"type":26,"value":2693},{"type":21,"tag":399,"props":16613,"children":16614},{"class":605,"line":4649},[16615,16620,16625,16630,16634,16638],{"type":21,"tag":399,"props":16616,"children":16617},{"style":616},[16618],{"type":26,"value":16619},"    if",{"type":21,"tag":399,"props":16621,"children":16622},{"style":610},[16623],{"type":26,"value":16624}," payment_result.is_success ",{"type":21,"tag":399,"props":16626,"children":16627},{"style":616},[16628],{"type":26,"value":16629},"is",{"type":21,"tag":399,"props":16631,"children":16632},{"style":616},[16633],{"type":26,"value":3327},{"type":21,"tag":399,"props":16635,"children":16636},{"style":630},[16637],{"type":26,"value":7692},{"type":21,"tag":399,"props":16639,"children":16640},{"style":610},[16641],{"type":26,"value":4683},{"type":21,"tag":399,"props":16643,"children":16644},{"class":605,"line":4657},[16645,16649,16653,16658,16663,16668],{"type":21,"tag":399,"props":16646,"children":16647},{"style":616},[16648],{"type":26,"value":15935},{"type":21,"tag":399,"props":16650,"children":16651},{"style":616},[16652],{"type":26,"value":3327},{"type":21,"tag":399,"props":16654,"children":16655},{"style":630},[16656],{"type":26,"value":16657}," hasattr",{"type":21,"tag":399,"props":16659,"children":16660},{"style":610},[16661],{"type":26,"value":16662},"(payment_result, ",{"type":21,"tag":399,"props":16664,"children":16665},{"style":644},[16666],{"type":26,"value":16667},"'transation'",{"type":21,"tag":399,"props":16669,"children":16670},{"style":610},[16671],{"type":26,"value":2722},{"type":21,"tag":399,"props":16673,"children":16674},{"class":605,"line":4686},[16675],{"type":21,"tag":399,"props":16676,"children":16677},{"style":644},[16678],{"type":26,"value":16679},"            \"\"\"\n",{"type":21,"tag":399,"props":16681,"children":16682},{"class":605,"line":12002},[16683],{"type":21,"tag":399,"props":16684,"children":16685},{"style":644},[16686],{"type":26,"value":16687},"            If the transaction was not successfully processed by braintree,\n",{"type":21,"tag":399,"props":16689,"children":16690},{"class":605,"line":15964},[16691],{"type":21,"tag":399,"props":16692,"children":16693},{"style":644},[16694],{"type":26,"value":16695},"            the list of possible errors is staggeringly long:\n",{"type":21,"tag":399,"props":16697,"children":16698},{"class":605,"line":15977},[16699],{"type":21,"tag":399,"props":16700,"children":16701},{"style":644},[16702],{"type":26,"value":16703},"            @see https://developers.braintreepayments.com/reference/general/validation-errors/all/python#transaction\n",{"type":21,"tag":399,"props":16705,"children":16706},{"class":605,"line":15985},[16707],{"type":21,"tag":399,"props":16708,"children":16709},{"style":644},[16710],{"type":26,"value":16711},"            You'll likely want to iterate through the deep_errors list and handle them appropriately.\n",{"type":21,"tag":399,"props":16713,"children":16714},{"class":605,"line":15993},[16715],{"type":21,"tag":399,"props":16716,"children":16717},{"style":644},[16718],{"type":26,"value":16679},{"type":21,"tag":399,"props":16720,"children":16721},{"class":605,"line":16002},[16722,16726,16730,16734,16738,16742],{"type":21,"tag":399,"props":16723,"children":16724},{"style":616},[16725],{"type":26,"value":15970},{"type":21,"tag":399,"props":16727,"children":16728},{"style":610},[16729],{"type":26,"value":16259},{"type":21,"tag":399,"props":16731,"children":16732},{"style":658},[16733],{"type":26,"value":14317},{"type":21,"tag":399,"props":16735,"children":16736},{"style":616},[16737],{"type":26,"value":619},{"type":21,"tag":399,"props":16739,"children":16740},{"style":630},[16741],{"type":26,"value":14326},{"type":21,"tag":399,"props":16743,"children":16744},{"style":610},[16745],{"type":26,"value":1652},{"type":21,"tag":399,"props":16747,"children":16749},{"class":605,"line":16748},54,[16750,16755],{"type":21,"tag":399,"props":16751,"children":16752},{"style":616},[16753],{"type":26,"value":16754},"        else",{"type":21,"tag":399,"props":16756,"children":16757},{"style":610},[16758],{"type":26,"value":4683},{"type":21,"tag":399,"props":16760,"children":16762},{"class":605,"line":16761},55,[16763],{"type":21,"tag":399,"props":16764,"children":16765},{"style":644},[16766],{"type":26,"value":16679},{"type":21,"tag":399,"props":16768,"children":16770},{"class":605,"line":16769},56,[16771],{"type":21,"tag":399,"props":16772,"children":16773},{"style":644},[16774],{"type":26,"value":16775},"            If the transacation was successfully processed by braintree, but was rejected by\n",{"type":21,"tag":399,"props":16777,"children":16779},{"class":605,"line":16778},57,[16780],{"type":21,"tag":399,"props":16781,"children":16782},{"style":644},[16783],{"type":26,"value":16784},"            the payment processor, you'll want to handle this rejection appropriately.\n",{"type":21,"tag":399,"props":16786,"children":16788},{"class":605,"line":16787},58,[16789],{"type":21,"tag":399,"props":16790,"children":16791},{"style":644},[16792],{"type":26,"value":16793},"            @see https://developers.braintreepayments.com/reference/general/processor-responses/settlement-responses\n",{"type":21,"tag":399,"props":16795,"children":16797},{"class":605,"line":16796},59,[16798],{"type":21,"tag":399,"props":16799,"children":16800},{"style":644},[16801],{"type":26,"value":16679},{"type":21,"tag":399,"props":16803,"children":16805},{"class":605,"line":16804},60,[16806,16810,16814,16818,16822,16826],{"type":21,"tag":399,"props":16807,"children":16808},{"style":616},[16809],{"type":26,"value":15970},{"type":21,"tag":399,"props":16811,"children":16812},{"style":610},[16813],{"type":26,"value":16259},{"type":21,"tag":399,"props":16815,"children":16816},{"style":658},[16817],{"type":26,"value":14317},{"type":21,"tag":399,"props":16819,"children":16820},{"style":616},[16821],{"type":26,"value":619},{"type":21,"tag":399,"props":16823,"children":16824},{"style":630},[16825],{"type":26,"value":14326},{"type":21,"tag":399,"props":16827,"children":16828},{"style":610},[16829],{"type":26,"value":1652},{"type":21,"tag":399,"props":16831,"children":16833},{"class":605,"line":16832},61,[16834],{"type":21,"tag":399,"props":16835,"children":16836},{"emptyLinePlaceholder":2690},[16837],{"type":26,"value":2693},{"type":21,"tag":399,"props":16839,"children":16841},{"class":605,"line":16840},62,[16842,16847,16851],{"type":21,"tag":399,"props":16843,"children":16844},{"style":610},[16845],{"type":26,"value":16846},"    transaction ",{"type":21,"tag":399,"props":16848,"children":16849},{"style":616},[16850],{"type":26,"value":619},{"type":21,"tag":399,"props":16852,"children":16853},{"style":610},[16854],{"type":26,"value":16855}," payment_result.transaction\n",{"type":21,"tag":399,"props":16857,"children":16859},{"class":605,"line":16858},63,[16860,16864,16869,16874],{"type":21,"tag":399,"props":16861,"children":16862},{"style":616},[16863],{"type":26,"value":16619},{"type":21,"tag":399,"props":16865,"children":16866},{"style":610},[16867],{"type":26,"value":16868}," transaction.amount ",{"type":21,"tag":399,"props":16870,"children":16871},{"style":616},[16872],{"type":26,"value":16873},"\u003C",{"type":21,"tag":399,"props":16875,"children":16876},{"style":610},[16877],{"type":26,"value":16878}," item.amount:\n",{"type":21,"tag":399,"props":16880,"children":16882},{"class":605,"line":16881},64,[16883],{"type":21,"tag":399,"props":16884,"children":16885},{"style":2818},[16886],{"type":26,"value":16887},"        # Confirm you actually were paid as much as your item costs! If you don't submit for settlement,\n",{"type":21,"tag":399,"props":16889,"children":16891},{"class":605,"line":16890},65,[16892],{"type":21,"tag":399,"props":16893,"children":16894},{"style":2818},[16895],{"type":26,"value":16896},"        # or only submit for partial settlement, you should skip this check.\n",{"type":21,"tag":399,"props":16898,"children":16900},{"class":605,"line":16899},66,[16901,16905,16909,16913,16917,16921],{"type":21,"tag":399,"props":16902,"children":16903},{"style":616},[16904],{"type":26,"value":14297},{"type":21,"tag":399,"props":16906,"children":16907},{"style":610},[16908],{"type":26,"value":16259},{"type":21,"tag":399,"props":16910,"children":16911},{"style":658},[16912],{"type":26,"value":14317},{"type":21,"tag":399,"props":16914,"children":16915},{"style":616},[16916],{"type":26,"value":619},{"type":21,"tag":399,"props":16918,"children":16919},{"style":630},[16920],{"type":26,"value":14326},{"type":21,"tag":399,"props":16922,"children":16923},{"style":610},[16924],{"type":26,"value":1652},{"type":21,"tag":399,"props":16926,"children":16928},{"class":605,"line":16927},67,[16929],{"type":21,"tag":399,"props":16930,"children":16931},{"emptyLinePlaceholder":2690},[16932],{"type":26,"value":2693},{"type":21,"tag":399,"props":16934,"children":16936},{"class":605,"line":16935},68,[16937],{"type":21,"tag":399,"props":16938,"children":16939},{"style":2818},[16940],{"type":26,"value":16941},"    # Transaction successful - you can now proceed to create a new user, produce a download link for an item,\n",{"type":21,"tag":399,"props":16943,"children":16945},{"class":605,"line":16944},69,[16946],{"type":21,"tag":399,"props":16947,"children":16948},{"style":2818},[16949],{"type":26,"value":16950},"    # or whatever else it is you need payment processing for.\n",{"type":21,"tag":399,"props":16952,"children":16954},{"class":605,"line":16953},70,[16955],{"type":21,"tag":399,"props":16956,"children":16957},{"style":2818},[16958],{"type":26,"value":16959},"    # You can also now create a local reference for the transaction, in case you ever need to look it up for refund\n",{"type":21,"tag":399,"props":16961,"children":16963},{"class":605,"line":16962},71,[16964],{"type":21,"tag":399,"props":16965,"children":16966},{"style":2818},[16967],{"type":26,"value":16968},"    # purposes, for example.\n",{"type":21,"tag":399,"props":16970,"children":16972},{"class":605,"line":16971},72,[16973,16978,16983,16987,16992,16997,17001],{"type":21,"tag":399,"props":16974,"children":16975},{"style":610},[16976],{"type":26,"value":16977},"    TransactionRecord.objects.create(",{"type":21,"tag":399,"props":16979,"children":16980},{"style":658},[16981],{"type":26,"value":16982},"item",{"type":21,"tag":399,"props":16984,"children":16985},{"style":616},[16986],{"type":26,"value":619},{"type":21,"tag":399,"props":16988,"children":16989},{"style":610},[16990],{"type":26,"value":16991},"item, ",{"type":21,"tag":399,"props":16993,"children":16994},{"style":658},[16995],{"type":26,"value":16996},"amount",{"type":21,"tag":399,"props":16998,"children":16999},{"style":616},[17000],{"type":26,"value":619},{"type":21,"tag":399,"props":17002,"children":17003},{"style":610},[17004],{"type":26,"value":17005},"transaction.amount,\n",{"type":21,"tag":399,"props":17007,"children":17009},{"class":605,"line":17008},73,[17010,17015,17019,17024,17028,17032],{"type":21,"tag":399,"props":17011,"children":17012},{"style":658},[17013],{"type":26,"value":17014},"                                     transaction_id",{"type":21,"tag":399,"props":17016,"children":17017},{"style":616},[17018],{"type":26,"value":619},{"type":21,"tag":399,"props":17020,"children":17021},{"style":610},[17022],{"type":26,"value":17023},"transaction.id, ",{"type":21,"tag":399,"props":17025,"children":17026},{"style":658},[17027],{"type":26,"value":16386},{"type":21,"tag":399,"props":17029,"children":17030},{"style":616},[17031],{"type":26,"value":619},{"type":21,"tag":399,"props":17033,"children":17034},{"style":610},[17035],{"type":26,"value":17036},"transaction.payment_instrument_type)\n",{"type":21,"tag":399,"props":17038,"children":17040},{"class":605,"line":17039},74,[17041],{"type":21,"tag":399,"props":17042,"children":17043},{"emptyLinePlaceholder":2690},[17044],{"type":26,"value":2693},{"type":21,"tag":399,"props":17046,"children":17048},{"class":605,"line":17047},75,[17049,17053,17057,17061,17065,17069],{"type":21,"tag":399,"props":17050,"children":17051},{"style":616},[17052],{"type":26,"value":2829},{"type":21,"tag":399,"props":17054,"children":17055},{"style":610},[17056],{"type":26,"value":16259},{"type":21,"tag":399,"props":17058,"children":17059},{"style":658},[17060],{"type":26,"value":14317},{"type":21,"tag":399,"props":17062,"children":17063},{"style":616},[17064],{"type":26,"value":619},{"type":21,"tag":399,"props":17066,"children":17067},{"style":630},[17068],{"type":26,"value":1578},{"type":21,"tag":399,"props":17070,"children":17071},{"style":610},[17072],{"type":26,"value":1652},{"type":21,"tag":190,"props":17074,"children":17076},{"id":17075},"some-assembly-required",[17077],{"type":26,"value":17078},"Some Assembly Required",{"type":21,"tag":22,"props":17080,"children":17081},{},[17082],{"type":26,"value":17083},"And there, we're done!",{"type":21,"tag":22,"props":17085,"children":17086},{},[17087,17089,17094],{"type":26,"value":17088},"... for a given value of done. Although we now have something approaching the ",{"type":21,"tag":166,"props":17090,"children":17091},{},[17092],{"type":26,"value":17093},"bare minimum",{"type":26,"value":17095}," for processing a transaction, what we have doesn't account for recurring subscriptions, storing customer information in the braintree 'vault', sub-merchants, handling refunds, performing authorizations and settling at a future time...",{"type":21,"tag":22,"props":17097,"children":17098},{},[17099,17101,17106],{"type":26,"value":17100},"For more information, I direct you once again to braintree's well-written ",{"type":21,"tag":206,"props":17102,"children":17104},{"href":13821,"rel":17103},[210],[17105],{"type":26,"value":13825},{"type":26,"value":17107},", and I would suggest directing any questions you may have to StackOverflow, where I've noted braintree developers to be fairly quick to respond with assistance.",{"type":21,"tag":22,"props":17109,"children":17110},{},[17111],{"type":26,"value":17112},"At this point, however, you should have the beginnings of a working implementation. Soon, your users will be happily trading in their useless monetary units for the happiness and lasting contentment that only cat pictures can provide. Cheers!",{"type":21,"tag":22,"props":17114,"children":17115},{},[17116],{"type":21,"tag":166,"props":17117,"children":17118},{},[17119,17121],{"type":26,"value":17120},"Cat image: CC-BY licensed, via ",{"type":21,"tag":206,"props":17122,"children":17125},{"href":17123,"rel":17124},"https://www.flickr.com/photos/53911972@N03/",[210],[17126],{"type":26,"value":17123},{"type":21,"tag":2495,"props":17128,"children":17129},{},[17130],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":17132},[17133,17134,17135,17136,17137],{"id":13905,"depth":239,"text":13908},{"id":14604,"depth":239,"text":14607},{"id":15222,"depth":239,"text":15225},{"id":16010,"depth":239,"text":16013},{"id":17075,"depth":239,"text":17078},"content:ckeefer:2016-2:paymentprocessing.md","ckeefer/2016-2/paymentprocessing.md","ckeefer/2016-2/paymentprocessing",{"user":9465,"name":9466},{"_path":17143,"_dir":17144,"_draft":7,"_partial":7,"_locale":8,"title":17145,"description":17146,"publishDate":17147,"tags":17148,"excerpt":17146,"body":17149,"_type":240,"_id":20020,"_source":242,"_file":20021,"_stem":20022,"_extension":245,"author":20023},"/ckeefer/2015-3/emailvalidation","2015-3","Email Validation with Django and python-social-auth","When it comes to user accounts, the standard litmus test is email validation. Besides the immediate benefits - of offering us a straightforward unique identifier for users, and making it more difficult to automate creating a mass of accounts on our service - by requiring that each account have an email address and interact therewith to confirm the addresses validity, it also offers us the chance to associate a known-working email account with a user account. This is important for transactional emails such as password resets or for potential two-factor authentication use... and if you're a little less ethical, for sending marketing desirable and informative emails about interesting products and services.","2015-07-23",[16,2537],{"type":18,"children":17150,"toc":20011},[17151,17157,17161,17166,17180,17185,17207,17213,17223,17349,17354,17501,17538,17543,17907,17919,18032,18037,18375,18381,18387,18400,18441,18462,18467,18472,19004,19026,19031,19090,19096,19101,19110,19121,19126,19148,19153,19996,20002,20007],{"type":21,"tag":190,"props":17152,"children":17154},{"id":17153},"so-youre-asking-yourself-robot-or-real-person",[17155],{"type":26,"value":17156},"So you're asking yourself - robot or real person?",{"type":21,"tag":22,"props":17158,"children":17159},{},[17160],{"type":26,"value":17146},{"type":21,"tag":22,"props":17162,"children":17163},{},[17164],{"type":26,"value":17165},"\"But,\" you whine piteously, \"the whole reason I integrated python-social-auth into my project was to let the OAuth providers look after this sort of thing for me!\"",{"type":21,"tag":22,"props":17167,"children":17168},{},[17169,17171,17178],{"type":26,"value":17170},"Tough rocks. ",{"type":21,"tag":206,"props":17172,"children":17175},{"href":17173,"rel":17174},"https://twittercommunity.com/t/how-to-get-email-is-using-twitter-api-on-callback-page-using-php/404/33",[210],[17176],{"type":26,"value":17177},"Twitter ain't gonna give you their users' email addresses",{"type":26,"value":17179},". Just look at all those angry comments. If their whining didn't get through to Twitter, yours isn't going to do the trick either. Besides, eventually you'll probably want to allow the user to login the good old-fashioned way, with a username (which may or may not be their email address) and a password - in which case, you'll want to handle validating their email address yourself anyways.",{"type":21,"tag":22,"props":17181,"children":17182},{},[17183],{"type":26,"value":17184},"So, given that we've already integrated python-social-auth into our Django project (some of the same principles will apply to other frameworks, but many of the details presented herein are specified to Django) - let's get email validation working as part of our user creation/authentication pipeline.",{"type":21,"tag":22,"props":17186,"children":17187},{},[17188,17189,17196,17198,17205],{"type":26,"value":6098},{"type":21,"tag":206,"props":17190,"children":17193},{"href":17191,"rel":17192},"http://psa.matiasaguirre.net/docs/",[210],[17194],{"type":26,"value":17195},"python-social-auth docs",{"type":26,"value":17197}," have a rather threadbare ",{"type":21,"tag":206,"props":17199,"children":17202},{"href":17200,"rel":17201},"http://psa.matiasaguirre.net/docs/pipeline.html#email-validation",[210],[17203],{"type":26,"value":17204},"section on email validation",{"type":26,"value":17206},", so we won't be reproducing the details discussed there. Instead, let's take a look at some examples of what an implementation should look like, and some workarounds for issues encountered.",{"type":21,"tag":190,"props":17208,"children":17210},{"id":17209},"step-1-acquiring-the-email",[17211],{"type":26,"value":17212},"Step 1 - Acquiring the Email",{"type":21,"tag":22,"props":17214,"children":17215},{},[17216,17218,17222],{"type":26,"value":17217},"To start, we'll need to be able to send emails of our own, so drop the details of your email server/service into ",{"type":21,"tag":195,"props":17219,"children":17220},{},[17221],{"type":26,"value":10502},{"type":26,"value":434},{"type":21,"tag":595,"props":17224,"children":17226},{"className":597,"code":17225,"language":16,"meta":8,"style":8},"EMAIL_HOST = 'smtp.yourmailserver.com'\nEMAIL_HOST_USER = 'smtp-username'\nEMAIL_HOST_PASSWORD = 'smtp-password'\n# Port 587 is the default port for smtp submission, but some will use 25, 465, or an arbitrary port #\nEMAIL_PORT = 587  \nEMAIL_USE_TLS = True\nEMAIL_NOREPLY = \"noreply@yourserver.com\"  # Not required, but potentially useful to have defined\n",[17227],{"type":21,"tag":43,"props":17228,"children":17229},{"__ignoreMap":8},[17230,17247,17264,17281,17289,17310,17327],{"type":21,"tag":399,"props":17231,"children":17232},{"class":605,"line":606},[17233,17238,17242],{"type":21,"tag":399,"props":17234,"children":17235},{"style":630},[17236],{"type":26,"value":17237},"EMAIL_HOST",{"type":21,"tag":399,"props":17239,"children":17240},{"style":616},[17241],{"type":26,"value":4224},{"type":21,"tag":399,"props":17243,"children":17244},{"style":644},[17245],{"type":26,"value":17246}," 'smtp.yourmailserver.com'\n",{"type":21,"tag":399,"props":17248,"children":17249},{"class":605,"line":239},[17250,17255,17259],{"type":21,"tag":399,"props":17251,"children":17252},{"style":630},[17253],{"type":26,"value":17254},"EMAIL_HOST_USER",{"type":21,"tag":399,"props":17256,"children":17257},{"style":616},[17258],{"type":26,"value":4224},{"type":21,"tag":399,"props":17260,"children":17261},{"style":644},[17262],{"type":26,"value":17263}," 'smtp-username'\n",{"type":21,"tag":399,"props":17265,"children":17266},{"class":605,"line":232},[17267,17272,17276],{"type":21,"tag":399,"props":17268,"children":17269},{"style":630},[17270],{"type":26,"value":17271},"EMAIL_HOST_PASSWORD",{"type":21,"tag":399,"props":17273,"children":17274},{"style":616},[17275],{"type":26,"value":4224},{"type":21,"tag":399,"props":17277,"children":17278},{"style":644},[17279],{"type":26,"value":17280}," 'smtp-password'\n",{"type":21,"tag":399,"props":17282,"children":17283},{"class":605,"line":654},[17284],{"type":21,"tag":399,"props":17285,"children":17286},{"style":2818},[17287],{"type":26,"value":17288},"# Port 587 is the default port for smtp submission, but some will use 25, 465, or an arbitrary port #\n",{"type":21,"tag":399,"props":17290,"children":17291},{"class":605,"line":698},[17292,17297,17301,17306],{"type":21,"tag":399,"props":17293,"children":17294},{"style":630},[17295],{"type":26,"value":17296},"EMAIL_PORT",{"type":21,"tag":399,"props":17298,"children":17299},{"style":616},[17300],{"type":26,"value":4224},{"type":21,"tag":399,"props":17302,"children":17303},{"style":630},[17304],{"type":26,"value":17305}," 587",{"type":21,"tag":399,"props":17307,"children":17308},{"style":610},[17309],{"type":26,"value":3624},{"type":21,"tag":399,"props":17311,"children":17312},{"class":605,"line":737},[17313,17318,17322],{"type":21,"tag":399,"props":17314,"children":17315},{"style":630},[17316],{"type":26,"value":17317},"EMAIL_USE_TLS",{"type":21,"tag":399,"props":17319,"children":17320},{"style":616},[17321],{"type":26,"value":4224},{"type":21,"tag":399,"props":17323,"children":17324},{"style":630},[17325],{"type":26,"value":17326}," True\n",{"type":21,"tag":399,"props":17328,"children":17329},{"class":605,"line":755},[17330,17335,17339,17344],{"type":21,"tag":399,"props":17331,"children":17332},{"style":630},[17333],{"type":26,"value":17334},"EMAIL_NOREPLY",{"type":21,"tag":399,"props":17336,"children":17337},{"style":616},[17338],{"type":26,"value":4224},{"type":21,"tag":399,"props":17340,"children":17341},{"style":644},[17342],{"type":26,"value":17343}," \"noreply@yourserver.com\"",{"type":21,"tag":399,"props":17345,"children":17346},{"style":2818},[17347],{"type":26,"value":17348},"  # Not required, but potentially useful to have defined\n",{"type":21,"tag":22,"props":17350,"children":17351},{},[17352],{"type":26,"value":17353},"We'll also need to have setup our social auth pipeline. An example:",{"type":21,"tag":595,"props":17355,"children":17357},{"className":597,"code":17356,"language":16,"meta":8,"style":8},"SOCIAL_AUTH_PIPELINE = (\n    'social.pipeline.social_auth.social_details',\n    'social.pipeline.social_auth.social_uid',\n    'social.pipeline.social_auth.auth_allowed',\n    'social.pipeline.social_auth.social_user',\n    'social.pipeline.user.get_username',\n    'app.auth.user_details.require_email',\n    'social.pipeline.mail.mail_validation',\n    'social.pipeline.social_auth.associate_by_email',\n    'social.pipeline.social_auth.associate_user',\n    'social.pipeline.social_auth.load_extra_data'\n)\n",[17358],{"type":21,"tag":43,"props":17359,"children":17360},{"__ignoreMap":8},[17361,17378,17390,17402,17414,17426,17438,17450,17462,17474,17486,17494],{"type":21,"tag":399,"props":17362,"children":17363},{"class":605,"line":606},[17364,17369,17373],{"type":21,"tag":399,"props":17365,"children":17366},{"style":630},[17367],{"type":26,"value":17368},"SOCIAL_AUTH_PIPELINE",{"type":21,"tag":399,"props":17370,"children":17371},{"style":616},[17372],{"type":26,"value":4224},{"type":21,"tag":399,"props":17374,"children":17375},{"style":610},[17376],{"type":26,"value":17377}," (\n",{"type":21,"tag":399,"props":17379,"children":17380},{"class":605,"line":239},[17381,17386],{"type":21,"tag":399,"props":17382,"children":17383},{"style":644},[17384],{"type":26,"value":17385},"    'social.pipeline.social_auth.social_details'",{"type":21,"tag":399,"props":17387,"children":17388},{"style":610},[17389],{"type":26,"value":638},{"type":21,"tag":399,"props":17391,"children":17392},{"class":605,"line":232},[17393,17398],{"type":21,"tag":399,"props":17394,"children":17395},{"style":644},[17396],{"type":26,"value":17397},"    'social.pipeline.social_auth.social_uid'",{"type":21,"tag":399,"props":17399,"children":17400},{"style":610},[17401],{"type":26,"value":638},{"type":21,"tag":399,"props":17403,"children":17404},{"class":605,"line":654},[17405,17410],{"type":21,"tag":399,"props":17406,"children":17407},{"style":644},[17408],{"type":26,"value":17409},"    'social.pipeline.social_auth.auth_allowed'",{"type":21,"tag":399,"props":17411,"children":17412},{"style":610},[17413],{"type":26,"value":638},{"type":21,"tag":399,"props":17415,"children":17416},{"class":605,"line":698},[17417,17422],{"type":21,"tag":399,"props":17418,"children":17419},{"style":644},[17420],{"type":26,"value":17421},"    'social.pipeline.social_auth.social_user'",{"type":21,"tag":399,"props":17423,"children":17424},{"style":610},[17425],{"type":26,"value":638},{"type":21,"tag":399,"props":17427,"children":17428},{"class":605,"line":737},[17429,17434],{"type":21,"tag":399,"props":17430,"children":17431},{"style":644},[17432],{"type":26,"value":17433},"    'social.pipeline.user.get_username'",{"type":21,"tag":399,"props":17435,"children":17436},{"style":610},[17437],{"type":26,"value":638},{"type":21,"tag":399,"props":17439,"children":17440},{"class":605,"line":755},[17441,17446],{"type":21,"tag":399,"props":17442,"children":17443},{"style":644},[17444],{"type":26,"value":17445},"    'app.auth.user_details.require_email'",{"type":21,"tag":399,"props":17447,"children":17448},{"style":610},[17449],{"type":26,"value":638},{"type":21,"tag":399,"props":17451,"children":17452},{"class":605,"line":773},[17453,17458],{"type":21,"tag":399,"props":17454,"children":17455},{"style":644},[17456],{"type":26,"value":17457},"    'social.pipeline.mail.mail_validation'",{"type":21,"tag":399,"props":17459,"children":17460},{"style":610},[17461],{"type":26,"value":638},{"type":21,"tag":399,"props":17463,"children":17464},{"class":605,"line":795},[17465,17470],{"type":21,"tag":399,"props":17466,"children":17467},{"style":644},[17468],{"type":26,"value":17469},"    'social.pipeline.social_auth.associate_by_email'",{"type":21,"tag":399,"props":17471,"children":17472},{"style":610},[17473],{"type":26,"value":638},{"type":21,"tag":399,"props":17475,"children":17476},{"class":605,"line":804},[17477,17482],{"type":21,"tag":399,"props":17478,"children":17479},{"style":644},[17480],{"type":26,"value":17481},"    'social.pipeline.social_auth.associate_user'",{"type":21,"tag":399,"props":17483,"children":17484},{"style":610},[17485],{"type":26,"value":638},{"type":21,"tag":399,"props":17487,"children":17488},{"class":605,"line":843},[17489],{"type":21,"tag":399,"props":17490,"children":17491},{"style":644},[17492],{"type":26,"value":17493},"    'social.pipeline.social_auth.load_extra_data'\n",{"type":21,"tag":399,"props":17495,"children":17496},{"class":605,"line":882},[17497],{"type":21,"tag":399,"props":17498,"children":17499},{"style":610},[17500],{"type":26,"value":1652},{"type":21,"tag":22,"props":17502,"children":17503},{},[17504,17506,17512,17513,17519,17521,17527,17529,17536],{"type":26,"value":17505},"The exact makeup and order of this pipeline will depend on the needs of your project. The elements of interest to us for the sake of setting up email validation are the lines ",{"type":21,"tag":43,"props":17507,"children":17509},{"className":17508},[],[17510],{"type":26,"value":17511},"app.auth.user_details.require_email",{"type":26,"value":2584},{"type":21,"tag":43,"props":17514,"children":17516},{"className":17515},[],[17517],{"type":26,"value":17518},"social.pipeline.mail.mail_validation",{"type":26,"value":17520},". Note also that we include these lines before ",{"type":21,"tag":43,"props":17522,"children":17524},{"className":17523},[],[17525],{"type":26,"value":17526},"associate_by_email",{"type":26,"value":17528}," - we don't want to associate users by email until we've confirmed that the user actually owns the email account they've entered, or they could gain control over someone's else's account (see the ",{"type":21,"tag":206,"props":17530,"children":17533},{"href":17531,"rel":17532},"http://psa.matiasaguirre.net/docs/use_cases.html#associate-users-by-email",[210],[17534],{"type":26,"value":17535},"warnings regarding associate_by_email in the python-social-auth docs",{"type":26,"value":17537}," for more details).",{"type":21,"tag":22,"props":17539,"children":17540},{},[17541],{"type":26,"value":17542},"Returning to the two entries in the pipeline I've called out - the former is a partial pipeline function that we create ourselves, to decide whether we need to ask the user for their email at this point in the process. This allows us to proceed seamlessly when we're logging the user in with a service that provides the email (like Google+ or Facebook), while allowing us to present the user with a form to fill in the necessary details otherwise. This fucntion could look something like:",{"type":21,"tag":595,"props":17544,"children":17546},{"className":597,"code":17545,"language":16,"meta":8,"style":8},"from django.shortcuts import redirect\nfrom social.pipeline.partial import partial\nfrom social.pipeline.user import USER_FIELDS\n\ndef require_email(strategy, details, user=None, is_new=False, *args, **kwargs):\n    backend = kwargs.get('backend')\n    if user and user.email:\n        return # The user we're logging in already has their email attribute set\n    elif is_new and and not details.get('email'):\n        # If we're creating a new user, and we can't find the email in the details\n        # we'll attempt to request it from the data returned from our backend strategy\n        userEmail = strategy.request_data().get('email')\n        if userEmail:\n            details['email'] = userEmail\n        else:\n            # If there's no email information to be had, we need to ask the user to fill it in\n            # This should redirect us to a view\n            return redirect('acquire_email')\n",[17547],{"type":21,"tag":43,"props":17548,"children":17549},{"__ignoreMap":8},[17550,17570,17591,17612,17619,17681,17707,17729,17741,17781,17789,17797,17822,17834,17859,17870,17878,17886],{"type":21,"tag":399,"props":17551,"children":17552},{"class":605,"line":606},[17553,17557,17561,17565],{"type":21,"tag":399,"props":17554,"children":17555},{"style":616},[17556],{"type":26,"value":2669},{"type":21,"tag":399,"props":17558,"children":17559},{"style":610},[17560],{"type":26,"value":14405},{"type":21,"tag":399,"props":17562,"children":17563},{"style":616},[17564],{"type":26,"value":2679},{"type":21,"tag":399,"props":17566,"children":17567},{"style":610},[17568],{"type":26,"value":17569}," redirect\n",{"type":21,"tag":399,"props":17571,"children":17572},{"class":605,"line":239},[17573,17577,17582,17586],{"type":21,"tag":399,"props":17574,"children":17575},{"style":616},[17576],{"type":26,"value":2669},{"type":21,"tag":399,"props":17578,"children":17579},{"style":610},[17580],{"type":26,"value":17581}," social.pipeline.partial ",{"type":21,"tag":399,"props":17583,"children":17584},{"style":616},[17585],{"type":26,"value":2679},{"type":21,"tag":399,"props":17587,"children":17588},{"style":610},[17589],{"type":26,"value":17590}," partial\n",{"type":21,"tag":399,"props":17592,"children":17593},{"class":605,"line":232},[17594,17598,17603,17607],{"type":21,"tag":399,"props":17595,"children":17596},{"style":616},[17597],{"type":26,"value":2669},{"type":21,"tag":399,"props":17599,"children":17600},{"style":610},[17601],{"type":26,"value":17602}," social.pipeline.user ",{"type":21,"tag":399,"props":17604,"children":17605},{"style":616},[17606],{"type":26,"value":2679},{"type":21,"tag":399,"props":17608,"children":17609},{"style":630},[17610],{"type":26,"value":17611}," USER_FIELDS\n",{"type":21,"tag":399,"props":17613,"children":17614},{"class":605,"line":654},[17615],{"type":21,"tag":399,"props":17616,"children":17617},{"emptyLinePlaceholder":2690},[17618],{"type":26,"value":2693},{"type":21,"tag":399,"props":17620,"children":17621},{"class":605,"line":698},[17622,17626,17631,17636,17640,17644,17649,17653,17657,17661,17666,17671,17676],{"type":21,"tag":399,"props":17623,"children":17624},{"style":616},[17625],{"type":26,"value":3281},{"type":21,"tag":399,"props":17627,"children":17628},{"style":2704},[17629],{"type":26,"value":17630}," require_email",{"type":21,"tag":399,"props":17632,"children":17633},{"style":610},[17634],{"type":26,"value":17635},"(strategy, details, user",{"type":21,"tag":399,"props":17637,"children":17638},{"style":616},[17639],{"type":26,"value":619},{"type":21,"tag":399,"props":17641,"children":17642},{"style":630},[17643],{"type":26,"value":9081},{"type":21,"tag":399,"props":17645,"children":17646},{"style":610},[17647],{"type":26,"value":17648},", is_new",{"type":21,"tag":399,"props":17650,"children":17651},{"style":616},[17652],{"type":26,"value":619},{"type":21,"tag":399,"props":17654,"children":17655},{"style":630},[17656],{"type":26,"value":1435},{"type":21,"tag":399,"props":17658,"children":17659},{"style":610},[17660],{"type":26,"value":685},{"type":21,"tag":399,"props":17662,"children":17663},{"style":616},[17664],{"type":26,"value":17665},"*",{"type":21,"tag":399,"props":17667,"children":17668},{"style":610},[17669],{"type":26,"value":17670},"args, ",{"type":21,"tag":399,"props":17672,"children":17673},{"style":616},[17674],{"type":26,"value":17675},"**",{"type":21,"tag":399,"props":17677,"children":17678},{"style":610},[17679],{"type":26,"value":17680},"kwargs):\n",{"type":21,"tag":399,"props":17682,"children":17683},{"class":605,"line":737},[17684,17689,17693,17698,17703],{"type":21,"tag":399,"props":17685,"children":17686},{"style":610},[17687],{"type":26,"value":17688},"    backend ",{"type":21,"tag":399,"props":17690,"children":17691},{"style":616},[17692],{"type":26,"value":619},{"type":21,"tag":399,"props":17694,"children":17695},{"style":610},[17696],{"type":26,"value":17697}," kwargs.get(",{"type":21,"tag":399,"props":17699,"children":17700},{"style":644},[17701],{"type":26,"value":17702},"'backend'",{"type":21,"tag":399,"props":17704,"children":17705},{"style":610},[17706],{"type":26,"value":1652},{"type":21,"tag":399,"props":17708,"children":17709},{"class":605,"line":755},[17710,17714,17719,17724],{"type":21,"tag":399,"props":17711,"children":17712},{"style":616},[17713],{"type":26,"value":16619},{"type":21,"tag":399,"props":17715,"children":17716},{"style":610},[17717],{"type":26,"value":17718}," user ",{"type":21,"tag":399,"props":17720,"children":17721},{"style":616},[17722],{"type":26,"value":17723},"and",{"type":21,"tag":399,"props":17725,"children":17726},{"style":610},[17727],{"type":26,"value":17728}," user.email:\n",{"type":21,"tag":399,"props":17730,"children":17731},{"class":605,"line":773},[17732,17736],{"type":21,"tag":399,"props":17733,"children":17734},{"style":616},[17735],{"type":26,"value":14297},{"type":21,"tag":399,"props":17737,"children":17738},{"style":2818},[17739],{"type":26,"value":17740}," # The user we're logging in already has their email attribute set\n",{"type":21,"tag":399,"props":17742,"children":17743},{"class":605,"line":795},[17744,17749,17754,17758,17763,17767,17772,17777],{"type":21,"tag":399,"props":17745,"children":17746},{"style":616},[17747],{"type":26,"value":17748},"    elif",{"type":21,"tag":399,"props":17750,"children":17751},{"style":610},[17752],{"type":26,"value":17753}," is_new ",{"type":21,"tag":399,"props":17755,"children":17756},{"style":616},[17757],{"type":26,"value":17723},{"type":21,"tag":399,"props":17759,"children":17760},{"style":616},[17761],{"type":26,"value":17762}," and",{"type":21,"tag":399,"props":17764,"children":17765},{"style":616},[17766],{"type":26,"value":3327},{"type":21,"tag":399,"props":17768,"children":17769},{"style":610},[17770],{"type":26,"value":17771}," details.get(",{"type":21,"tag":399,"props":17773,"children":17774},{"style":644},[17775],{"type":26,"value":17776},"'email'",{"type":21,"tag":399,"props":17778,"children":17779},{"style":610},[17780],{"type":26,"value":2722},{"type":21,"tag":399,"props":17782,"children":17783},{"class":605,"line":804},[17784],{"type":21,"tag":399,"props":17785,"children":17786},{"style":2818},[17787],{"type":26,"value":17788},"        # If we're creating a new user, and we can't find the email in the details\n",{"type":21,"tag":399,"props":17790,"children":17791},{"class":605,"line":843},[17792],{"type":21,"tag":399,"props":17793,"children":17794},{"style":2818},[17795],{"type":26,"value":17796},"        # we'll attempt to request it from the data returned from our backend strategy\n",{"type":21,"tag":399,"props":17798,"children":17799},{"class":605,"line":882},[17800,17805,17809,17814,17818],{"type":21,"tag":399,"props":17801,"children":17802},{"style":610},[17803],{"type":26,"value":17804},"        userEmail ",{"type":21,"tag":399,"props":17806,"children":17807},{"style":616},[17808],{"type":26,"value":619},{"type":21,"tag":399,"props":17810,"children":17811},{"style":610},[17812],{"type":26,"value":17813}," strategy.request_data().get(",{"type":21,"tag":399,"props":17815,"children":17816},{"style":644},[17817],{"type":26,"value":17776},{"type":21,"tag":399,"props":17819,"children":17820},{"style":610},[17821],{"type":26,"value":1652},{"type":21,"tag":399,"props":17823,"children":17824},{"class":605,"line":900},[17825,17829],{"type":21,"tag":399,"props":17826,"children":17827},{"style":616},[17828],{"type":26,"value":15935},{"type":21,"tag":399,"props":17830,"children":17831},{"style":610},[17832],{"type":26,"value":17833}," userEmail:\n",{"type":21,"tag":399,"props":17835,"children":17836},{"class":605,"line":923},[17837,17842,17846,17850,17854],{"type":21,"tag":399,"props":17838,"children":17839},{"style":610},[17840],{"type":26,"value":17841},"            details[",{"type":21,"tag":399,"props":17843,"children":17844},{"style":644},[17845],{"type":26,"value":17776},{"type":21,"tag":399,"props":17847,"children":17848},{"style":610},[17849],{"type":26,"value":8792},{"type":21,"tag":399,"props":17851,"children":17852},{"style":616},[17853],{"type":26,"value":619},{"type":21,"tag":399,"props":17855,"children":17856},{"style":610},[17857],{"type":26,"value":17858}," userEmail\n",{"type":21,"tag":399,"props":17860,"children":17861},{"class":605,"line":969},[17862,17866],{"type":21,"tag":399,"props":17863,"children":17864},{"style":616},[17865],{"type":26,"value":16754},{"type":21,"tag":399,"props":17867,"children":17868},{"style":610},[17869],{"type":26,"value":4683},{"type":21,"tag":399,"props":17871,"children":17872},{"class":605,"line":991},[17873],{"type":21,"tag":399,"props":17874,"children":17875},{"style":2818},[17876],{"type":26,"value":17877},"            # If there's no email information to be had, we need to ask the user to fill it in\n",{"type":21,"tag":399,"props":17879,"children":17880},{"class":605,"line":1013},[17881],{"type":21,"tag":399,"props":17882,"children":17883},{"style":2818},[17884],{"type":26,"value":17885},"            # This should redirect us to a view\n",{"type":21,"tag":399,"props":17887,"children":17888},{"class":605,"line":1035},[17889,17893,17898,17903],{"type":21,"tag":399,"props":17890,"children":17891},{"style":616},[17892],{"type":26,"value":15970},{"type":21,"tag":399,"props":17894,"children":17895},{"style":610},[17896],{"type":26,"value":17897}," redirect(",{"type":21,"tag":399,"props":17899,"children":17900},{"style":644},[17901],{"type":26,"value":17902},"'acquire_email'",{"type":21,"tag":399,"props":17904,"children":17905},{"style":610},[17906],{"type":26,"value":1652},{"type":21,"tag":22,"props":17908,"children":17909},{},[17910,17911,17917],{"type":26,"value":6098},{"type":21,"tag":43,"props":17912,"children":17914},{"className":17913},[],[17915],{"type":26,"value":17916},"acquire_email",{"type":26,"value":17918}," view we're redirecting to can look something like the following:",{"type":21,"tag":595,"props":17920,"children":17922},{"className":597,"code":17921,"language":16,"meta":8,"style":8},"def acquire_email(request, template_name=\"email/acquire.html\"):\n    \"\"\"\n    Request email for the create user flow for logins that don't specify their email address.\n    \"\"\"\n    backend = request.session['partial_pipeline']['backend']\n    return render(request, template_name, {\"backend\": backend})\n",[17923],{"type":21,"tag":43,"props":17924,"children":17925},{"__ignoreMap":8},[17926,17955,17962,17970,17977,18011],{"type":21,"tag":399,"props":17927,"children":17928},{"class":605,"line":606},[17929,17933,17938,17942,17946,17951],{"type":21,"tag":399,"props":17930,"children":17931},{"style":616},[17932],{"type":26,"value":3281},{"type":21,"tag":399,"props":17934,"children":17935},{"style":2704},[17936],{"type":26,"value":17937}," acquire_email",{"type":21,"tag":399,"props":17939,"children":17940},{"style":610},[17941],{"type":26,"value":14457},{"type":21,"tag":399,"props":17943,"children":17944},{"style":616},[17945],{"type":26,"value":619},{"type":21,"tag":399,"props":17947,"children":17948},{"style":644},[17949],{"type":26,"value":17950},"\"email/acquire.html\"",{"type":21,"tag":399,"props":17952,"children":17953},{"style":610},[17954],{"type":26,"value":2722},{"type":21,"tag":399,"props":17956,"children":17957},{"class":605,"line":239},[17958],{"type":21,"tag":399,"props":17959,"children":17960},{"style":644},[17961],{"type":26,"value":14223},{"type":21,"tag":399,"props":17963,"children":17964},{"class":605,"line":232},[17965],{"type":21,"tag":399,"props":17966,"children":17967},{"style":644},[17968],{"type":26,"value":17969},"    Request email for the create user flow for logins that don't specify their email address.\n",{"type":21,"tag":399,"props":17971,"children":17972},{"class":605,"line":654},[17973],{"type":21,"tag":399,"props":17974,"children":17975},{"style":644},[17976],{"type":26,"value":14223},{"type":21,"tag":399,"props":17978,"children":17979},{"class":605,"line":698},[17980,17984,17988,17993,17998,18003,18007],{"type":21,"tag":399,"props":17981,"children":17982},{"style":610},[17983],{"type":26,"value":17688},{"type":21,"tag":399,"props":17985,"children":17986},{"style":616},[17987],{"type":26,"value":619},{"type":21,"tag":399,"props":17989,"children":17990},{"style":610},[17991],{"type":26,"value":17992}," request.session[",{"type":21,"tag":399,"props":17994,"children":17995},{"style":644},[17996],{"type":26,"value":17997},"'partial_pipeline'",{"type":21,"tag":399,"props":17999,"children":18000},{"style":610},[18001],{"type":26,"value":18002},"][",{"type":21,"tag":399,"props":18004,"children":18005},{"style":644},[18006],{"type":26,"value":17702},{"type":21,"tag":399,"props":18008,"children":18009},{"style":610},[18010],{"type":26,"value":10997},{"type":21,"tag":399,"props":18012,"children":18013},{"class":605,"line":737},[18014,18018,18022,18027],{"type":21,"tag":399,"props":18015,"children":18016},{"style":616},[18017],{"type":26,"value":2829},{"type":21,"tag":399,"props":18019,"children":18020},{"style":610},[18021],{"type":26,"value":14592},{"type":21,"tag":399,"props":18023,"children":18024},{"style":644},[18025],{"type":26,"value":18026},"\"backend\"",{"type":21,"tag":399,"props":18028,"children":18029},{"style":610},[18030],{"type":26,"value":18031},": backend})\n",{"type":21,"tag":22,"props":18033,"children":18034},{},[18035],{"type":26,"value":18036},"And somewhere within the template, add a form that will submit the required data into the pipeline:",{"type":21,"tag":595,"props":18038,"children":18040},{"className":15027,"code":18039,"language":15029,"meta":8,"style":8},"\u003Cform action=\"{% url \"social:complete\" backend=backend %}\" method=\"post\" role=\"form\">\n    {% csrf_token %}\n    \u003Cdiv class=\"form-group\">\n        \u003Clabel class=\"control-label\" for=\"email\">Email address:\u003C/label>\n        \u003Cinput class=\"form-control\" id=\"email\" type=\"email\" name=\"email\" value=\"\" required />\n    \u003C/div>\n    \u003Cbutton type=\"submit\">Submit\u003C/button>\n\u003C/form>\n",[18041],{"type":21,"tag":43,"props":18042,"children":18043},{"__ignoreMap":8},[18044,18133,18141,18170,18222,18308,18323,18359],{"type":21,"tag":399,"props":18045,"children":18046},{"class":605,"line":606},[18047,18051,18055,18060,18064,18069,18074,18078,18083,18087,18092,18097,18101,18106,18110,18115,18120,18124,18129],{"type":21,"tag":399,"props":18048,"children":18049},{"style":610},[18050],{"type":26,"value":16873},{"type":21,"tag":399,"props":18052,"children":18053},{"style":15044},[18054],{"type":26,"value":15072},{"type":21,"tag":399,"props":18056,"children":18057},{"style":2704},[18058],{"type":26,"value":18059}," action",{"type":21,"tag":399,"props":18061,"children":18062},{"style":610},[18063],{"type":26,"value":619},{"type":21,"tag":399,"props":18065,"children":18066},{"style":644},[18067],{"type":26,"value":18068},"\"{% url \"",{"type":21,"tag":399,"props":18070,"children":18071},{"style":2704},[18072],{"type":26,"value":18073},"social:complete",{"type":21,"tag":399,"props":18075,"children":18076},{"style":11538},[18077],{"type":26,"value":2986},{"type":21,"tag":399,"props":18079,"children":18080},{"style":2704},[18081],{"type":26,"value":18082}," backend",{"type":21,"tag":399,"props":18084,"children":18085},{"style":610},[18086],{"type":26,"value":619},{"type":21,"tag":399,"props":18088,"children":18089},{"style":644},[18090],{"type":26,"value":18091},"backend",{"type":21,"tag":399,"props":18093,"children":18094},{"style":2704},[18095],{"type":26,"value":18096}," %}",{"type":21,"tag":399,"props":18098,"children":18099},{"style":11538},[18100],{"type":26,"value":2986},{"type":21,"tag":399,"props":18102,"children":18103},{"style":2704},[18104],{"type":26,"value":18105}," method",{"type":21,"tag":399,"props":18107,"children":18108},{"style":610},[18109],{"type":26,"value":619},{"type":21,"tag":399,"props":18111,"children":18112},{"style":644},[18113],{"type":26,"value":18114},"\"post\"",{"type":21,"tag":399,"props":18116,"children":18117},{"style":2704},[18118],{"type":26,"value":18119}," role",{"type":21,"tag":399,"props":18121,"children":18122},{"style":610},[18123],{"type":26,"value":619},{"type":21,"tag":399,"props":18125,"children":18126},{"style":644},[18127],{"type":26,"value":18128},"\"form\"",{"type":21,"tag":399,"props":18130,"children":18131},{"style":610},[18132],{"type":26,"value":15052},{"type":21,"tag":399,"props":18134,"children":18135},{"class":605,"line":239},[18136],{"type":21,"tag":399,"props":18137,"children":18138},{"style":610},[18139],{"type":26,"value":18140},"    {% csrf_token %}\n",{"type":21,"tag":399,"props":18142,"children":18143},{"class":605,"line":232},[18144,18148,18152,18157,18161,18166],{"type":21,"tag":399,"props":18145,"children":18146},{"style":610},[18147],{"type":26,"value":15041},{"type":21,"tag":399,"props":18149,"children":18150},{"style":15044},[18151],{"type":26,"value":15089},{"type":21,"tag":399,"props":18153,"children":18154},{"style":2704},[18155],{"type":26,"value":18156}," class",{"type":21,"tag":399,"props":18158,"children":18159},{"style":610},[18160],{"type":26,"value":619},{"type":21,"tag":399,"props":18162,"children":18163},{"style":644},[18164],{"type":26,"value":18165},"\"form-group\"",{"type":21,"tag":399,"props":18167,"children":18168},{"style":610},[18169],{"type":26,"value":15052},{"type":21,"tag":399,"props":18171,"children":18172},{"class":605,"line":654},[18173,18177,18182,18186,18190,18195,18200,18204,18209,18214,18218],{"type":21,"tag":399,"props":18174,"children":18175},{"style":610},[18176],{"type":26,"value":15067},{"type":21,"tag":399,"props":18178,"children":18179},{"style":15044},[18180],{"type":26,"value":18181},"label",{"type":21,"tag":399,"props":18183,"children":18184},{"style":2704},[18185],{"type":26,"value":18156},{"type":21,"tag":399,"props":18187,"children":18188},{"style":610},[18189],{"type":26,"value":619},{"type":21,"tag":399,"props":18191,"children":18192},{"style":644},[18193],{"type":26,"value":18194},"\"control-label\"",{"type":21,"tag":399,"props":18196,"children":18197},{"style":2704},[18198],{"type":26,"value":18199}," for",{"type":21,"tag":399,"props":18201,"children":18202},{"style":610},[18203],{"type":26,"value":619},{"type":21,"tag":399,"props":18205,"children":18206},{"style":644},[18207],{"type":26,"value":18208},"\"email\"",{"type":21,"tag":399,"props":18210,"children":18211},{"style":610},[18212],{"type":26,"value":18213},">Email address:\u003C/",{"type":21,"tag":399,"props":18215,"children":18216},{"style":15044},[18217],{"type":26,"value":18181},{"type":21,"tag":399,"props":18219,"children":18220},{"style":610},[18221],{"type":26,"value":15052},{"type":21,"tag":399,"props":18223,"children":18224},{"class":605,"line":698},[18225,18229,18234,18238,18242,18247,18251,18255,18259,18263,18267,18271,18276,18280,18284,18289,18293,18298,18303],{"type":21,"tag":399,"props":18226,"children":18227},{"style":610},[18228],{"type":26,"value":15067},{"type":21,"tag":399,"props":18230,"children":18231},{"style":15044},[18232],{"type":26,"value":18233},"input",{"type":21,"tag":399,"props":18235,"children":18236},{"style":2704},[18237],{"type":26,"value":18156},{"type":21,"tag":399,"props":18239,"children":18240},{"style":610},[18241],{"type":26,"value":619},{"type":21,"tag":399,"props":18243,"children":18244},{"style":644},[18245],{"type":26,"value":18246},"\"form-control\"",{"type":21,"tag":399,"props":18248,"children":18249},{"style":2704},[18250],{"type":26,"value":15094},{"type":21,"tag":399,"props":18252,"children":18253},{"style":610},[18254],{"type":26,"value":619},{"type":21,"tag":399,"props":18256,"children":18257},{"style":644},[18258],{"type":26,"value":18208},{"type":21,"tag":399,"props":18260,"children":18261},{"style":2704},[18262],{"type":26,"value":15140},{"type":21,"tag":399,"props":18264,"children":18265},{"style":610},[18266],{"type":26,"value":619},{"type":21,"tag":399,"props":18268,"children":18269},{"style":644},[18270],{"type":26,"value":18208},{"type":21,"tag":399,"props":18272,"children":18273},{"style":2704},[18274],{"type":26,"value":18275}," name",{"type":21,"tag":399,"props":18277,"children":18278},{"style":610},[18279],{"type":26,"value":619},{"type":21,"tag":399,"props":18281,"children":18282},{"style":644},[18283],{"type":26,"value":18208},{"type":21,"tag":399,"props":18285,"children":18286},{"style":2704},[18287],{"type":26,"value":18288}," value",{"type":21,"tag":399,"props":18290,"children":18291},{"style":610},[18292],{"type":26,"value":619},{"type":21,"tag":399,"props":18294,"children":18295},{"style":644},[18296],{"type":26,"value":18297},"\"\"",{"type":21,"tag":399,"props":18299,"children":18300},{"style":2704},[18301],{"type":26,"value":18302}," required",{"type":21,"tag":399,"props":18304,"children":18305},{"style":610},[18306],{"type":26,"value":18307}," />\n",{"type":21,"tag":399,"props":18309,"children":18310},{"class":605,"line":737},[18311,18315,18319],{"type":21,"tag":399,"props":18312,"children":18313},{"style":610},[18314],{"type":26,"value":15193},{"type":21,"tag":399,"props":18316,"children":18317},{"style":15044},[18318],{"type":26,"value":15089},{"type":21,"tag":399,"props":18320,"children":18321},{"style":610},[18322],{"type":26,"value":15052},{"type":21,"tag":399,"props":18324,"children":18325},{"class":605,"line":755},[18326,18330,18334,18338,18342,18346,18351,18355],{"type":21,"tag":399,"props":18327,"children":18328},{"style":610},[18329],{"type":26,"value":15041},{"type":21,"tag":399,"props":18331,"children":18332},{"style":15044},[18333],{"type":26,"value":15135},{"type":21,"tag":399,"props":18335,"children":18336},{"style":2704},[18337],{"type":26,"value":15140},{"type":21,"tag":399,"props":18339,"children":18340},{"style":610},[18341],{"type":26,"value":619},{"type":21,"tag":399,"props":18343,"children":18344},{"style":644},[18345],{"type":26,"value":15149},{"type":21,"tag":399,"props":18347,"children":18348},{"style":610},[18349],{"type":26,"value":18350},">Submit\u003C/",{"type":21,"tag":399,"props":18352,"children":18353},{"style":15044},[18354],{"type":26,"value":15135},{"type":21,"tag":399,"props":18356,"children":18357},{"style":610},[18358],{"type":26,"value":15052},{"type":21,"tag":399,"props":18360,"children":18361},{"class":605,"line":773},[18362,18367,18371],{"type":21,"tag":399,"props":18363,"children":18364},{"style":610},[18365],{"type":26,"value":18366},"\u003C/",{"type":21,"tag":399,"props":18368,"children":18369},{"style":15044},[18370],{"type":26,"value":15072},{"type":21,"tag":399,"props":18372,"children":18373},{"style":610},[18374],{"type":26,"value":15052},{"type":21,"tag":12255,"props":18376,"children":18378},{"id":18377},"thats-step-1-complete",[18379],{"type":26,"value":18380},"That's step 1 complete...",{"type":21,"tag":190,"props":18382,"children":18384},{"id":18383},"step-2-validating-the-email",[18385],{"type":26,"value":18386},"Step 2 - Validating the email",{"type":21,"tag":22,"props":18388,"children":18389},{},[18390,18392,18398],{"type":26,"value":18391},"The latter of the two pipeline functions called out is the ",{"type":21,"tag":206,"props":18393,"children":18395},{"href":17200,"rel":18394},[210],[18396],{"type":26,"value":18397},"email validation partial pipeline discussed in the docs",{"type":26,"value":18399},". In order to effectively use it, we need to include a few additional entries in settings.py, as well as create some further custom functions.",{"type":21,"tag":595,"props":18401,"children":18403},{"className":597,"code":18402,"language":16,"meta":8,"style":8},"SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION = 'app.email.SendVerificationEmail'\nSOCIAL_AUTH_EMAIL_VALIDATION_URL = '/email_verify_sent/'\n",[18404],{"type":21,"tag":43,"props":18405,"children":18406},{"__ignoreMap":8},[18407,18424],{"type":21,"tag":399,"props":18408,"children":18409},{"class":605,"line":606},[18410,18415,18419],{"type":21,"tag":399,"props":18411,"children":18412},{"style":630},[18413],{"type":26,"value":18414},"SOCIAL_AUTH_EMAIL_VALIDATION_FUNCTION",{"type":21,"tag":399,"props":18416,"children":18417},{"style":616},[18418],{"type":26,"value":4224},{"type":21,"tag":399,"props":18420,"children":18421},{"style":644},[18422],{"type":26,"value":18423}," 'app.email.SendVerificationEmail'\n",{"type":21,"tag":399,"props":18425,"children":18426},{"class":605,"line":239},[18427,18432,18436],{"type":21,"tag":399,"props":18428,"children":18429},{"style":630},[18430],{"type":26,"value":18431},"SOCIAL_AUTH_EMAIL_VALIDATION_URL",{"type":21,"tag":399,"props":18433,"children":18434},{"style":616},[18435],{"type":26,"value":4224},{"type":21,"tag":399,"props":18437,"children":18438},{"style":644},[18439],{"type":26,"value":18440}," '/email_verify_sent/'\n",{"type":21,"tag":22,"props":18442,"children":18443},{},[18444,18446,18452,18454,18460],{"type":26,"value":18445},"The latter of these two is the url that the user will be redirected to once the validation email has been sent - you should add a view and url entry for this as per usual. For the former, we'll need to create a function that actually sends the verifiction email. Here we've created it in a module ",{"type":21,"tag":43,"props":18447,"children":18449},{"className":18448},[],[18450],{"type":26,"value":18451},"email",{"type":26,"value":18453}," within the application ",{"type":21,"tag":43,"props":18455,"children":18457},{"className":18456},[],[18458],{"type":26,"value":18459},"app",{"type":26,"value":18461},", but those are details that can be altered according to your project's needs.",{"type":21,"tag":22,"props":18463,"children":18464},{},[18465],{"type":26,"value":18466},"The function itself needs to build the email text you're going to send, and include some details that will enable the email validation partial_pipeline to resume. We'll be including more than the standard recommended details for reasons we'll discuss momentarily.",{"type":21,"tag":22,"props":18468,"children":18469},{},[18470],{"type":26,"value":18471},"Your email verification function should look something like the following:",{"type":21,"tag":595,"props":18473,"children":18475},{"className":597,"code":18474,"language":16,"meta":8,"style":8},"from django.core import signing\nfrom django.core.mail import EmailMultiAlternatives\n\ndef SendVerificationEmail(strategy, backend, code):\n    \"\"\"\n    Send an email with an embedded verification code and the necessary details to restore the required session\n    elements to complete the verification and sign-in, regardless of what browser the user completes the\n    verification from.\n    \"\"\"\n    signature = signing.dumps({\"session_key\": strategy.session.session_key, \"email\": code.email},\n                              key=settings.EMAIL_SECRET_KEY)\n    verifyURL = \"{0}?verification_code={1}&signature={2}\".format(\n        reverse('social:complete', args=(backend.name,)),\n        code.code, signature)\n    verifyURL = strategy.request.build_absolute_uri(verifyURL)   \n\n    emailHTML = # Include your function that returns an html string here\n    emailText = \"\"\"Welcome to MyApp!\nIn order to login with your new user account, you need to verify your email address with us.\nPlease copy and paste the following into your browser's url bar: {verifyURL}\n\"\"\".format(verifyURL=verifyURL)\n\n    kwargs = {\n        \"subject\": \"Verify Your Account\",\n        \"body\": emailText,\n        \"from_email\": \"MyApp \",\n        \"to\": [\"recipient@email.address\"],\n    }\n\n    email = EmailMultiAlternatives(**kwargs)\n    email.attach_alternative(emailHTML, \"text/html\")\n    email.send()\n",[18476],{"type":21,"tag":43,"props":18477,"children":18478},{"__ignoreMap":8},[18479,18500,18521,18528,18545,18552,18560,18568,18576,18583,18619,18644,18695,18726,18734,18750,18757,18774,18791,18799,18812,18839,18846,18862,18883,18896,18917,18939,18946,18953,18979,18996],{"type":21,"tag":399,"props":18480,"children":18481},{"class":605,"line":606},[18482,18486,18491,18495],{"type":21,"tag":399,"props":18483,"children":18484},{"style":616},[18485],{"type":26,"value":2669},{"type":21,"tag":399,"props":18487,"children":18488},{"style":610},[18489],{"type":26,"value":18490}," django.core ",{"type":21,"tag":399,"props":18492,"children":18493},{"style":616},[18494],{"type":26,"value":2679},{"type":21,"tag":399,"props":18496,"children":18497},{"style":610},[18498],{"type":26,"value":18499}," signing\n",{"type":21,"tag":399,"props":18501,"children":18502},{"class":605,"line":239},[18503,18507,18512,18516],{"type":21,"tag":399,"props":18504,"children":18505},{"style":616},[18506],{"type":26,"value":2669},{"type":21,"tag":399,"props":18508,"children":18509},{"style":610},[18510],{"type":26,"value":18511}," django.core.mail ",{"type":21,"tag":399,"props":18513,"children":18514},{"style":616},[18515],{"type":26,"value":2679},{"type":21,"tag":399,"props":18517,"children":18518},{"style":610},[18519],{"type":26,"value":18520}," EmailMultiAlternatives\n",{"type":21,"tag":399,"props":18522,"children":18523},{"class":605,"line":232},[18524],{"type":21,"tag":399,"props":18525,"children":18526},{"emptyLinePlaceholder":2690},[18527],{"type":26,"value":2693},{"type":21,"tag":399,"props":18529,"children":18530},{"class":605,"line":654},[18531,18535,18540],{"type":21,"tag":399,"props":18532,"children":18533},{"style":616},[18534],{"type":26,"value":3281},{"type":21,"tag":399,"props":18536,"children":18537},{"style":2704},[18538],{"type":26,"value":18539}," SendVerificationEmail",{"type":21,"tag":399,"props":18541,"children":18542},{"style":610},[18543],{"type":26,"value":18544},"(strategy, backend, code):\n",{"type":21,"tag":399,"props":18546,"children":18547},{"class":605,"line":698},[18548],{"type":21,"tag":399,"props":18549,"children":18550},{"style":644},[18551],{"type":26,"value":14223},{"type":21,"tag":399,"props":18553,"children":18554},{"class":605,"line":737},[18555],{"type":21,"tag":399,"props":18556,"children":18557},{"style":644},[18558],{"type":26,"value":18559},"    Send an email with an embedded verification code and the necessary details to restore the required session\n",{"type":21,"tag":399,"props":18561,"children":18562},{"class":605,"line":755},[18563],{"type":21,"tag":399,"props":18564,"children":18565},{"style":644},[18566],{"type":26,"value":18567},"    elements to complete the verification and sign-in, regardless of what browser the user completes the\n",{"type":21,"tag":399,"props":18569,"children":18570},{"class":605,"line":773},[18571],{"type":21,"tag":399,"props":18572,"children":18573},{"style":644},[18574],{"type":26,"value":18575},"    verification from.\n",{"type":21,"tag":399,"props":18577,"children":18578},{"class":605,"line":795},[18579],{"type":21,"tag":399,"props":18580,"children":18581},{"style":644},[18582],{"type":26,"value":14223},{"type":21,"tag":399,"props":18584,"children":18585},{"class":605,"line":804},[18586,18591,18595,18600,18605,18610,18614],{"type":21,"tag":399,"props":18587,"children":18588},{"style":610},[18589],{"type":26,"value":18590},"    signature ",{"type":21,"tag":399,"props":18592,"children":18593},{"style":616},[18594],{"type":26,"value":619},{"type":21,"tag":399,"props":18596,"children":18597},{"style":610},[18598],{"type":26,"value":18599}," signing.dumps({",{"type":21,"tag":399,"props":18601,"children":18602},{"style":644},[18603],{"type":26,"value":18604},"\"session_key\"",{"type":21,"tag":399,"props":18606,"children":18607},{"style":610},[18608],{"type":26,"value":18609},": strategy.session.session_key, ",{"type":21,"tag":399,"props":18611,"children":18612},{"style":644},[18613],{"type":26,"value":18208},{"type":21,"tag":399,"props":18615,"children":18616},{"style":610},[18617],{"type":26,"value":18618},": code.email},\n",{"type":21,"tag":399,"props":18620,"children":18621},{"class":605,"line":843},[18622,18627,18631,18635,18640],{"type":21,"tag":399,"props":18623,"children":18624},{"style":658},[18625],{"type":26,"value":18626},"                              key",{"type":21,"tag":399,"props":18628,"children":18629},{"style":616},[18630],{"type":26,"value":619},{"type":21,"tag":399,"props":18632,"children":18633},{"style":610},[18634],{"type":26,"value":8968},{"type":21,"tag":399,"props":18636,"children":18637},{"style":630},[18638],{"type":26,"value":18639},"EMAIL_SECRET_KEY",{"type":21,"tag":399,"props":18641,"children":18642},{"style":610},[18643],{"type":26,"value":1652},{"type":21,"tag":399,"props":18645,"children":18646},{"class":605,"line":882},[18647,18652,18656,18661,18666,18671,18676,18681,18686,18690],{"type":21,"tag":399,"props":18648,"children":18649},{"style":610},[18650],{"type":26,"value":18651},"    verifyURL ",{"type":21,"tag":399,"props":18653,"children":18654},{"style":616},[18655],{"type":26,"value":619},{"type":21,"tag":399,"props":18657,"children":18658},{"style":644},[18659],{"type":26,"value":18660}," \"",{"type":21,"tag":399,"props":18662,"children":18663},{"style":630},[18664],{"type":26,"value":18665},"{0}",{"type":21,"tag":399,"props":18667,"children":18668},{"style":644},[18669],{"type":26,"value":18670},"?verification_code=",{"type":21,"tag":399,"props":18672,"children":18673},{"style":630},[18674],{"type":26,"value":18675},"{1}",{"type":21,"tag":399,"props":18677,"children":18678},{"style":644},[18679],{"type":26,"value":18680},"&signature=",{"type":21,"tag":399,"props":18682,"children":18683},{"style":630},[18684],{"type":26,"value":18685},"{2}",{"type":21,"tag":399,"props":18687,"children":18688},{"style":644},[18689],{"type":26,"value":2986},{"type":21,"tag":399,"props":18691,"children":18692},{"style":610},[18693],{"type":26,"value":18694},".format(\n",{"type":21,"tag":399,"props":18696,"children":18697},{"class":605,"line":900},[18698,18703,18708,18712,18717,18721],{"type":21,"tag":399,"props":18699,"children":18700},{"style":610},[18701],{"type":26,"value":18702},"        reverse(",{"type":21,"tag":399,"props":18704,"children":18705},{"style":644},[18706],{"type":26,"value":18707},"'social:complete'",{"type":21,"tag":399,"props":18709,"children":18710},{"style":610},[18711],{"type":26,"value":685},{"type":21,"tag":399,"props":18713,"children":18714},{"style":658},[18715],{"type":26,"value":18716},"args",{"type":21,"tag":399,"props":18718,"children":18719},{"style":616},[18720],{"type":26,"value":619},{"type":21,"tag":399,"props":18722,"children":18723},{"style":610},[18724],{"type":26,"value":18725},"(backend.name,)),\n",{"type":21,"tag":399,"props":18727,"children":18728},{"class":605,"line":923},[18729],{"type":21,"tag":399,"props":18730,"children":18731},{"style":610},[18732],{"type":26,"value":18733},"        code.code, signature)\n",{"type":21,"tag":399,"props":18735,"children":18736},{"class":605,"line":969},[18737,18741,18745],{"type":21,"tag":399,"props":18738,"children":18739},{"style":610},[18740],{"type":26,"value":18651},{"type":21,"tag":399,"props":18742,"children":18743},{"style":616},[18744],{"type":26,"value":619},{"type":21,"tag":399,"props":18746,"children":18747},{"style":610},[18748],{"type":26,"value":18749}," strategy.request.build_absolute_uri(verifyURL)   \n",{"type":21,"tag":399,"props":18751,"children":18752},{"class":605,"line":991},[18753],{"type":21,"tag":399,"props":18754,"children":18755},{"emptyLinePlaceholder":2690},[18756],{"type":26,"value":2693},{"type":21,"tag":399,"props":18758,"children":18759},{"class":605,"line":1013},[18760,18765,18769],{"type":21,"tag":399,"props":18761,"children":18762},{"style":610},[18763],{"type":26,"value":18764},"    emailHTML ",{"type":21,"tag":399,"props":18766,"children":18767},{"style":616},[18768],{"type":26,"value":619},{"type":21,"tag":399,"props":18770,"children":18771},{"style":2818},[18772],{"type":26,"value":18773}," # Include your function that returns an html string here\n",{"type":21,"tag":399,"props":18775,"children":18776},{"class":605,"line":1035},[18777,18782,18786],{"type":21,"tag":399,"props":18778,"children":18779},{"style":610},[18780],{"type":26,"value":18781},"    emailText ",{"type":21,"tag":399,"props":18783,"children":18784},{"style":616},[18785],{"type":26,"value":619},{"type":21,"tag":399,"props":18787,"children":18788},{"style":644},[18789],{"type":26,"value":18790}," \"\"\"Welcome to MyApp!\n",{"type":21,"tag":399,"props":18792,"children":18793},{"class":605,"line":1092},[18794],{"type":21,"tag":399,"props":18795,"children":18796},{"style":644},[18797],{"type":26,"value":18798},"In order to login with your new user account, you need to verify your email address with us.\n",{"type":21,"tag":399,"props":18800,"children":18801},{"class":605,"line":1114},[18802,18807],{"type":21,"tag":399,"props":18803,"children":18804},{"style":644},[18805],{"type":26,"value":18806},"Please copy and paste the following into your browser's url bar: ",{"type":21,"tag":399,"props":18808,"children":18809},{"style":630},[18810],{"type":26,"value":18811},"{verifyURL}\n",{"type":21,"tag":399,"props":18813,"children":18814},{"class":605,"line":1136},[18815,18820,18825,18830,18834],{"type":21,"tag":399,"props":18816,"children":18817},{"style":644},[18818],{"type":26,"value":18819},"\"\"\"",{"type":21,"tag":399,"props":18821,"children":18822},{"style":610},[18823],{"type":26,"value":18824},".format(",{"type":21,"tag":399,"props":18826,"children":18827},{"style":658},[18828],{"type":26,"value":18829},"verifyURL",{"type":21,"tag":399,"props":18831,"children":18832},{"style":616},[18833],{"type":26,"value":619},{"type":21,"tag":399,"props":18835,"children":18836},{"style":610},[18837],{"type":26,"value":18838},"verifyURL)\n",{"type":21,"tag":399,"props":18840,"children":18841},{"class":605,"line":1158},[18842],{"type":21,"tag":399,"props":18843,"children":18844},{"emptyLinePlaceholder":2690},[18845],{"type":26,"value":2693},{"type":21,"tag":399,"props":18847,"children":18848},{"class":605,"line":1180},[18849,18854,18858],{"type":21,"tag":399,"props":18850,"children":18851},{"style":610},[18852],{"type":26,"value":18853},"    kwargs ",{"type":21,"tag":399,"props":18855,"children":18856},{"style":616},[18857],{"type":26,"value":619},{"type":21,"tag":399,"props":18859,"children":18860},{"style":610},[18861],{"type":26,"value":8993},{"type":21,"tag":399,"props":18863,"children":18864},{"class":605,"line":1202},[18865,18870,18874,18879],{"type":21,"tag":399,"props":18866,"children":18867},{"style":644},[18868],{"type":26,"value":18869},"        \"subject\"",{"type":21,"tag":399,"props":18871,"children":18872},{"style":610},[18873],{"type":26,"value":911},{"type":21,"tag":399,"props":18875,"children":18876},{"style":644},[18877],{"type":26,"value":18878},"\"Verify Your Account\"",{"type":21,"tag":399,"props":18880,"children":18881},{"style":610},[18882],{"type":26,"value":638},{"type":21,"tag":399,"props":18884,"children":18885},{"class":605,"line":1211},[18886,18891],{"type":21,"tag":399,"props":18887,"children":18888},{"style":644},[18889],{"type":26,"value":18890},"        \"body\"",{"type":21,"tag":399,"props":18892,"children":18893},{"style":610},[18894],{"type":26,"value":18895},": emailText,\n",{"type":21,"tag":399,"props":18897,"children":18898},{"class":605,"line":1228},[18899,18904,18908,18913],{"type":21,"tag":399,"props":18900,"children":18901},{"style":644},[18902],{"type":26,"value":18903},"        \"from_email\"",{"type":21,"tag":399,"props":18905,"children":18906},{"style":610},[18907],{"type":26,"value":911},{"type":21,"tag":399,"props":18909,"children":18910},{"style":644},[18911],{"type":26,"value":18912},"\"MyApp \"",{"type":21,"tag":399,"props":18914,"children":18915},{"style":610},[18916],{"type":26,"value":638},{"type":21,"tag":399,"props":18918,"children":18919},{"class":605,"line":1269},[18920,18925,18930,18935],{"type":21,"tag":399,"props":18921,"children":18922},{"style":644},[18923],{"type":26,"value":18924},"        \"to\"",{"type":21,"tag":399,"props":18926,"children":18927},{"style":610},[18928],{"type":26,"value":18929},": [",{"type":21,"tag":399,"props":18931,"children":18932},{"style":644},[18933],{"type":26,"value":18934},"\"recipient@email.address\"",{"type":21,"tag":399,"props":18936,"children":18937},{"style":610},[18938],{"type":26,"value":14878},{"type":21,"tag":399,"props":18940,"children":18941},{"class":605,"line":1307},[18942],{"type":21,"tag":399,"props":18943,"children":18944},{"style":610},[18945],{"type":26,"value":4328},{"type":21,"tag":399,"props":18947,"children":18948},{"class":605,"line":1346},[18949],{"type":21,"tag":399,"props":18950,"children":18951},{"emptyLinePlaceholder":2690},[18952],{"type":26,"value":2693},{"type":21,"tag":399,"props":18954,"children":18955},{"class":605,"line":1355},[18956,18961,18965,18970,18974],{"type":21,"tag":399,"props":18957,"children":18958},{"style":610},[18959],{"type":26,"value":18960},"    email ",{"type":21,"tag":399,"props":18962,"children":18963},{"style":616},[18964],{"type":26,"value":619},{"type":21,"tag":399,"props":18966,"children":18967},{"style":610},[18968],{"type":26,"value":18969}," EmailMultiAlternatives(",{"type":21,"tag":399,"props":18971,"children":18972},{"style":616},[18973],{"type":26,"value":17675},{"type":21,"tag":399,"props":18975,"children":18976},{"style":610},[18977],{"type":26,"value":18978},"kwargs)\n",{"type":21,"tag":399,"props":18980,"children":18981},{"class":605,"line":1364},[18982,18987,18992],{"type":21,"tag":399,"props":18983,"children":18984},{"style":610},[18985],{"type":26,"value":18986},"    email.attach_alternative(emailHTML, ",{"type":21,"tag":399,"props":18988,"children":18989},{"style":644},[18990],{"type":26,"value":18991},"\"text/html\"",{"type":21,"tag":399,"props":18993,"children":18994},{"style":610},[18995],{"type":26,"value":1652},{"type":21,"tag":399,"props":18997,"children":18998},{"class":605,"line":1403},[18999],{"type":21,"tag":399,"props":19000,"children":19001},{"style":610},[19002],{"type":26,"value":19003},"    email.send()\n",{"type":21,"tag":22,"props":19005,"children":19006},{},[19007,19009,19016,19018,19025],{"type":26,"value":19008},"You'll notice we used Django's ",{"type":21,"tag":206,"props":19010,"children":19013},{"href":19011,"rel":19012},"https://docs.djangoproject.com/en/1.8/topics/email/#django.core.mail.EmailMessage",[210],[19014],{"type":26,"value":19015},"EmailMultiAlternatives",{"type":26,"value":19017}," to attach an html version of the email. You can find more details on sending email with Django in the ",{"type":21,"tag":206,"props":19019,"children":19022},{"href":19020,"rel":19021},"https://docs.djangoproject.com/en/1.8/topics/email/",[210],[19023],{"type":26,"value":19024},"Django docs",{"type":26,"value":2458},{"type":21,"tag":22,"props":19027,"children":19028},{},[19029],{"type":26,"value":19030},"You'll also likely have noticed that we're including some signed details in that verification email string we're sending. What details are for what purpose? Let's break it down, and discuss some necessary evil.",{"type":21,"tag":60,"props":19032,"children":19033},{},[19034,19045,19056],{"type":21,"tag":64,"props":19035,"children":19036},{},[19037,19043],{"type":21,"tag":43,"props":19038,"children":19040},{"className":19039},[],[19041],{"type":26,"value":19042},"code.code",{"type":26,"value":19044}," is the email verification code generated by the mail validation pipeline.",{"type":21,"tag":64,"props":19046,"children":19047},{},[19048,19054],{"type":21,"tag":43,"props":19049,"children":19051},{"className":19050},[],[19052],{"type":26,"value":19053},"code.email",{"type":26,"value":19055}," is the email address we're attempting to verify.",{"type":21,"tag":64,"props":19057,"children":19058},{},[19059,19065,19067,19073,19075,19081,19083,19089],{"type":21,"tag":43,"props":19060,"children":19062},{"className":19061},[],[19063],{"type":26,"value":19064},"signature",{"type":26,"value":19066}," is our own addition to the process - you'll notice it include both the email we're trying to verify, and a ",{"type":21,"tag":43,"props":19068,"children":19070},{"className":19069},[],[19071],{"type":26,"value":19072},"session_key",{"type":26,"value":19074},", which contains the session_key stored in our current backend strategy. Finally, ",{"type":21,"tag":43,"props":19076,"children":19078},{"className":19077},[],[19079],{"type":26,"value":19080},"key",{"type":26,"value":19082}," is the secret we're using to sign this signature with, to prevent tampering - you can find more details on signing in the ",{"type":21,"tag":206,"props":19084,"children":19087},{"href":19085,"rel":19086},"https://docs.djangoproject.com/en/1.8/topics/signing/",[210],[19088],{"type":26,"value":19024},{"type":26,"value":2458},{"type":21,"tag":190,"props":19091,"children":19093},{"id":19092},"step-3-validating-the-email-from-any-browser",[19094],{"type":26,"value":19095},"Step 3 - Validating the email from any browser",{"type":21,"tag":22,"props":19097,"children":19098},{},[19099],{"type":26,"value":19100},"What do we need that signature for, though? This may have been remedied in whatever version of python-social-auth your using, but at the time of this writing (July 2015), if your receiving user were to click/paste the link you included in your validation email into a different browser, they'd likely be confronted with a 500 error page, and you'll have an entry in your log:",{"type":21,"tag":595,"props":19102,"children":19105},{"className":19103,"code":19104,"language":26},[3074],"Partial pipeline can not resume\n",[19106],{"type":21,"tag":43,"props":19107,"children":19108},{"__ignoreMap":8},[19109],{"type":26,"value":19104},{"type":21,"tag":22,"props":19111,"children":19112},{},[19113,19115,19120],{"type":26,"value":19114},"In fact, if they were to click that link in the same browser, but after clearing their cache, or after a long period of time causes their current session to expire, they'd run into the same problem. It turns out that python-social-auth relies on the session to store the details of a partial pipeline - which means that if a user tries to resume the pipeline (as in the case of validating their email) from a different browser, ",{"type":21,"tag":195,"props":19116,"children":19117},{},[19118],{"type":26,"value":19119},"it will fail",{"type":26,"value":2458},{"type":21,"tag":22,"props":19122,"children":19123},{},[19124],{"type":26,"value":19125},"The expected user flow here is that the user should be able to validate their email address from any browser, indeed, from any device that can access their email account. They shouldn't need to use the same browser within the arbitrary lifetime of their current server session. Let's fix that.",{"type":21,"tag":22,"props":19127,"children":19128},{},[19129,19131,19137,19139,19146],{"type":26,"value":19130},"Here comes the necessary evil - we'll need to monkey-patch ",{"type":21,"tag":43,"props":19132,"children":19134},{"className":19133},[],[19135],{"type":26,"value":19136},"social.utils.partial_pipeline_data",{"type":26,"value":19138}," in order to allow us to restore the required session data from the stored session in order to resume the pipeline. Our solution takes advantage of the standard storage of ",{"type":21,"tag":206,"props":19140,"children":19143},{"href":19141,"rel":19142},"https://docs.djangoproject.com/en/1.8/topics/http/sessions/#using-database-backed-sessions",[210],[19144],{"type":26,"value":19145},"Django sessions in a database table",{"type":26,"value":19147},", allowing us to interact with the stored sessions in the same way we do other tables.",{"type":21,"tag":22,"props":19149,"children":19150},{},[19151],{"type":26,"value":19152},"We confirm the cryptographic signature on the details within the url parameter, fetch the necessary table row for the session using the session_key, get the needed fields from the row, and presto, we can now properly resume the pipeline.",{"type":21,"tag":595,"props":19154,"children":19156},{"className":597,"code":19155,"language":16,"meta":8,"style":8},"# Monkey patching - an occasionally necessary evil.\nfrom social import utils\nfrom social.exceptions import InvalidEmail\n\nfrom django.core import signing\nfrom django.core.signing import BadSignature\nfrom django.contrib.sessions.models import Session\nfrom django.conf import settings\n\ndef partial_pipeline_data(backend, user=None, *args, **kwargs):\n    \"\"\"\n    Monkey-patch utils.partial_pipeline_data to enable us to retrieve session data by signature key in request.\n    This is necessary to allow users to follow a link in an email to validate their account from a different\n    browser than the one they were using to sign up for the account, or after they've closed/re-opened said\n    browser and potentially flushed their cookies. By adding the session key to a signed base64 encoded signature\n    on the email request, we can retrieve the necessary details from our Django session table.\n    We fetch only the needed details to complete the pipeline authorization process from the session, to prevent\n    nefarious use.\n    \"\"\"\n    data = backend.strategy.request_data()\n    if 'signature' in data:\n        try:\n            signed_details = signing.loads(data['signature'], key=settings.EMAIL_SECRET_KEY)\n            session = Session.objects.get(pk=signed_details['session_key'])\n        except BadSignature, Session.DoesNotExist:\n            raise InvalidEmail(backend)\n\n        session_details = session.get_decoded()\n        backend.strategy.session_set('email_validation_address', session_details['email_validation_address'])\n        backend.strategy.session_set('next', session_details.get('next'))\n        backend.strategy.session_set('partial_pipeline', session_details['partial_pipeline'])\n        backend.strategy.session_set(backend.name + '_state', session_details.get(backend.name + '_state'))\n        backend.strategy.session_set(backend.name + 'unauthorized_token_name',\n                                     session_details.get(backend.name + 'unauthorized_token_name'))\n\n    partial = backend.strategy.session_get('partial_pipeline', None)\n    if partial:\n        idx, backend_name, xargs, xkwargs = \\\n            backend.strategy.partial_from_session(partial)\n        if backend_name == backend.name:\n            kwargs.setdefault('pipeline_index', idx)\n            if user:  # don't update user if it's None\n                kwargs.setdefault('user', user)\n            kwargs.setdefault('request', backend.strategy.request_data())\n            xkwargs.update(kwargs)\n            return xargs, xkwargs\n        else:\n            backend.strategy.clean_partial_pipeline()\nutils.partial_pipeline_data = partial_pipeline_data\nViewport\nWindow\n",[19157],{"type":21,"tag":43,"props":19158,"children":19159},{"__ignoreMap":8},[19160,19168,19189,19210,19217,19236,19257,19278,19299,19306,19351,19358,19366,19374,19382,19390,19398,19406,19414,19421,19438,19458,19470,19516,19556,19569,19582,19589,19606,19632,19658,19681,19715,19735,19755,19762,19795,19807,19824,19832,19853,19871,19889,19907,19924,19932,19944,19955,19963,19980,19988],{"type":21,"tag":399,"props":19161,"children":19162},{"class":605,"line":606},[19163],{"type":21,"tag":399,"props":19164,"children":19165},{"style":2818},[19166],{"type":26,"value":19167},"# Monkey patching - an occasionally necessary evil.\n",{"type":21,"tag":399,"props":19169,"children":19170},{"class":605,"line":239},[19171,19175,19180,19184],{"type":21,"tag":399,"props":19172,"children":19173},{"style":616},[19174],{"type":26,"value":2669},{"type":21,"tag":399,"props":19176,"children":19177},{"style":610},[19178],{"type":26,"value":19179}," social ",{"type":21,"tag":399,"props":19181,"children":19182},{"style":616},[19183],{"type":26,"value":2679},{"type":21,"tag":399,"props":19185,"children":19186},{"style":610},[19187],{"type":26,"value":19188}," utils\n",{"type":21,"tag":399,"props":19190,"children":19191},{"class":605,"line":232},[19192,19196,19201,19205],{"type":21,"tag":399,"props":19193,"children":19194},{"style":616},[19195],{"type":26,"value":2669},{"type":21,"tag":399,"props":19197,"children":19198},{"style":610},[19199],{"type":26,"value":19200}," social.exceptions ",{"type":21,"tag":399,"props":19202,"children":19203},{"style":616},[19204],{"type":26,"value":2679},{"type":21,"tag":399,"props":19206,"children":19207},{"style":610},[19208],{"type":26,"value":19209}," InvalidEmail\n",{"type":21,"tag":399,"props":19211,"children":19212},{"class":605,"line":654},[19213],{"type":21,"tag":399,"props":19214,"children":19215},{"emptyLinePlaceholder":2690},[19216],{"type":26,"value":2693},{"type":21,"tag":399,"props":19218,"children":19219},{"class":605,"line":698},[19220,19224,19228,19232],{"type":21,"tag":399,"props":19221,"children":19222},{"style":616},[19223],{"type":26,"value":2669},{"type":21,"tag":399,"props":19225,"children":19226},{"style":610},[19227],{"type":26,"value":18490},{"type":21,"tag":399,"props":19229,"children":19230},{"style":616},[19231],{"type":26,"value":2679},{"type":21,"tag":399,"props":19233,"children":19234},{"style":610},[19235],{"type":26,"value":18499},{"type":21,"tag":399,"props":19237,"children":19238},{"class":605,"line":737},[19239,19243,19248,19252],{"type":21,"tag":399,"props":19240,"children":19241},{"style":616},[19242],{"type":26,"value":2669},{"type":21,"tag":399,"props":19244,"children":19245},{"style":610},[19246],{"type":26,"value":19247}," django.core.signing ",{"type":21,"tag":399,"props":19249,"children":19250},{"style":616},[19251],{"type":26,"value":2679},{"type":21,"tag":399,"props":19253,"children":19254},{"style":610},[19255],{"type":26,"value":19256}," BadSignature\n",{"type":21,"tag":399,"props":19258,"children":19259},{"class":605,"line":755},[19260,19264,19269,19273],{"type":21,"tag":399,"props":19261,"children":19262},{"style":616},[19263],{"type":26,"value":2669},{"type":21,"tag":399,"props":19265,"children":19266},{"style":610},[19267],{"type":26,"value":19268}," django.contrib.sessions.models ",{"type":21,"tag":399,"props":19270,"children":19271},{"style":616},[19272],{"type":26,"value":2679},{"type":21,"tag":399,"props":19274,"children":19275},{"style":610},[19276],{"type":26,"value":19277}," Session\n",{"type":21,"tag":399,"props":19279,"children":19280},{"class":605,"line":773},[19281,19285,19290,19294],{"type":21,"tag":399,"props":19282,"children":19283},{"style":616},[19284],{"type":26,"value":2669},{"type":21,"tag":399,"props":19286,"children":19287},{"style":610},[19288],{"type":26,"value":19289}," django.conf ",{"type":21,"tag":399,"props":19291,"children":19292},{"style":616},[19293],{"type":26,"value":2679},{"type":21,"tag":399,"props":19295,"children":19296},{"style":610},[19297],{"type":26,"value":19298}," settings\n",{"type":21,"tag":399,"props":19300,"children":19301},{"class":605,"line":795},[19302],{"type":21,"tag":399,"props":19303,"children":19304},{"emptyLinePlaceholder":2690},[19305],{"type":26,"value":2693},{"type":21,"tag":399,"props":19307,"children":19308},{"class":605,"line":804},[19309,19313,19318,19323,19327,19331,19335,19339,19343,19347],{"type":21,"tag":399,"props":19310,"children":19311},{"style":616},[19312],{"type":26,"value":3281},{"type":21,"tag":399,"props":19314,"children":19315},{"style":2704},[19316],{"type":26,"value":19317}," partial_pipeline_data",{"type":21,"tag":399,"props":19319,"children":19320},{"style":610},[19321],{"type":26,"value":19322},"(backend, user",{"type":21,"tag":399,"props":19324,"children":19325},{"style":616},[19326],{"type":26,"value":619},{"type":21,"tag":399,"props":19328,"children":19329},{"style":630},[19330],{"type":26,"value":9081},{"type":21,"tag":399,"props":19332,"children":19333},{"style":610},[19334],{"type":26,"value":685},{"type":21,"tag":399,"props":19336,"children":19337},{"style":616},[19338],{"type":26,"value":17665},{"type":21,"tag":399,"props":19340,"children":19341},{"style":610},[19342],{"type":26,"value":17670},{"type":21,"tag":399,"props":19344,"children":19345},{"style":616},[19346],{"type":26,"value":17675},{"type":21,"tag":399,"props":19348,"children":19349},{"style":610},[19350],{"type":26,"value":17680},{"type":21,"tag":399,"props":19352,"children":19353},{"class":605,"line":843},[19354],{"type":21,"tag":399,"props":19355,"children":19356},{"style":644},[19357],{"type":26,"value":14223},{"type":21,"tag":399,"props":19359,"children":19360},{"class":605,"line":882},[19361],{"type":21,"tag":399,"props":19362,"children":19363},{"style":644},[19364],{"type":26,"value":19365},"    Monkey-patch utils.partial_pipeline_data to enable us to retrieve session data by signature key in request.\n",{"type":21,"tag":399,"props":19367,"children":19368},{"class":605,"line":900},[19369],{"type":21,"tag":399,"props":19370,"children":19371},{"style":644},[19372],{"type":26,"value":19373},"    This is necessary to allow users to follow a link in an email to validate their account from a different\n",{"type":21,"tag":399,"props":19375,"children":19376},{"class":605,"line":923},[19377],{"type":21,"tag":399,"props":19378,"children":19379},{"style":644},[19380],{"type":26,"value":19381},"    browser than the one they were using to sign up for the account, or after they've closed/re-opened said\n",{"type":21,"tag":399,"props":19383,"children":19384},{"class":605,"line":969},[19385],{"type":21,"tag":399,"props":19386,"children":19387},{"style":644},[19388],{"type":26,"value":19389},"    browser and potentially flushed their cookies. By adding the session key to a signed base64 encoded signature\n",{"type":21,"tag":399,"props":19391,"children":19392},{"class":605,"line":991},[19393],{"type":21,"tag":399,"props":19394,"children":19395},{"style":644},[19396],{"type":26,"value":19397},"    on the email request, we can retrieve the necessary details from our Django session table.\n",{"type":21,"tag":399,"props":19399,"children":19400},{"class":605,"line":1013},[19401],{"type":21,"tag":399,"props":19402,"children":19403},{"style":644},[19404],{"type":26,"value":19405},"    We fetch only the needed details to complete the pipeline authorization process from the session, to prevent\n",{"type":21,"tag":399,"props":19407,"children":19408},{"class":605,"line":1035},[19409],{"type":21,"tag":399,"props":19410,"children":19411},{"style":644},[19412],{"type":26,"value":19413},"    nefarious use.\n",{"type":21,"tag":399,"props":19415,"children":19416},{"class":605,"line":1092},[19417],{"type":21,"tag":399,"props":19418,"children":19419},{"style":644},[19420],{"type":26,"value":14223},{"type":21,"tag":399,"props":19422,"children":19423},{"class":605,"line":1114},[19424,19429,19433],{"type":21,"tag":399,"props":19425,"children":19426},{"style":610},[19427],{"type":26,"value":19428},"    data ",{"type":21,"tag":399,"props":19430,"children":19431},{"style":616},[19432],{"type":26,"value":619},{"type":21,"tag":399,"props":19434,"children":19435},{"style":610},[19436],{"type":26,"value":19437}," backend.strategy.request_data()\n",{"type":21,"tag":399,"props":19439,"children":19440},{"class":605,"line":1136},[19441,19445,19450,19454],{"type":21,"tag":399,"props":19442,"children":19443},{"style":616},[19444],{"type":26,"value":16619},{"type":21,"tag":399,"props":19446,"children":19447},{"style":644},[19448],{"type":26,"value":19449}," 'signature'",{"type":21,"tag":399,"props":19451,"children":19452},{"style":616},[19453],{"type":26,"value":3381},{"type":21,"tag":399,"props":19455,"children":19456},{"style":610},[19457],{"type":26,"value":3332},{"type":21,"tag":399,"props":19459,"children":19460},{"class":605,"line":1158},[19461,19466],{"type":21,"tag":399,"props":19462,"children":19463},{"style":616},[19464],{"type":26,"value":19465},"        try",{"type":21,"tag":399,"props":19467,"children":19468},{"style":610},[19469],{"type":26,"value":4683},{"type":21,"tag":399,"props":19471,"children":19472},{"class":605,"line":1180},[19473,19478,19482,19487,19492,19496,19500,19504,19508,19512],{"type":21,"tag":399,"props":19474,"children":19475},{"style":610},[19476],{"type":26,"value":19477},"            signed_details ",{"type":21,"tag":399,"props":19479,"children":19480},{"style":616},[19481],{"type":26,"value":619},{"type":21,"tag":399,"props":19483,"children":19484},{"style":610},[19485],{"type":26,"value":19486}," signing.loads(data[",{"type":21,"tag":399,"props":19488,"children":19489},{"style":644},[19490],{"type":26,"value":19491},"'signature'",{"type":21,"tag":399,"props":19493,"children":19494},{"style":610},[19495],{"type":26,"value":3443},{"type":21,"tag":399,"props":19497,"children":19498},{"style":658},[19499],{"type":26,"value":19080},{"type":21,"tag":399,"props":19501,"children":19502},{"style":616},[19503],{"type":26,"value":619},{"type":21,"tag":399,"props":19505,"children":19506},{"style":610},[19507],{"type":26,"value":8968},{"type":21,"tag":399,"props":19509,"children":19510},{"style":630},[19511],{"type":26,"value":18639},{"type":21,"tag":399,"props":19513,"children":19514},{"style":610},[19515],{"type":26,"value":1652},{"type":21,"tag":399,"props":19517,"children":19518},{"class":605,"line":1202},[19519,19524,19528,19533,19538,19542,19547,19552],{"type":21,"tag":399,"props":19520,"children":19521},{"style":610},[19522],{"type":26,"value":19523},"            session ",{"type":21,"tag":399,"props":19525,"children":19526},{"style":616},[19527],{"type":26,"value":619},{"type":21,"tag":399,"props":19529,"children":19530},{"style":610},[19531],{"type":26,"value":19532}," Session.objects.get(",{"type":21,"tag":399,"props":19534,"children":19535},{"style":658},[19536],{"type":26,"value":19537},"pk",{"type":21,"tag":399,"props":19539,"children":19540},{"style":616},[19541],{"type":26,"value":619},{"type":21,"tag":399,"props":19543,"children":19544},{"style":610},[19545],{"type":26,"value":19546},"signed_details[",{"type":21,"tag":399,"props":19548,"children":19549},{"style":644},[19550],{"type":26,"value":19551},"'session_key'",{"type":21,"tag":399,"props":19553,"children":19554},{"style":610},[19555],{"type":26,"value":4432},{"type":21,"tag":399,"props":19557,"children":19558},{"class":605,"line":1211},[19559,19564],{"type":21,"tag":399,"props":19560,"children":19561},{"style":616},[19562],{"type":26,"value":19563},"        except",{"type":21,"tag":399,"props":19565,"children":19566},{"style":610},[19567],{"type":26,"value":19568}," BadSignature, Session.DoesNotExist:\n",{"type":21,"tag":399,"props":19570,"children":19571},{"class":605,"line":1228},[19572,19577],{"type":21,"tag":399,"props":19573,"children":19574},{"style":616},[19575],{"type":26,"value":19576},"            raise",{"type":21,"tag":399,"props":19578,"children":19579},{"style":610},[19580],{"type":26,"value":19581}," InvalidEmail(backend)\n",{"type":21,"tag":399,"props":19583,"children":19584},{"class":605,"line":1269},[19585],{"type":21,"tag":399,"props":19586,"children":19587},{"emptyLinePlaceholder":2690},[19588],{"type":26,"value":2693},{"type":21,"tag":399,"props":19590,"children":19591},{"class":605,"line":1307},[19592,19597,19601],{"type":21,"tag":399,"props":19593,"children":19594},{"style":610},[19595],{"type":26,"value":19596},"        session_details ",{"type":21,"tag":399,"props":19598,"children":19599},{"style":616},[19600],{"type":26,"value":619},{"type":21,"tag":399,"props":19602,"children":19603},{"style":610},[19604],{"type":26,"value":19605}," session.get_decoded()\n",{"type":21,"tag":399,"props":19607,"children":19608},{"class":605,"line":1346},[19609,19614,19619,19624,19628],{"type":21,"tag":399,"props":19610,"children":19611},{"style":610},[19612],{"type":26,"value":19613},"        backend.strategy.session_set(",{"type":21,"tag":399,"props":19615,"children":19616},{"style":644},[19617],{"type":26,"value":19618},"'email_validation_address'",{"type":21,"tag":399,"props":19620,"children":19621},{"style":610},[19622],{"type":26,"value":19623},", session_details[",{"type":21,"tag":399,"props":19625,"children":19626},{"style":644},[19627],{"type":26,"value":19618},{"type":21,"tag":399,"props":19629,"children":19630},{"style":610},[19631],{"type":26,"value":4432},{"type":21,"tag":399,"props":19633,"children":19634},{"class":605,"line":1355},[19635,19639,19644,19649,19653],{"type":21,"tag":399,"props":19636,"children":19637},{"style":610},[19638],{"type":26,"value":19613},{"type":21,"tag":399,"props":19640,"children":19641},{"style":644},[19642],{"type":26,"value":19643},"'next'",{"type":21,"tag":399,"props":19645,"children":19646},{"style":610},[19647],{"type":26,"value":19648},", session_details.get(",{"type":21,"tag":399,"props":19650,"children":19651},{"style":644},[19652],{"type":26,"value":19643},{"type":21,"tag":399,"props":19654,"children":19655},{"style":610},[19656],{"type":26,"value":19657},"))\n",{"type":21,"tag":399,"props":19659,"children":19660},{"class":605,"line":1364},[19661,19665,19669,19673,19677],{"type":21,"tag":399,"props":19662,"children":19663},{"style":610},[19664],{"type":26,"value":19613},{"type":21,"tag":399,"props":19666,"children":19667},{"style":644},[19668],{"type":26,"value":17997},{"type":21,"tag":399,"props":19670,"children":19671},{"style":610},[19672],{"type":26,"value":19623},{"type":21,"tag":399,"props":19674,"children":19675},{"style":644},[19676],{"type":26,"value":17997},{"type":21,"tag":399,"props":19678,"children":19679},{"style":610},[19680],{"type":26,"value":4432},{"type":21,"tag":399,"props":19682,"children":19683},{"class":605,"line":1403},[19684,19689,19693,19698,19703,19707,19711],{"type":21,"tag":399,"props":19685,"children":19686},{"style":610},[19687],{"type":26,"value":19688},"        backend.strategy.session_set(backend.name ",{"type":21,"tag":399,"props":19690,"children":19691},{"style":616},[19692],{"type":26,"value":8807},{"type":21,"tag":399,"props":19694,"children":19695},{"style":644},[19696],{"type":26,"value":19697}," '_state'",{"type":21,"tag":399,"props":19699,"children":19700},{"style":610},[19701],{"type":26,"value":19702},", session_details.get(backend.name ",{"type":21,"tag":399,"props":19704,"children":19705},{"style":616},[19706],{"type":26,"value":8807},{"type":21,"tag":399,"props":19708,"children":19709},{"style":644},[19710],{"type":26,"value":19697},{"type":21,"tag":399,"props":19712,"children":19713},{"style":610},[19714],{"type":26,"value":19657},{"type":21,"tag":399,"props":19716,"children":19717},{"class":605,"line":1442},[19718,19722,19726,19731],{"type":21,"tag":399,"props":19719,"children":19720},{"style":610},[19721],{"type":26,"value":19688},{"type":21,"tag":399,"props":19723,"children":19724},{"style":616},[19725],{"type":26,"value":8807},{"type":21,"tag":399,"props":19727,"children":19728},{"style":644},[19729],{"type":26,"value":19730}," 'unauthorized_token_name'",{"type":21,"tag":399,"props":19732,"children":19733},{"style":610},[19734],{"type":26,"value":638},{"type":21,"tag":399,"props":19736,"children":19737},{"class":605,"line":1463},[19738,19743,19747,19751],{"type":21,"tag":399,"props":19739,"children":19740},{"style":610},[19741],{"type":26,"value":19742},"                                     session_details.get(backend.name ",{"type":21,"tag":399,"props":19744,"children":19745},{"style":616},[19746],{"type":26,"value":8807},{"type":21,"tag":399,"props":19748,"children":19749},{"style":644},[19750],{"type":26,"value":19730},{"type":21,"tag":399,"props":19752,"children":19753},{"style":610},[19754],{"type":26,"value":19657},{"type":21,"tag":399,"props":19756,"children":19757},{"class":605,"line":1501},[19758],{"type":21,"tag":399,"props":19759,"children":19760},{"emptyLinePlaceholder":2690},[19761],{"type":26,"value":2693},{"type":21,"tag":399,"props":19763,"children":19764},{"class":605,"line":1546},[19765,19770,19774,19779,19783,19787,19791],{"type":21,"tag":399,"props":19766,"children":19767},{"style":610},[19768],{"type":26,"value":19769},"    partial ",{"type":21,"tag":399,"props":19771,"children":19772},{"style":616},[19773],{"type":26,"value":619},{"type":21,"tag":399,"props":19775,"children":19776},{"style":610},[19777],{"type":26,"value":19778}," backend.strategy.session_get(",{"type":21,"tag":399,"props":19780,"children":19781},{"style":644},[19782],{"type":26,"value":17997},{"type":21,"tag":399,"props":19784,"children":19785},{"style":610},[19786],{"type":26,"value":685},{"type":21,"tag":399,"props":19788,"children":19789},{"style":630},[19790],{"type":26,"value":9081},{"type":21,"tag":399,"props":19792,"children":19793},{"style":610},[19794],{"type":26,"value":1652},{"type":21,"tag":399,"props":19796,"children":19797},{"class":605,"line":1585},[19798,19802],{"type":21,"tag":399,"props":19799,"children":19800},{"style":616},[19801],{"type":26,"value":16619},{"type":21,"tag":399,"props":19803,"children":19804},{"style":610},[19805],{"type":26,"value":19806}," partial:\n",{"type":21,"tag":399,"props":19808,"children":19809},{"class":605,"line":1624},[19810,19815,19819],{"type":21,"tag":399,"props":19811,"children":19812},{"style":610},[19813],{"type":26,"value":19814},"        idx, backend_name, xargs, xkwargs ",{"type":21,"tag":399,"props":19816,"children":19817},{"style":616},[19818],{"type":26,"value":619},{"type":21,"tag":399,"props":19820,"children":19821},{"style":610},[19822],{"type":26,"value":19823}," \\\n",{"type":21,"tag":399,"props":19825,"children":19826},{"class":605,"line":1646},[19827],{"type":21,"tag":399,"props":19828,"children":19829},{"style":610},[19830],{"type":26,"value":19831},"            backend.strategy.partial_from_session(partial)\n",{"type":21,"tag":399,"props":19833,"children":19834},{"class":605,"line":4599},[19835,19839,19844,19848],{"type":21,"tag":399,"props":19836,"children":19837},{"style":616},[19838],{"type":26,"value":15935},{"type":21,"tag":399,"props":19840,"children":19841},{"style":610},[19842],{"type":26,"value":19843}," backend_name ",{"type":21,"tag":399,"props":19845,"children":19846},{"style":616},[19847],{"type":26,"value":1070},{"type":21,"tag":399,"props":19849,"children":19850},{"style":610},[19851],{"type":26,"value":19852}," backend.name:\n",{"type":21,"tag":399,"props":19854,"children":19855},{"class":605,"line":4608},[19856,19861,19866],{"type":21,"tag":399,"props":19857,"children":19858},{"style":610},[19859],{"type":26,"value":19860},"            kwargs.setdefault(",{"type":21,"tag":399,"props":19862,"children":19863},{"style":644},[19864],{"type":26,"value":19865},"'pipeline_index'",{"type":21,"tag":399,"props":19867,"children":19868},{"style":610},[19869],{"type":26,"value":19870},", idx)\n",{"type":21,"tag":399,"props":19872,"children":19873},{"class":605,"line":4617},[19874,19879,19884],{"type":21,"tag":399,"props":19875,"children":19876},{"style":616},[19877],{"type":26,"value":19878},"            if",{"type":21,"tag":399,"props":19880,"children":19881},{"style":610},[19882],{"type":26,"value":19883}," user:  ",{"type":21,"tag":399,"props":19885,"children":19886},{"style":2818},[19887],{"type":26,"value":19888},"# don't update user if it's None\n",{"type":21,"tag":399,"props":19890,"children":19891},{"class":605,"line":4626},[19892,19897,19902],{"type":21,"tag":399,"props":19893,"children":19894},{"style":610},[19895],{"type":26,"value":19896},"                kwargs.setdefault(",{"type":21,"tag":399,"props":19898,"children":19899},{"style":644},[19900],{"type":26,"value":19901},"'user'",{"type":21,"tag":399,"props":19903,"children":19904},{"style":610},[19905],{"type":26,"value":19906},", user)\n",{"type":21,"tag":399,"props":19908,"children":19909},{"class":605,"line":4635},[19910,19914,19919],{"type":21,"tag":399,"props":19911,"children":19912},{"style":610},[19913],{"type":26,"value":19860},{"type":21,"tag":399,"props":19915,"children":19916},{"style":644},[19917],{"type":26,"value":19918},"'request'",{"type":21,"tag":399,"props":19920,"children":19921},{"style":610},[19922],{"type":26,"value":19923},", backend.strategy.request_data())\n",{"type":21,"tag":399,"props":19925,"children":19926},{"class":605,"line":4649},[19927],{"type":21,"tag":399,"props":19928,"children":19929},{"style":610},[19930],{"type":26,"value":19931},"            xkwargs.update(kwargs)\n",{"type":21,"tag":399,"props":19933,"children":19934},{"class":605,"line":4657},[19935,19939],{"type":21,"tag":399,"props":19936,"children":19937},{"style":616},[19938],{"type":26,"value":15970},{"type":21,"tag":399,"props":19940,"children":19941},{"style":610},[19942],{"type":26,"value":19943}," xargs, xkwargs\n",{"type":21,"tag":399,"props":19945,"children":19946},{"class":605,"line":4686},[19947,19951],{"type":21,"tag":399,"props":19948,"children":19949},{"style":616},[19950],{"type":26,"value":16754},{"type":21,"tag":399,"props":19952,"children":19953},{"style":610},[19954],{"type":26,"value":4683},{"type":21,"tag":399,"props":19956,"children":19957},{"class":605,"line":12002},[19958],{"type":21,"tag":399,"props":19959,"children":19960},{"style":610},[19961],{"type":26,"value":19962},"            backend.strategy.clean_partial_pipeline()\n",{"type":21,"tag":399,"props":19964,"children":19965},{"class":605,"line":15964},[19966,19971,19975],{"type":21,"tag":399,"props":19967,"children":19968},{"style":610},[19969],{"type":26,"value":19970},"utils.partial_pipeline_data ",{"type":21,"tag":399,"props":19972,"children":19973},{"style":616},[19974],{"type":26,"value":619},{"type":21,"tag":399,"props":19976,"children":19977},{"style":610},[19978],{"type":26,"value":19979}," partial_pipeline_data\n",{"type":21,"tag":399,"props":19981,"children":19982},{"class":605,"line":15977},[19983],{"type":21,"tag":399,"props":19984,"children":19985},{"style":610},[19986],{"type":26,"value":19987},"Viewport\n",{"type":21,"tag":399,"props":19989,"children":19990},{"class":605,"line":15985},[19991],{"type":21,"tag":399,"props":19992,"children":19993},{"style":610},[19994],{"type":26,"value":19995},"Window\n",{"type":21,"tag":190,"props":19997,"children":19999},{"id":19998},"step-4-relax-with-hot-beverage-of-choice",[20000],{"type":26,"value":20001},"Step 4 - Relax with hot beverage of choice",{"type":21,"tag":22,"props":20003,"children":20004},{},[20005],{"type":26,"value":20006},"Optional, but recommended.",{"type":21,"tag":2495,"props":20008,"children":20009},{},[20010],{"type":26,"value":2499},{"title":8,"searchDepth":232,"depth":232,"links":20012},[20013,20014,20017,20018,20019],{"id":17153,"depth":239,"text":17156},{"id":17209,"depth":239,"text":17212,"children":20015},[20016],{"id":18377,"depth":654,"text":18380},{"id":18383,"depth":239,"text":18386},{"id":19092,"depth":239,"text":19095},{"id":19998,"depth":239,"text":20001},"content:ckeefer:2015-3:EmailValidation.md","ckeefer/2015-3/EmailValidation.md","ckeefer/2015-3/EmailValidation",{"user":9465,"name":9466},1780330263861]