{"id":509778,"date":"2024-09-19T09:59:08","date_gmt":"2024-09-19T08:59:08","guid":{"rendered":"https:\/\/blog.jetbrains.com\/?post_type=pycharm&#038;p=509778"},"modified":"2026-03-11T15:57:19","modified_gmt":"2026-03-11T14:57:19","slug":"how-to-use-fastapi-for-machine-learning","status":"publish","type":"pycharm","link":"https:\/\/blog.jetbrains.com\/es\/pycharm\/2024\/09\/how-to-use-fastapi-for-machine-learning","title":{"rendered":"How to Use FastAPI for Machine Learning"},"content":{"rendered":"\n<figure class=\"wp-block-image size-full\"><img decoding=\"async\" loading=\"lazy\" width=\"2560\" height=\"1440\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/09\/pc-featured_blog_1280x720_en-1.png\" alt=\"How to Use FastAPI for Machine Learning\" class=\"wp-image-509816\"\/><\/figure>\n\n\n\n<p>FastAPI provides a quick way to build a backend service with Python. With a few decorators, you can turn your Python function into an API application. <\/p>\n\n\n\n<p>It is widely used by many companies including Microsoft, Uber, and Netflix. According to the <a href=\"https:\/\/lp.jetbrains.com\/python-developers-survey-2023\/\" target=\"_blank\" rel=\"noopener\">Python Developers Survey<\/a>, FastAPI usage has grown from 21% in 2021 to 29% in 2023. For data scientists, it&#8217;s the second most popular framework, with 31% using it. <\/p>\n\n\n\n<p>In this blog post, we will cover the basics of FastAPI for data scientists who may want to build a quick prototype for their project.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What is FastAPI?<\/h2>\n\n\n\n<p>FastAPI is a popular web framework for building APIs with Python, based on standard Python type hints. It is intuitive and easy to use, and it can provide a production-ready application in a short period of time. It is fully compatible with <a href=\"https:\/\/github.com\/OAI\/OpenAPI-Specification\" target=\"_blank\" rel=\"noopener\">OpenAPI<\/a> and <a href=\"https:\/\/json-schema.org\/\" target=\"_blank\" rel=\"noopener\">JSON Schema<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Why use FastAPI for machine learning?<\/h2>\n\n\n\n<p>Most teams working on machine learning projects consist of data scientists whose domains and professions lie on the statistics side of things. They may not have experience developing software or applications to ship their machine learning projects. FastAPI enables data scientists to easily create APIs for the following projects:<\/p>\n\n\n\n<p><strong>Deploying prediction models<\/strong><\/p>\n\n\n\n<p>The data science team may have trained a model for the prediction of the sales demand in a warehouse. To make it useful, they have to provide an API interface so other parts of the stock management system can use this new prediction functionality.<\/p>\n\n\n\n<p><strong>Suggestion engines<\/strong><\/p>\n\n\n\n<p>One of the very common uses of machine learning is as a system that provides suggestions based on the users&#8217; choices. For example, if someone puts certain products in their shopping cart, more items can be suggested to that user. Such an e-commerce system requires an API call to the suggestion engine that takes input parameters.<\/p>\n\n\n\n<p><strong>Dynamic dashboards and reporting systems<\/strong><\/p>\n\n\n\n<p>Sometimes, reports for data science projects need to be presented as dashboards so users can inspect the results themselves. One possible approach is to have the data model provide an API. Frontend developers can use this API to create applications that allow users to interact with the data.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Advantages of using FastAPI<\/h2>\n\n\n\n<p>Compared to other <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2025\/02\/django-flask-fastapi\/\">Python web frameworks<\/a>, FastAPI is simple yet fully functional. Mainly using decorators and type hints, it allows you to build a web application without the complexity of building a whole ORM (<a href=\"https:\/\/en.wikipedia.org\/wiki\/Object-relational_mapping\" target=\"_blank\" rel=\"noopener\">object-relational mapping<\/a>) model and with the <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/sql-databases\/\" target=\"_blank\" rel=\"noopener\">flexibility of using any database<\/a>, including any SQL and NoSQL databases. FastAPI also provides automatic documentation generation, support for additional information and validation for query parameters, and good async support.<\/p>\n\n\n\n<p><strong>Fast development<\/strong><\/p>\n\n\n\n<p>Creating API calls in FastAPI is as easy as adding decorators in the Python code. Little to no backend experience is needed for anyone who wants to turn a Python function into an application that will respond to API calls.<\/p>\n\n\n\n<p><strong>Fast documentation<\/strong><\/p>\n\n\n\n<p>FastAPI provides automatic interactive API documentation using <a href=\"https:\/\/github.com\/swagger-api\/swagger-ui\" target=\"_blank\" rel=\"noopener\">Swagger UI<\/a>, which is an industry standard. No extra effort is required to build clear documentation with API call examples. This creates an advantage for busy data science teams who may not have the energy and expertise to write technical specifications and documentation.<\/p>\n\n\n\n<p><strong>Easy testing<\/strong><\/p>\n\n\n\n<p>Writing tests is one of the most important steps in software development, but it can also be one of the most tedious, especially when the time of the data science team is valuable. Testing FastAPI is made simple thanks to <a href=\"https:\/\/www.starlette.io\/testclient\/\" target=\"_blank\" rel=\"noopener\">Starlette<\/a> and <a href=\"https:\/\/www.python-httpx.org\/\" target=\"_blank\" rel=\"noopener\">HTTPX<\/a>. Most of the time no monkey patching is needed and tests are easy to write and understand.<\/p>\n\n\n\n<p><strong>Fast deployment<\/strong><\/p>\n\n\n\n<p>FastAPI comes with a <a href=\"https:\/\/fastapi.tiangolo.com\/fastapi-cli\/\" target=\"_blank\" rel=\"noopener\">CLI tool<\/a> that can bridge development and <a href=\"https:\/\/fastapi.tiangolo.com\/deployment\/\" data-type=\"link\" data-id=\"https:\/\/fastapi.tiangolo.com\/deployment\/\" target=\"_blank\" rel=\"noopener\">deployment<\/a> smoothly. It allows you to switch between development mode and production mode easily. Once development is completed, the code can be easily deployed using a <a href=\"https:\/\/fastapi.tiangolo.com\/deployment\/docker\/\" target=\"_blank\" rel=\"noopener\">Docker container<\/a> with images that have Python prebuilt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to use FastAPI for a machine learning project<\/h2>\n\n\n\n<p>In this example, we will turn a classification prediction model that uses the <a href=\"https:\/\/scikit-learn.org\/stable\/modules\/neighbors.html\" target=\"_blank\" rel=\"noopener\">Nearest Neighbors algorithm<\/a> to predict the species of various penguins based on their bill and flipper length into a backend application. We will provide an API that takes parameters from the query parameters of a URL and gives back the prediction. This shows how a prototype can be made quickly by any data scientist with no backend development experience.<\/p>\n\n\n\n<p>We will use a simple `<a href=\"https:\/\/scikit-learn.org\/stable\/modules\/generated\/sklearn.neighbors.KNeighborsClassifier.html\" target=\"_blank\" rel=\"noopener\">KNeighborsClassifier<\/a>` on the <a href=\"https:\/\/www.kaggle.com\/code\/parulpandey\/penguin-dataset-the-new-iris\" target=\"_blank\" rel=\"noopener\">penguin data set<\/a> as an example. Details of how to build the model will be omitted, but feel free to check out the <a href=\"https:\/\/github.com\/Cheukting\/FastAPI-ml-demo\/blob\/main\/penguins_predict.ipynb\" target=\"_blank\" rel=\"noopener\">relevant notebook here<\/a>. In the following tutorial, we will focus on the usage of FastAPI and explain some fundamental concepts. We will be building a prototype to do so.&nbsp;<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>1. Start a FastAPI project with PyCharm<br><\/strong><\/h3>\n\n\n\n<p>In this blog post, we will be using PyCharm Professional 2024.1. The best way to start using FastAPI is to <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/fastapi-project.html\" target=\"_blank\" rel=\"noopener\">create a FastAPI project with PyCharm<\/a>. When you click <em>New Project<\/em> in PyCharm, you will be presented with a large selection of projects to choose from. Select the <em>FastAPI<\/em> tab:<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"505\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXcXg0ydrDqTUAn3ymmERQmAoV_qLFJ05payAjEsBkGx6b74YxGu9gvP26Qo_y0wW0GXCUJ1DpwkkEse5bTuKZMMsYnHnquO4NU8bKk_DbP0giqKvZjKHJF9re7DKIjgzwENo7s_LV40IMd3_KRzt99heu2r.png\"><br><br>From here, you can put in the name of your project and take advantage of other options such as initializing Git and the virtual environment that you want to use.<br><br>After doing so, you will see the basic structure of a FastAPI project set up for you.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"369\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXeSOPOjjaQNOFGvlFgvrv_e434hpL8nCTtgzU3H8cd_eTbOhKTbhwWqdztL88hGdEH-1_atdJcAKUQNzC2XZaTUrGkNsrW-uVnFWjpr2FzLHLxG2ZJp9rBQM-pqEPqgHwh_37km9svOyCifpf95LR19ss4q.png\"><br><br>There is also a `<code>test_main.http<\/code>` file set up for you to quickly test all the endpoints.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"328\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXeIcvsbT4t0rpVnH-9LWY6hpxXR5cyFIv3xIumNXQKV8jSbv_Cc9b7F8ce1vetA7bwkAhzuygPeIu_1UhQITIbnm347gUudXrh_pXp-s49mP4aqfMDDt2Bc600Nk89YfsyvbmDl-dLgAu3w9Qc-GS0mHWM.png\"><br><\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>2. Set up environment dependencies<\/strong><\/h3>\n\n\n\n<p>Next, set up our environment dependency with `<code>requirements.txt<\/code>` by selecting <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/managing-dependencies.html\" target=\"_blank\" rel=\"noopener\">\u200b\u200b<em>Sync Python Requirements<\/em><\/a> under PyCarm\u2019s <em>Tool<\/em> menu.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"314\" height=\"542\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXccO5ZpgBQ8xj8SGza3OR7E61Qg_8Y1MBNlW2cYwyhzmtkFvN6kyg0cKTXQF6ofyXk8JaS7_Gk3018gyhT-zik9cBX2VLtXernqyGHIloJM41avMZLH75siACXXzzjMCS9BRwGDxEPYDCUF4pGgJI7m6fyR.png\"><br><br>Then you can select the `<code>requirements.txt<\/code>` file to be used.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"455\" height=\"262\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXef3bAg0oWF8B26HpHgBfbbpO7VXV79OuDEENpHPyEmhmYmW2CSdDRlQV4y-9496Y0hu9OQXSeZWVCIoU2ZNyUCX9wrsybBoarI5YVl79SKgcW5oCSSp0RiVEtD6ld1APRae-jsC0gFT2DYvUrjdMZCqqGr.png\"><br><br>You can copy and use this `<a href=\"https:\/\/github.com\/Cheukting\/FastAPI-ml-demo\/blob\/main\/requirements.txt\" target=\"_blank\" rel=\"noopener\">requirements.txt<\/a>` file. We will be using pandas and scikit-learn for the machine learning part of the project. Also, add the `<a href=\"https:\/\/github.com\/Cheukting\/FastAPI-ml-demo\/blob\/main\/penguins.csv\" target=\"_blank\" rel=\"noopener\">penguins.csv<\/a>` file to your project directory.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>3. Set up your machine learning model<\/strong><\/h3>\n\n\n\n<p>Arrange your machine learning code in the `<code>main.py<\/code>` file. We will start with a script that trains our model:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">import pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn import preprocessing\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.preprocessing import StandardScaler\n\ndata = pd.read_csv('penguins.csv')\ndata = data.dropna()\n\nle = preprocessing.LabelEncoder()\nX = data[[\"bill_length_mm\", \"flipper_length_mm\"]]\nle.fit(data[\"species\"])\ny = le.transform(data[\"species\"])\nX_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0)\nclf = Pipeline(\n    steps=[(\"scaler\", StandardScaler()), (\"knn\", KNeighborsClassifier(n_neighbors=11))]\n)\nclf.set_params().fit(X_train, y_train)<\/pre>\n\n\n\n<p>We can place the above code after `<code>app = FastAPI()<\/code>`. All of it will be run when we start the application.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"411\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXfCiG8OY_xW5ITZwfPYCjh-IVbcM9i2T5fj12uiIi8OqxbyaDUsokGeYa1tMswK13Sae7_flr4ExHo12NOk_mTBM9o8f_eRUwTxjozoo8UEhFQY7F4OSvDwuStgbz03nNa6UKhUVBOuQ5ol9CwoEWKqnmNc.png\"><br><br>However, there is a better way to run the start-up code we used to set up our model. We will cover that <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2024\/09\/how-to-use-fastapi-for-machine-learning\/#setup\" data-type=\"link\" data-id=\"https:\/\/blog.jetbrains.com\/pycharm\/2024\/09\/how-to-use-fastapi-for-machine-learning\/#setup\">in a later part of the blog post<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>4. Request a response<\/strong><\/h3>\n\n\n\n<p>Next we will look at how to add our model to FastAPI functionality. As a first step, we will add a response to the root of the URL and just simply return a message about our model in JSON format. Change the code in `<code>async def root():<\/code>` from \u201cHello world\u201d to our message like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@app.get(\"\/\")\nasync def root():\n    return {\n        \"Name\": \"Penguins Prediction\",\n        \"description\": \"This is a penguins prediction model based on the bill length and flipper length of the bird.\",\n    }<\/pre>\n\n\n\n<p>Now, test our application. First, we will start our application, which is easy in PyCharm. Just press the arrow button (<img decoding=\"async\" loading=\"lazy\" width=\"16\" height=\"16\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXc0LiYi_rXCHDApg8w6BegZcjsWLgehi6PBIrRpJ4zPftksMIzCfs47_pI-vZrweWVcIiRJdUpabGzit3V0CRAG5ie5y-rbViIhChPVFFhFrxiyuCjTD3NGx7RqH-U2od_7ZZHFX2EoW4AM4Ubt2J3qBYvd.png\">) next to your project name at the top.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"187\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXeKzc7TFPb_VNjfob2Lk7EnsjbiCa4-PUnTlkSQ0YDU3z1uJL9CCq-_gY8F6awyFCCrmW3XNkWqM5iWHIAfRF64ZXUzG6nNfB4eMF72pHKpwrHa9MvrbQ59BfVpcE0IZKB4UPpDU9i3WCyOe3myK6AqQ_ET.jpg\"><br><br>If you are using the default settings, your application will run on <a href=\"http:\/\/127.0.0.1:8000\" target=\"_blank\">http:\/\/127.0.0.1:8000<\/a>. You can double-check that by looking at the prompt from the <em>Run <\/em>window.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"191\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXecnLB8N5ac5zTUWtyLook3mTf_gXUyI8xv_-XjxBObQTitn3e19b1dROsT9bIbQOtKoOvwsMRC3HrTMuZenT98xabZpjv8euX2fYOi2U9cRJahqt69AK7WoVJLLg8nM3QRb4HzMzDYA8ldn1NTXvzoS_VN.png\"><\/p>\n\n\n\n<p>Once the process has started, let\u2019s go to `<code>test_main.http<\/code>` and press the first arrow button (<img decoding=\"async\" loading=\"lazy\" width=\"16\" height=\"16\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXc0LiYi_rXCHDApg8w6BegZcjsWLgehi6PBIrRpJ4zPftksMIzCfs47_pI-vZrweWVcIiRJdUpabGzit3V0CRAG5ie5y-rbViIhChPVFFhFrxiyuCjTD3NGx7RqH-U2od_7ZZHFX2EoW4AM4Ubt2J3qBYvd.png\">) next to `<code>GET<\/code>`. From the <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/http-client-in-product-code-editor.html\" target=\"_blank\" rel=\"noopener\">HTTP Client<\/a> in the <em>Services <\/em>window, you will see the response message that we put in.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"147\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXceMBLb9PaWs9fcHiWZlvaPrjP-4I1EgzTlSMhBaz6iLhpcj59PVvPR3KdD34UYVyxXZZHtg6bVUERP3P9jSneVFHKt3-dtjJL18bezSLMFn5fw5JavFmtzFKmxD534m0SPH5b-nNTBcu4OuFi_v8zaK7rg.png\"><br><br>The response JSON file is also saved for future inspection.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><strong>5. Request with query parameters<\/strong><\/h3>\n\n\n\n<p>Next, we would like to let users make predictions by providing <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/query-params\/\" target=\"_blank\" rel=\"noopener\">query parameters<\/a> in the URL. Let\u2019s add the code below after the `<code>root<\/code>` function.<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@app.get(\"\/predict\/\")\nasync def predict(bill_length_mm: float = 0.0, flipper_length_mm: float = 0.0):\n    param = {\n                \"bill_length_mm\": bill_length_mm,\n                \"flipper_length_mm\": flipper_length_mm\n            }\n    if bill_length_mm &lt;=0.0 or flipper_length_mm &lt;=0.0:\n        return {\n            \"parameters\": param,\n            \"error message\": \"Invalid input values\",\n        }\n    else:\n        result = clf.predict([[bill_length_mm, flipper_length_mm]])\n        return {\n            \"parameters\": param,\n            \"result\": le.inverse_transform(result)[0],\n        }<\/pre>\n\n\n\n<p>Here we set the default value of the `<code>bill_length_mm<\/code>` and `<code>flipper_length_mm<\/code>` to be 0 if the user didn\u2019t input a value. We also add a check to see if either of the values is 0 and return an error message instead of trying to predict which penguin the input refers to.<br><br>If the inputs are not 0, we will use the model to make a prediction and use the encoder to do an inverse transformation to get the label of the predicted target, i.e. the name of the penguin species.<br><br>This is not the only way you can verify inputs. You can also consider using <a href=\"https:\/\/docs.pydantic.dev\/latest\/\" target=\"_blank\" rel=\"noopener\">Pydantic<\/a> for input verification.<\/p>\n\n\n\n<p>If you are using the same version of FastAPI as stated in `<code>requirements.txt<\/code>`, FastAPI automatically refreshes the service and applies changes on save. Now put in a new URL in `<code>test_main.http<\/code>` to test (separated from the URL before with ###):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">###\n\nGET http:\/\/127.0.0.1:8000\/predict\/?bill_length_mm=40.3&amp;flipper_length_mm=195\nAccept: application\/json<\/pre>\n\n\n\n<p>Press the arrow button (<img decoding=\"async\" loading=\"lazy\" width=\"16\" height=\"16\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXc0LiYi_rXCHDApg8w6BegZcjsWLgehi6PBIrRpJ4zPftksMIzCfs47_pI-vZrweWVcIiRJdUpabGzit3V0CRAG5ie5y-rbViIhChPVFFhFrxiyuCjTD3NGx7RqH-U2od_7ZZHFX2EoW4AM4Ubt2J3qBYvd.png\">) next to our new URL and see the output.<br><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"188\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXeYkMnT_haEQMxSRk_h4PkmwJAlZR2IVvMMOBx7UvTaNWQtN8wq-RapQj3XOXDvtsVYZFjIH7byKvZBU-eDnmAfaO40Va7GzK64aROh88YC71llKuUJKmzT_O44bwjjIB3uGkAuUBSDbwzAVu0VUEEJ9ob0.png\"><br><br>Next you can try a URL with one or both of the parameters removed to see the error message:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">###\n\nGET http:\/\/127.0.0.1:8000\/predict\/?bill_length_mm=40.3\nAccept: application\/json<\/pre>\n\n\n\n<p><br><img decoding=\"async\" loading=\"lazy\" width=\"624\" height=\"215\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2025\/09\/AD_4nXdjD96fac_PpLDGlSQ87cC72oVVDuPprpQ-86ptWUYiprblyLTxAYrxY0cq22N90p0Foohp4hQNFsznzl701MHnuabW4SHpoVSbHWjCTfKtZCRvwPY7tK1fWoutWSYnYpzCaUkpaO5Gd4dbSP2DwtvrIl4I.png\"><\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"setup\"><strong>6. Set up a machine learning model with lifespan events<\/strong><\/h3>\n\n\n\n<p>Last, let\u2019s look at how we can set up our model with <a href=\"https:\/\/fastapi.tiangolo.com\/advanced\/events\/\" target=\"_blank\" rel=\"noopener\">FastAPI lifespan events<\/a>. The advantage of doing that is we can make sure no request will be accepted while the model is still being set up and the memory used will be cleaned up afterward. To do that, we will use an `<a href=\"https:\/\/docs.python.org\/3\/library\/contextlib.html#contextlib.asynccontextmanager\" target=\"_blank\" rel=\"noopener\">asynccontextmanager<\/a>`. Before `<code>app = FastAPI()<\/code>` we will add:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from contextlib import asynccontextmanager\n\nml_models = {}\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Set up the ML model here\n    \n    yield\n    # Clean up the models and release resources\n\n\n    ml_models.clear()<\/pre>\n\n\n\n<p>Now we will move the import of pandas and scikit-learn to be alongside the other imports. We will also move our setup code inside the `<code>lifespan<\/code>` function, setting the machine learning model and <em>LabelEncoder<\/em> inside `<code>ml_models<\/code>` like this:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from fastapi import FastAPI\nfrom contextlib import asynccontextmanager\n\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn import preprocessing\nfrom sklearn.neighbors import KNeighborsClassifier\nfrom sklearn.pipeline import Pipeline\nfrom sklearn.preprocessing import StandardScaler\n\nml_models = {}\n\n@asynccontextmanager\nasync def lifespan(app: FastAPI):\n    # Set up the ML model here\n\n    data = pd.read_csv('penguins.csv')\n    data = data.dropna()\n\n    le = preprocessing.LabelEncoder()\n    X = data[[\"bill_length_mm\", \"flipper_length_mm\"]]\n    le.fit(data[\"species\"])\n    y = le.transform(data[\"species\"])\n    X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0)\n    clf = Pipeline(\n        steps=[(\"scaler\", StandardScaler()), (\"knn\", KNeighborsClassifier(n_neighbors=11))]\n    )\n    clf.set_params().fit(X_train, y_train)\n\n    ml_models[\"clf\"] = clf\n    ml_models[\"le\"] = le\n\n    yield\n    # Clean up the models and release resources\n\n    ml_models.clear()<\/pre>\n\n\n\n<p>After that we will add the `<code>lifespan=lifespan<\/code>` parameter in `<code>app = FastAPI()<\/code>`:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">app = FastAPI(lifespan=lifespan)<\/pre>\n\n\n\n<p>Now save and test again. Everything should work and we should see the same result as before.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Afterthought: When to train the model?<\/h3>\n\n\n\n<p>From our example, you may wonder when the model is trained. Since `<code>clf<\/code>` is trained at the beginning, i.e. when the service is launched, you may wonder why we do not train the model every time someone makes a prediction.<\/p>\n\n\n\n<p>We do not want the model to be trained every time someone makes a call, because it costs way more resources to re-train everything. Additionally, it may cause race conditions since our FastAPI application is working concurrently. This is especially the case if we use live data that changes all the time.<\/p>\n\n\n\n<p>Technically, we can set up an API to collect data and re-train the model (which we will demonstrate in the next example). Other options would be to schedule a re-train at a certain time when a certain amount of new data has been collected or to let a super user upload new data and trigger the re-training.<\/p>\n\n\n\n<p>So far, we are aiming to build a prototype that runs locally. <a href=\"https:\/\/fastapi.tiangolo.com\/deployment\/\" target=\"_blank\" rel=\"noopener\">Check out this article on deploying a FastAPI project<\/a> on a cloud service for more information.<\/p>\n\n\n\n<h4 class=\"wp-block-heading\">What is concurrency?<\/h4>\n\n\n\n<p>To put it simply, concurrency is like when you are cooking in the kitchen, and while waiting for the water to boil, you go ahead and chop the vegetables. Since, in the web service world, the server is talking to many terminals, and the communication between the server and the terminals is slower than most internal applications, so the server will not talk to and serve the terminals one by one. Instead, it will talk to and serve many of them at the same time while fulfilling their requests. You may want to check out <a href=\"https:\/\/fastapi.tiangolo.com\/async\/\" target=\"_blank\" rel=\"noopener\">this explanation<\/a> in the FastAPI documentation.<\/p>\n\n\n\n<p>In Python, this is achieved by using async code. In our FastAPI code, the use of `<code>async def<\/code>` instead of `<code>def<\/code>` is obvious evidence that FastAPI is working concurrently. There are other keywords used in Python async code, like `<code>await<\/code>` and `<code>asyncio.get_event_loop<\/code>`, but we won\u2019t be able to cover them in this blog post.&nbsp;<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">How to use FastAPI for an image classification project<\/h2>\n\n\n\n<p>To discover more FastAPI functionality, we will add an image classification model based on the <a href=\"https:\/\/keras.io\/examples\/vision\/mnist_convnet\/\" target=\"_blank\" rel=\"noopener\">MNIST example<\/a> in Keras to our application as well (we are using the TensorFlow backend). If you installed the `<code>requirements.txt<\/code>` provided, you should have Keras and Pillow installed for image processing and building a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Convolutional_neural_network\" target=\"_blank\" rel=\"noopener\">convolutional neural network (CNN)<\/a>.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">1. <strong>Refactoring<\/strong><\/h3>\n\n\n\n<p>Before we start, let\u2019s refactor our code. To make the code more organized, we will put the model setup for the penguins prediction in a function:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">def penguins_pipeline():\n    data = pd.read_csv('penguins.csv')\n    data = data.dropna()\n\n    le = preprocessing.LabelEncoder()\n    X = data[[\"bill_length_mm\", \"flipper_length_mm\"]]\n    le.fit(data[\"species\"])\n    y = le.transform(data[\"species\"])\n    X_train, X_test, y_train, y_test = train_test_split(X, y, stratify=y, random_state=0)\n    clf = Pipeline(\n        steps=[(\"scaler\", StandardScaler()), (\"knn\", KNeighborsClassifier(n_neighbors=11))]\n    )\n    clf.set_params().fit(X_train, y_train)\n\n    return clf, le<\/pre>\n\n\n\n<p>Then we rewrite the lifespan function. With <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/full-line-code-completion.html\" target=\"_blank\" rel=\"noopener\">full-line code completion<\/a> in PyCharm, it is very easy:<\/p>\n\n\n\n<figure class=\"wp-block-image\"><img decoding=\"async\" loading=\"lazy\" width=\"852\" height=\"550\" src=\"https:\/\/blog.jetbrains.com\/wp-content\/uploads\/2024\/09\/image-2.gif\" alt=\"\" class=\"wp-image-511837\"\/><\/figure>\n\n\n\n<h3 class=\"wp-block-heading\">2. <strong>Set up a CNN model for MNIST prediction<\/strong><\/h3>\n\n\n\n<p>In similar fashion as the penguin prediction model, we create a function for <a href=\"https:\/\/en.wikipedia.org\/wiki\/MNIST_database#:~:text=The%20MNIST%20database%20(Modified%20National,the%20field%20of%20machine%20learning.\" target=\"_blank\" rel=\"noopener\">MNIST<\/a> prediction (and we will store the meta parameters globally):<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># MNIST model meta parameters\nnum_classes = 10\ninput_shape = (28, 28, 1)\nbatch_size = 128\nepochs = 15\n\ndef mnist_pipeline():\n    # Load the data and split it between train and test sets\n    (x_train, y_train), _ = keras.datasets.mnist.load_data()\n\n    # Scale images to the [0, 1] range\n    x_train = x_train.astype(\"float32\") \/ 255\n\n    # Make sure images have shape (28, 28, 1)\n    x_train = np.expand_dims(x_train, -1)\n\n    # convert class vectors to binary class matrices\n    y_train = keras.utils.to_categorical(y_train, num_classes)\n\n    model = keras.Sequential(\n        [\n            keras.Input(shape=input_shape),\n            layers.Conv2D(32, kernel_size=(3, 3), activation=\"relu\"),\n            layers.MaxPooling2D(pool_size=(2, 2)),\n            layers.Conv2D(64, kernel_size=(3, 3), activation=\"relu\"),\n            layers.MaxPooling2D(pool_size=(2, 2)),\n            layers.Flatten(),\n            layers.Dropout(0.5),\n            layers.Dense(num_classes, activation=\"softmax\"),\n        ]\n    )\n\n    model.compile(loss=\"categorical_crossentropy\", optimizer=\"adam\", metrics=[\"accuracy\"])\n\n    model.fit(x_train, y_train, batch_size=batch_size, epochs=epochs, validation_split=0.1)\n\n    return model<\/pre>\n\n\n\n<p>Then add the model setup in the lifespan function:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">ml_models[\"cnn\"] = mnist_pipeline()<\/pre>\n\n\n\n<p>Note that since this is added, every time you make changes to `<code>main.py<\/code>` and save, the model will be trained again. It can take a bit of time. So in development you may want to use a dummy model that requires no training time at all or a pre-trained model instead. After training, the CNN model will be ready to go.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">3. <strong>Set up a <code>POST<\/code> endpoint for uploading an image file for prediction<\/strong><\/h3>\n\n\n\n<p>To set up an endpoint that takes an upload file, we have to use <a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/request-files\/#file-parameters-with-uploadfile\" target=\"_blank\" rel=\"noopener\">UploadFile in FastAPI<\/a>:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@app.post(\"\/predict-image\/\")\nasync def predicct_upload_file(file: UploadFile):\n    img = await file.read()\n    \n    # process image for prediction\n    img = Image.open(BytesIO(img)).convert('L')\n    img = np.array(img).astype(\"float32\") \/ 255\n    img = np.expand_dims(img, (0, -1))\n    \n    # predict the result\n    result = ml_models[\"cnn\"].predict(img).argmax(axis=-1)[0]\n    return {\"filename\": file.filename,\n            \"result\": str(result)}<\/pre>\n\n\n\n<p>Please note that this is a <code>POST<\/code> endpoint (so far we have only set up <code>GET<\/code> endpoints).<\/p>\n\n\n\n<p>Don\u2019t forget to import `<code>UploadFile<\/code>` from `<code>fastapi<\/code>`:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from fastapi import FastAPI, UploadFile<\/pre>\n\n\n\n<p>And `<code>Image<\/code>` from Pillow. We are also using `<code>BytesIO<\/code>` from the `<code>io<\/code>` module:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from PIL import Image\nfrom io import BytesIO<\/pre>\n\n\n\n<p>To test this using the PyCharm HTTP Client with a <a href=\"https:\/\/github.com\/Cheukting\/FastAPI-ml-demo\/blob\/main\/test_img0.png\" target=\"_blank\" rel=\"noopener\">test image file<\/a>, we will make use of the `<code>multipart\/form-data<\/code>` encoding. You can check out the <a href=\"https:\/\/www.jetbrains.com\/help\/pycharm\/exploring-http-syntax.html#use-multipart-form-data\" target=\"_blank\" rel=\"noopener\">HTTP request syntax here<\/a>. This is what you will put in the `<code>test_in.http<\/code>` file:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">###\n\nPOST http:\/\/127.0.0.1:8000\/predict-image\/ HTTP\/1.1\nContent-Type: multipart\/form-data; boundary=boundary\n\n--boundary\nContent-Disposition: form-data; name=\"file\"; filename=\"test_img0.png\"\n\n&lt; .\/test_img0.png\n\n--boundary\u2013<\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">4. <strong>Add an API to collect data and trigger retraining<\/strong><\/h3>\n\n\n\n<p>Now, here comes the retraining. We set up a <code>POST<\/code> endpoint like above to accept a zip file which contains training images and labels. The zip file will then be processed and the training data will be prepared. After that we will fit the CNN model again:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@app.post(\"\/upload-images\/\")\nasync def retrain_upload_file(file: UploadFile):\n\n    img_files = []\n    labels_file = None\n    train_img = None\n\n    with ZipFile(BytesIO(await file.read()), 'r') as zfile:\n        for fname in zfile.namelist():\n            if fname[-4:] == '.txt' and fname[:2] != '__':\n                labels_file = fname\n            elif fname[-4:] == '.png':\n                img_files.append(fname)\n\n        if len(img_files) == 0:\n            return {\"error\": \"No training images (png files) found.\"}\n        else:\n            for fname in sorted(img_files):\n                with zfile.open(fname) as img_file:\n                    img = img_file.read()\n\n                    # process image\n                    img = Image.open(BytesIO(img)).convert('L')\n                    img = np.array(img).astype(\"float32\") \/ 255\n                    img = np.expand_dims(img, (0, -1))\n\n                    if train_img is None:\n                        train_img = img\n                    else:\n                        train_img = np.vstack((train_img, img))\n\n        if labels_file is None:\n            return {\"error\": \"No training labels file (txt file) found.\"}\n        else:\n            with zfile.open(labels_file) as labels:\n                labels_data = labels.read()\n                labels_data = labels_data.decode(\"utf-8\").split()\n                labels_data = np.array(labels_data).astype(\"int\")\n                labels_data = keras.utils.to_categorical(labels_data, num_classes)\n\n    # retrain model\n    ml_models[\"cnn\"].fit(train_img, labels_data, batch_size=batch_size, epochs=epochs, validation_split=0.1)\n\n    return {\"message\": \"Model trained successfully.\"}<\/pre>\n\n\n\n<p>Remember to import `<code>ZipFile<\/code>`:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from zipfile import ZipFile<\/pre>\n\n\n\n<p>If we now try the endpoint with this <a href=\"https:\/\/github.com\/Cheukting\/FastAPI-ml-demo\/blob\/main\/retrain_img.zip\" target=\"_blank\" rel=\"noopener\">zip file of 1000 retraining images and labels, <\/a>you will see that it takes a moment for the response to come, as the training is taking a while:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"generic\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">POST http:\/\/127.0.0.1:8000\/upload-images\/ HTTP\/1.1\nContent-Type: multipart\/form-data; boundary=boundary\n\n--boundary\nContent-Disposition: form-data; name=\"file\"; filename=\"training_data.zip\"\n\n&lt; .\/retrain_img.zip\n\n--boundary--<\/pre>\n\n\n\n<p>Imagine the zip files contain more training data or you\u2019re retraining a more complicated model. The user would then have to wait for a long time and it would seem like things are not working for them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">5. <strong>Retrain the model with <code>BackgroundTasks<\/code><\/strong><\/h3>\n\n\n\n<p>A better way to handle retraining is, after receiving the training data, we process it and check if the data is in the right format, then give a response saying that the retraining has restarted and train the model in `<a href=\"https:\/\/fastapi.tiangolo.com\/tutorial\/background-tasks\/\" target=\"_blank\" rel=\"noopener\">BackgroundTasks<\/a>`. Here is how to do it. First, we will add `<code>BackgroundTasks<\/code>` to our `upload-images` endpoint:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">@app.post(\"\/upload-image\/\")\nasync def retrain_upload_file(file: UploadFile, background_tasks: BackgroundTasks):\n\t...<\/pre>\n\n\n\n<p>Remember to import it from `<code>fastapi<\/code>`:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">from fastapi import FastAPI, UploadFile, BackgroundTasks<\/pre>\n\n\n\n<p>Then, we will put the fitting of the model into the `<code>background_tasks<\/code>`:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\"># retrain model\n    background_tasks.add_task(\n        ml_models[\"cnn\"].fit,\n        train_img,\n        labels_data,\n        batch_size=batch_size,\n        epochs=epochs,\n        validation_split=0.1\n    )<\/pre>\n\n\n\n<p>Also, we will update the message in the response:<\/p>\n\n\n\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"python\" data-enlighter-theme=\"\" data-enlighter-highlight=\"\" data-enlighter-linenumbers=\"\" data-enlighter-lineoffset=\"\" data-enlighter-title=\"\" data-enlighter-group=\"\">return {\"message\": \"Data received successfully, model training has started.\"}<\/pre>\n\n\n\n<p>Now test the endpoint again. You will see that the response has arrived much quicker, and if you look at the <em>Run <\/em>window, you\u2019ll see that the training is running after the response has arrived.<br><br>At this point, more functionality can be added, for example, an option to notify the user later (e.g. via email) when the training is finished or track the training progress in a dashboard when a full application is built.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Conclusion and next steps<\/h2>\n\n\n\n<p>In this tutorial, we walked through how FastAPI can be used to turn machine learning models into usable APIs with minimal overhead. Starting from a simple classification model, we built GET and POST endpoints, handled input validation, managed model lifecycles with lifespan events, and explored more advanced workflows like image classification and background retraining tasks.<\/p>\n\n\n\n<p>This approach shows why FastAPI is such a strong fit for data science teams. It allows you to move quickly from experimentation to a working prototype, without needing deep backend expertise, while still following production-ready patterns.<\/p>\n\n\n\n<p>If you want to take this prototype further, here are some logical next steps:<\/p>\n\n\n\n<ul>\n<li>Dockerise the application to ensure consistent environments and simplify deployment across development, staging, and production.<\/li>\n\n\n\n<li>Deploy to the cloud using platforms like AWS, GCP, Azure, or a container-based service, so your models can be accessed reliably by other systems or users.<\/li>\n\n\n\n<li>Add authentication and authorisation to protect prediction and retraining endpoints, especially if models or data are sensitive.<\/li>\n\n\n\n<li>Introduce model versioning and monitoring to track performance, detect data drift, and safely roll out updates.<\/li>\n\n\n\n<li>Connect a frontend or dashboard to visualise predictions, training status, or model metrics for non-technical stakeholders.<\/li>\n<\/ul>\n\n\n\n<p>With these additions, a simple FastAPI prototype can evolve into a robust machine learning service that\u2019s ready for real-world use.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Develop ML FastAPI applications with PyCharm<\/h2>\n\n\n\n<p>FastAPI provides an easy way to convert your data science project into a working application in several easy steps. It is perfect for data science teams that want to provide an application prototype for their machine learning model which can be further developed into a professional web application if needed.&nbsp;<\/p>\n\n\n\n<p>PyCharm Professional is the Python IDE that allows you to develop FastAPI applications more easily with a preconfigured project for FastAPI, coding assistance, tailored run\/debug configurations, and the <em>Endpoints <\/em>tool window for managing API endpoints efficiently.<\/p>\n\n\n    <div class=\"buttons\">\n        <div class=\"buttons__row\">\n                                                <a href=\"https:\/\/www.jetbrains.com\/pycharm\/data-science\" class=\"btn\" target=\"\" rel=\"noopener\">Get a free trial of PyCharm Professional<\/a>\n                                                    <\/div>\n    <\/div>\n\n\n\n\n\n\n\n<p>In this blog post, we showed the process of providing a simple API for a pre-trained prediction model. To learn more about FastAPI, I would suggest checking out the <a href=\"https:\/\/fastapi.tiangolo.com\/\" target=\"_blank\" rel=\"noopener\">official FastAPI documentation<\/a>. If you&#8217;re choosing between different frameworks, explore how <a href=\"https:\/\/blog.jetbrains.com\/pycharm\/2023\/12\/django-vs-fastapi-which-is-the-best-python-web-framework\/\">FastAPI differs from Django<\/a>.<\/p>\n","protected":false},"author":1528,"featured_media":509816,"comment_status":"closed","ping_status":"closed","template":"","categories":[952,1401,2347],"tags":[],"cross-post-tag":[],"acf":[],"_links":{"self":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/509778"}],"collection":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm"}],"about":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/types\/pycharm"}],"author":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/users\/1528"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/comments?post=509778"}],"version-history":[{"count":10,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/509778\/revisions"}],"predecessor-version":[{"id":687063,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/pycharm\/509778\/revisions\/687063"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/media\/509816"}],"wp:attachment":[{"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/media?parent=509778"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/categories?post=509778"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/tags?post=509778"},{"taxonomy":"cross-post-tag","embeddable":true,"href":"https:\/\/blog.jetbrains.com\/es\/wp-json\/wp\/v2\/cross-post-tag?post=509778"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}