“农场”技术栈是什么?浅聊FARM Stack

news2025/1/2 4:35:54

介绍 FARM 堆栈 - FastAPI、React 和 MongoDB。

长按关注《Python学研大本营》,加入读者群,分享更多精彩 扫码关注《Python学研大本营》,加入读者群,分享更多精彩

当我获得第一份编程工作时,LAMP(Linux、Apache、MySQL、PHP)堆栈非常主流。我在工作中使用 WAMP,在家里使用 DAMP,并将我们的客户部署到 SAMP。

但是现在所有带有令人难忘的首字母缩略词的堆栈似乎都非常适合 JavaScript。MEAN(MongoDB、Express、Angular、Node.js)、MERN(MongoDB、Express、React、Node.js)、MEVN(MongoDB、Express、Vue、Node.js)、JAM(JavaScript、API、Markup)等。

尽管我很喜欢使用 React 和 Vue,但 Python 仍然是我最喜欢的用于构建后端 Web 服务的语言。我想要从 MERN 获得的相同好处——MongoDB、速度、灵活性、最少的样板——但使用 Python 而不是 Node.js。考虑到这一点,我想介绍FARM stack;FastAPI、React 和 MongoDB。

什么是 FastAPI?

FARM 堆栈在许多方面与 MERN 非常相似。我们保留了 MongoDB 和 React,但我们用 Python 和 FastAPI 替换了 Node.js 和 Express 后端。

FastAPI 是一个现代、高性能的 Python 3.6+ Web 框架 . 就 Web 框架而言,它是令人难以置信的新事物。我能找到的最早的 git commit 是从 2018 年 12 月 5 日开始的,但它是 Python 社区的一颗冉冉升起的新星。它已经被以下公司用于生产 微软、优步和 Netflix。

它速度迅速。基准测试表明它不如 golang 的 chi 或 fasthttp 快,但它比所有其他测试过的 Python 框架都快,并且也击败了大多数 Node.js 框架。

入门

如果您想尝试一下 FARM 堆栈,我已经创建了可以从 GitHub 克隆的示例 TODO 应用程序。

git clone git@github.com:mongodb-developer/FARM-Intro.git

代码被组织到两个目录中:后端和前端。后端代码是我们的 FastAPI 服务器。此目录中的代码与我们的 MongoDB 数据库交互,创建我们的 API 端点,并且感谢 OAS3(OpenAPI 规范 3)。它还生成我们的交互式文档。

运行 FastAPI 服务器

在我浏览代码之前,请尝试自己运行 FastAPI 服务器。您将需要 Python 3.8+ 和 MongoDB 数据库。一个 免费的 Atlas 集群会绰绰有余。 记下您的 MongoDB 用户名、密码和连接字符串,稍后您将需要它们。

安装依赖

cd FARM-Intro/backend
pip install -r requirements.txt

配置环境变量

export DEBUG_MODE=True
export DB_URL="mongodb+srv://<username>:<password>@<url>/<db>?retryWrites=true&w=majority"
export DB_NAME="farmstack"

安装和配置完所有内容后,您可以运行服务器python main.py并在您的浏览器中访问http://localhost:8000/docs。

这个交互式文档是由 FastAPI 自动为我们生成的,是在开发过程中尝试 API 的好方法。您可以看到我们涵盖了 CRUD 的主要元素。尝试添加、更新和删除一些任务,并探索从 FastAPI 服务器返回的响应。

创建 FastAPI 服务器

我们初始化服务器main.py;这是我们创建应用程序的地方。

app = FastAPI()

附加我们的路由或 API 端点。

app.include_router(todo_router, tags=["tasks"], prefix="/task")

启动异步事件循环和 ASGI 服务器。

if __name__ == "__main__":
    uvicorn.run(
        "main:app",
        host=settings.HOST,
        reload=settings.DEBUG_MODE,
        port=settings.PORT,
    )

它也是我们打开和关闭与 MongoDB 服务器的连接的地方。

@app.on_event("startup")
async def startup_db_client():
    app.mongodb_client = AsyncIOMotorClient(settings.DB_URL)
    app.mongodb = app.mongodb_client[settings.DB_NAME]


@app.on_event("shutdown")
async def shutdown_db_client():
    app.mongodb_client.close()

因为 FastAPI 是一个异步框架,所以我们使用 Motor 连接到我们的 MongoDB 服务器。Motor 是官方维护的 MongoDB 异步 Python 驱动程序 。

当应用程序启动事件被触发时,我打开一个到 MongoDB 的连接,并确保它可以通过应用程序对象访问,这样我以后可以在我的不同路由器中访问它。

定义模型

许多人认为 MongoDB 是无模式的,这是错误的。MongoDB 具有灵活的模式。也就是说,默认情况下集合不强制执行文档结构,因此您可以灵活地选择最符合您的应用程序及其性能要求的任何数据建模选择。因此,在使用 MongoDB 数据库时创建模型并不少见。 TODO 应用程序的模型在 中backend/apps/todo/models.py,正是这些模型帮助 FastAPI 创建交互式文档。

class TaskModel(BaseModel):
    id: str = Field(default_factory=uuid.uuid4, alias="_id")
    name: str = Field(...)
    completed: bool = False

    class Config:
        allow_population_by_field_name = True
        schema_extra = {
            "example": {
                "id": "00010203-0405-0607-0809-0a0b0c0d0e0f",
                "name": "My important task",
                "completed": True,
            }
        }

我想提请注意id这个模型的领域。MongoDB 使用_id,但在 Python 中,属性开头的下划线具有特殊含义。如果您的模型上有以下划线开头的属性,pydantic— FastAPI 使用的数据验证框架— 将假定它是一个私有变量,这意味着您将无法为其分配值!为了解决这个问题,我们为该字段命名,但id给它一个alias. _id您还需要在模型的类中设置allow_population_by_field_name为。True Config 你可能会注意到我没有使用 MongoDB 的 对象 ID . 你可以 将 ObjectIds 与 FastAPI 一起使用 ; 在序列化和反序列化期间需要做更多的工作。尽管如此,对于这个例子,我发现自己生成 UUID 更容易,所以它们总是字符串。

class UpdateTaskModel(BaseModel):
    name: Optional[str]
    completed: Optional[bool]

    class Config:
        schema_extra = {
            "example": {
                "name": "My important task",
                "completed": True,
            }
        }

当用户更新任务时,我们不希望他们改变 id,所以UpdateTaskModel只包括名称和完成的字段。我还将这两个字段设为可选,以便您可以独立更新它们中的任何一个。将它们都设为可选确实意味着所有字段都是可选的,这导致我花了太长时间来决定如何处理PUT用户未发送任何要更改的字段的请求(更新)。我们将在接下来查看路由器时看到这一点。

FastAPI 路由器

任务路由器位于backend/apps/todo/routers.py. 为了涵盖不同的 CRUD(创建、读取、更新和删除)操作,我需要以下端点:

  • POST /task/ - 创建一个新任务。

  • GET /task/ - 查看所有现有任务。

  • GET /task/{id}/ - 查看单个任务。

  • PUT /task/{id}/ - 更新一个任务。

  • DELETE /task/{id}/ - 删除一个任务。

CREATE

@router.post("/", response_description="Add new task")
async def create_task(request: Request, task: TaskModel = Body(...)):
    task = jsonable_encoder(task)
    new_task = await request.app.mongodb["tasks"].insert_one(task)
    created_task = await request.app.mongodb["tasks"].find_one(
        {"_id": new_task.inserted_id}
    )

    return JSONResponse(status_code=status.HTTP_201_CREATED, content=created_task)

create_task 路由器接受请求正文中的新任务数据作为 JSON 字符串。我们将此数据写入 MongoDB,然后以 HTTP 201 状态和新创建的任务进行响应。

READ

@router.get("/", response_description="List all tasks")
async def list_tasks(request: Request):
    tasks = []
    for doc in await request.app.mongodb["tasks"].find().to_list(length=100):
        tasks.append(doc)
    return tasks

list_tasks 路由器过于简单。在实际应用程序中,您至少需要包含分页。值得庆幸的是,有 FastAPI 的包可以简化这个过程

@router.get("/{id}", response_description="Get a single task")
async def show_task(id: str, request: Request):
    if (task := await request.app.mongodb["tasks"].find_one({"_id": id})) is not None:
        return task

    raise HTTPException(status_code=404, detail=f"Task {id} not found")

虽然 FastAPI 支持 Python 3.6+,但我在像这样的路由器中使用了赋值表达式,这就是为什么这个示例应用程序需要 Python 3.8+。 在这里,如果我们找不到具有正确 ID 的任务,我将提出异常。

UPDATE

@router.put("/{id}", response_description="Update a task")
async def update_task(id: str, request: Request, task: UpdateTaskModel = Body(...)):
    task = {k: v for k, v in task.dict().items() if v is not None}

    if len(task) >= 1:
        update_result = await request.app.mongodb["tasks"].update_one(
            {"_id": id}, {"$set": task}
        )

        if update_result.modified_count == 1:
            if (
                updated_task := await request.app.mongodb["tasks"].find_one({"_id": id})
            ) is not None:
                return updated_task

    if (
        existing_task := await request.app.mongodb["tasks"].find_one({"_id": id})
    ) is not None:
        return existing_task

    raise HTTPException(status_code=404, detail=f"Task {id} not found")

我们不想将任何字段更新为空值,因此首先,我们从更新文档中删除这些字段。如上所述,由于所有值都是可选的,因此带有空负载的更新请求仍然有效。经过深思熟虑,我决定在这种情况下,API 要做的正确事情是返回未修改的任务和 HTTP 200 状态。 如果用户提供了一个或多个要更新的字段,我们会在返回修改后的文档之前尝试$set使用新值update_one。但是,如果我们找不到具有指定 id 的文档,我们的路由器将引发 404。

DELETE

@router.delete("/{id}", response_description="Delete Task")
async def delete_task(id: str, request: Request):
    delete_result = await request.app.mongodb["tasks"].delete_one({"_id": id})

    if delete_result.deleted_count == 1:
        return JSONResponse(status_code=status.HTTP_204_NO_CONTENT)

    raise HTTPException(status_code=404, detail=f"Task {id} not found")

最终路由器不会在成功时返回响应正文,因为请求的文档不再存在,因为我们刚刚删除了它。相反,它返回 204 的 HTTP 状态,这意味着请求已成功完成,但服务器没有任何数据可提供给您。

React 前端

React 前端没有改变,因为它只使用 API,因此在某种程度上与后端无关。它主要是由create-react-app. 因此,要启动我们的 React 前端,请打开一个新的终端窗口——让您的 FastAPI 服务器在现有终端中运行——然后在前端目录中输入以下命令。

npm install
npm start

这些命令可能需要一点时间才能完成,但之后,它应该会打开一个新的浏览器窗口http://localhost:3000.

React 前端只是我们任务列表的一个视图,但您可以通过 FastAPI 文档更新您的任务,并查看 React 中出现的更改!

我们的大部分前端代码都在frontend/src/App.js

useEffect(() => {
    const fetchAllTasks = async () => {
        const response = await fetch("/task/")
        const fetchedTasks = await response.json()
        setTasks(fetchedTasks)
    }

    const interval = setInterval(fetchAllTasks, 1000)

    return () => {
        clearInterval(interval)
    }
}, [])

当我们的组件挂载时,我们开始一个间隔,它每秒运行一次,并在将它们存储到我们的状态之前获取最新的任务列表。每当组件卸载时,挂钩末尾返回的函数将运行,清理我们的间隔。

useEffect(() => {
    const timelineItems = tasks.reverse().map((task) => {
        return task.completed ? (
            <Timeline.Item
                dot={<CheckCircleOutlined />}
                color="green"
                style={{ textDecoration: "line-through", color: "green" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        ) : (
            <Timeline.Item
                dot={<MinusCircleOutlined />}
                color="blue"
                style={{ textDecoration: "initial" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        )
    })

    setTimeline(timelineItems)
}, [tasks])
    const timelineItems = tasks.reverse().map((task) => {
        return task.completed ? (
            <Timeline.Item
                dot={<CheckCircleOutlined />}
                color="green"
                style={{ textDecoration: "line-through", color: "green" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        ) : (
            <Timeline.Item
                dot={<MinusCircleOutlined />}
                color="blue"
                style={{ textDecoration: "initial" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        )
    })

    setTimeline(timelineItems)
}, [tasks])
```useEffec
    const timelineItems = tasks.reverse().map((task) => {
        return task.completed ? (
            <Timeline.Item
                dot={<CheckCircleOutlined />}
                color="green"
                style={{ textDecoration: "line-through", color: "green" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        ) : (
            <Timeline.Item
                dot={<MinusCircleOutlined />}
                color="blue"
                style={{ textDecoration: "initial" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        )
    })

    setTimeline(timelineItems)
}, [tasks])useEffect(() => {
    const timelineItems = tasks.reverse().map((task) => {
        return task.completed ? (
            <Timeline.Item
                dot={<CheckCircleOutlined />}
                color="green"
                style={{ textDecoration: "line-through", color: "green" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        ) : (
            <Timeline.Item
                dot={<MinusCircleOutlined />}
                color="blue"
                style={{ textDecoration: "initial" }}
            >
                {task.name} <small>({task._id})</small>
            </Timeline.Item>
        )
    })

    setTimeline(timelineItems)
}, [tasks])

每当我们状态中的任务列表发生变化时,都会触发第二个钩子。这个钩子为我们列表中的每个任务创建一个Timeline Item组件。

<>
    <Row style={{ marginTop: 50 }}>
        <Col span={14} offset={5}>
            <Timeline mode="alternate">{timeline}</Timeline>
        </Col>
    </Row>
</>

最后一部分App.js是将任务呈现到页面的标记。如果您以前使用过 MERN 或其他 React 堆栈,这可能看起来非常熟悉。

总结

希望大家能够使用本文介绍的技术构建高性能、异步的 Web 应用程序!

推荐书单

《Pandas1.x实例精解》

本书详细阐述了与Pandas相关的基本解决方案,主要包括Pandas基础,DataFrame基本操作,创建和保留DataFrame,开始数据分析,探索性数据分析,选择数据子集,过滤行,对齐索引,分组以进行聚合、过滤和转换,将数据重组为规整形式,组合Pandas对象,时间序列分析,使用Matplotlib、Pandas和Seaborn进行可视化,调试和测试等内容。此外,本书还提供了相应的示例、代码,以帮助读者进一步理解相关方案的实现过程。 本书适合作为高等院校计算机及相关专业的教材和教学参考书,也可作为相关开发人员的自学用书和参考手册。

链接:https://u.jd.com/UKjx4et

精彩回顾

《Pandas1.x实例精解》新书抢先看!

【第1篇】利用Pandas操作DataFrame的列与行

【第2篇】Pandas如何对DataFrame排序和统计

【第3篇】Pandas如何使用DataFrame方法链

【第4篇】Pandas如何比较缺失值以及转置方向?

【第5篇】DataFrame如何玩转多样性数据

【第6篇】如何进行探索性数据分析?

【第7篇】使用Pandas处理分类数据

【第8篇】使用Pandas处理连续数据

【第9篇】使用Pandas比较连续值和连续列

【第10篇】如何比较分类值以及使用Pandas分析库

长按关注《Python学研大本营》

长按二维码,加入Python读者群

扫码关注《Python学研大本营》,加入读者群,分享更多精彩

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/21849.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[附源码]java毕业设计万科电子商城

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

【毕业设计】22-基于单片机的智能温度计的系统设计(原理图工程+仿真工程+源代码+仿真视频+答辩论文+答辩PPT)

【毕业设计】22-基于单片机的智能温度计的系统设计&#xff08;原理图工程仿真工程源代码仿真视频答辩论文答辩PPT&#xff09;[toc] 资料下载链接 资料下载链接 资料链接&#xff1a;https://www.cirmall.com/circuit/28616/ 包含此题目毕业设计全套资料&#xff1a; 基于单…

029-Swing实现简单计算器功能

https://blog.csdn.net/software7503/article/details/127952712https://blog.csdn.net/software7503/article/details/127952712上一讲:028-GUI事件处理,ActionListener事件,MouseListener事件 下一讲:030-用Swing组件及Action事件实现登录功能_CS

UNCTF2022 writeup

题量太多了&#xff0c;比赛结束之后又要做一遍… 注&#xff1a;最后给出的均为题目解出的flag&#xff0c;提交时需将格式修改为UNCTF{} 文章目录Web我太喜欢bilibili大学啦ezgame签到babyphpeasy_upload给你一刀我太喜欢bilibili大学啦修复版302与深大随便注PwnwelcomeUNCT…

企业日志分析ELK(Logstash+Elasticsearch+Kibana)介绍及搭建

一、ELK概述 1、ELK日志分析系统 ELK平台是一套完整的日志集中处理解决方案&#xff0c;将 ElasticSearch、Logstash 和 Kiabana 三个开源工具配合使用&#xff0c; 完成更强大的用户对日志的查询、排序、统计需求。 ElasticSearch&#xff1a; ElasticSearch&#xff1a;是…

Linux:进程(一)

文章目录前言一、进程是什么二、描述进程--PCB三、查看进程四、通过系统调用获取进程标示符五、通过系统调用创建进程-fork初识六、进程状态七、进程状态查看八、僵尸进程&#xff08;Z&#xff1a;zombie&#xff09;1.是什么2.为什么3.怎么避免九、孤儿进程十、进程优先级十一…

内蒙古简易医院企业网设计与规划

目 录 摘 要 1 Abstract 2 第1章 绪论 5 1.1 背景及意义 5 1.2 国内外研究现状 6 1.2.1 国外研究现状 7 1.2.2 国内研究现状 7 1.3研究内容 8 第2章 医院企业网需求分析 9 2.1医院基本情况 9 2.1.1基本情况 9 2.1.2建筑楼群及信息点分布图 9 2.2需求需求 10 2.2.1管理需求 10 2…

Matlab下载安装详细教程

下载链接&#xff1a;https://pan.baidu.com/s/19JbPP2hWlZraVbLuNlHpcg 提取码&#xff1a;6666 下载网盘链接是公众号“电脑DIY圈”里的分享&#xff0c;不是博主自己的&#xff0c;另外此安装教程同样来自电脑DIY圈公众号&#xff0c;博主仅做整理&#xff0c;以便日后需要 …

图 知识点总结(王道)

图的定义 图G由顶点集V和边集E组成&#xff0c;记为G&#xff08;V,E&#xff09;&#xff0c;其中V&#xff08;G&#xff09;表示图G中顶点的有限非空集&#xff1b;E(G)表示图G中顶点之间的关系&#xff08;边&#xff09;集合。若V{v1&#xff0c;v2vn}&#xff0c;则用|V…

asp开发的人脸识别:人脸照片+身份证号+姓名,核验实人认证

今天接到一个客户需求&#xff0c;要求用asp开发人脸识别功能&#xff0c;主要用于网站上用户的实人核验&#xff0c;用户上传照片后&#xff0c;通过照片姓名身份证号码&#xff0c;核验是不是一个人&#xff0c;判断用户的真实性。asp写这个其实很简单&#xff0c;经过一个小…

黑*头条_第7章_kafka实战应用文章自动审核

黑*头条_第7章_kafka实战应用&文章自动审核 文章目录黑*头条_第7章_kafka实战应用&文章自动审核kafka实战应用&文章自动审核今日目标1 kafka封装1.1 功能需求1.2 定义1.2.1 约束定义1.3 实现设计1.4 开发实现1.4.1 配置文件1.4.2 KafkaMessage1.4.3 KafkaListener1…

SpringMVC ---- HttpMessageConverter

SpringMVC ---- HttpMessageConverter1. RequestBody2. RequestEntity3. ResponseBody4. SpringMVC处理json5. SpringMVC处理ajax6. RestController注解7. ResponseEntityHttpMessageConverter&#xff0c;报文信息转换器&#xff0c;将请求报文转换为 Java 对象&#xff0c;或…

[牛客top101]详解01,02,反转链表问题

文章目录前言1. 整体翻转链表1.1 题目描述1.2 题目详解2. 翻转链表的部分区间2.1 题目描述2.2 题目详解3. 完整代码展示前言 从本章开始,我们就开始刷题旅程啦,路上必定问题多多,但还是得练呐!所以,就现在,开始啦! 1. 整体翻转链表 1.1 题目描述 给定一个单链表的头结点pHea…

(续)SSM整合之springmvc笔记(拦截器)(P164-168)

目录 一 准备工作 1. 创建spring_mvc_extension com.atguigu 2 .导入依赖 3. 添加web模块 4 .web.xml 5 . springmvc.xml 6 index.html 7 控制层 8 success.html 9 .添加到tomcat ​10 测试 二 . 测试拦截器 1 . index.html 2 . FirstInterceptor …

Docker概念及安装

一、Docker概述 1 IT架构的演进&#xff1a; 裸金属 → 虚拟机 → 容器→ 函数化、代码化 云计算涌现出很多改变传统IT架构和运维方式的新技术&#xff0c;比如虚拟机、容器、微服务、Serverless&#xff08;无服务&#xff09;&#xff0c;无论这些技术应用在哪些场景&…

智慧能源解决方案-最新全套文件

智慧能源解决方案-最新全套文件一、建设背景存在的问题二、建设架构三、建设方案四、获取 - 智慧能源全套最新解决方案合集一、建设背景 我国工业能耗占全国总能耗比例近70%&#xff0c;许多经济大省工业能耗占比甚至显著高于70%&#xff0c;工业企业能耗增速也明显领先全国其…

Bash脚本debug攻略

初学Bash时, 我从未想过去debug Bash脚本, 也从未想过Bash脚本也能debug. 随着技术的增长, 写的脚本越来越复杂, 使用echo打印日志来调试脚本的方式也越来越捉襟见肘了. 直到某天 通读了一遍Bash Reference Manual, 才发现Bash脚本也是可以debug的. 下面就介绍三种debug Bash脚…

定时器之编码器模式

1.什么是编码器 编码器&#xff08;encoder&#xff09;是将信号或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。编码器把角位移或直线位移转换成电信号&#xff0c;前者称为码盘&#xff0c;后者称为码尺。 2.分类 按照读出方式编码器可以分为接触式和非接…

C++学习笔记(面向对象部分开始6500字复习总结)

函数重载 1.对象属性 对象方法 和 类属性 类方法 2.const函数read only&#xff0c;不会修改任何数据 3.class默认private&#xff0c;struct默认public 4.&#xff01;&#xff01;&#xff01;&#xff01;C编译器优化&#xff01;&#xff01;&#xff01;《个人理解》当…

单细胞分析:数据整合(九)

导读 本文将学习跨条件执行单细胞整合&#xff0c;以识别彼此相似的细胞。 1. 目标 跨条件对齐相同的细胞类型。2. 挑战 对齐相似细胞类型的细胞&#xff0c;这样就不会因为样本、条件、模式或批次之间的差异而在后续分析中进行聚类。 3. 推荐 建议先不整合分析&#xff0c;再决…